Make spacer update layout params successfully during diff-updating

Bug: 330230902
Test: updated existed tests to verify
Change-Id: I3e6879637dee01cdbe0dde514b6047b42276f306
diff --git a/wear/protolayout/protolayout-renderer/src/main/java/androidx/wear/protolayout/renderer/inflater/ProtoLayoutInflater.java b/wear/protolayout/protolayout-renderer/src/main/java/androidx/wear/protolayout/renderer/inflater/ProtoLayoutInflater.java
index ca14d6b..0751da3 100644
--- a/wear/protolayout/protolayout-renderer/src/main/java/androidx/wear/protolayout/renderer/inflater/ProtoLayoutInflater.java
+++ b/wear/protolayout/protolayout-renderer/src/main/java/androidx/wear/protolayout/renderer/inflater/ProtoLayoutInflater.java
@@ -69,6 +69,7 @@
 import android.view.ContextThemeWrapper;
 import android.view.Gravity;
 import android.view.View;
+import android.view.View.OnAttachStateChangeListener;
 import android.view.ViewGroup;
 import android.view.ViewGroup.LayoutParams;
 import android.view.ViewOutlineProvider;
@@ -2481,16 +2482,7 @@
             if (spacer.getWidth().hasLinearDimension()) {
                 handleProp(
                         spacer.getWidth().getLinearDimension(),
-                        width -> {
-                            LayoutParams lp = view.getLayoutParams();
-                            if (lp == null) {
-                                Log.e(TAG, "LayoutParams was null when updating spacer width");
-                                return;
-                            }
-
-                            lp.width = safeDpToPx(width);
-                            view.requestLayout();
-                        },
+                        widthDp -> updateLayoutWidthParam(view, widthDp),
                         posId,
                         pipelineMaker);
             }
@@ -2498,16 +2490,7 @@
             if (spacer.getHeight().hasLinearDimension()) {
                 handleProp(
                         spacer.getHeight().getLinearDimension(),
-                        height -> {
-                            LayoutParams lp = view.getLayoutParams();
-                            if (lp == null) {
-                                Log.e(TAG, "LayoutParams was null when updating spacer height");
-                                return;
-                            }
-
-                            lp.height = safeDpToPx(height);
-                            view.requestLayout();
-                        },
+                        heightDp -> updateLayoutHeightParam(view, heightDp),
                         posId,
                         pipelineMaker);
             }
@@ -2517,45 +2500,14 @@
             if (spacer.getWidth().hasLinearDimension()) {
                 handleProp(
                         spacer.getWidth().getLinearDimension(),
-                        width -> {
-                            // Update minimum width first, because LayoutParams could be null.
-                            // This calls requestLayout.
-                            int widthPx = safeDpToPx(width);
-                            view.setMinimumWidth(widthPx);
-
-                            // We still need to update layout params in case other dimension is
-                            // expand, so 0 could
-                            // be miss interpreted.
-                            LayoutParams lp = view.getLayoutParams();
-                            if (lp == null) {
-                                Log.e(TAG, "LayoutParams was null when updating spacer width");
-                                return;
-                            }
-
-                            lp.width = widthPx;
-                        },
+                        widthDp -> updateLayoutWidthParam(view, widthDp),
                         posId,
                         pipelineMaker);
             }
             if (spacer.getHeight().hasLinearDimension()) {
                 handleProp(
                         spacer.getHeight().getLinearDimension(),
-                        height -> {
-                            // Update minimum height first, because LayoutParams could be null.
-                            // This calls requestLayout.
-                            int heightPx = safeDpToPx(height);
-                            view.setMinimumHeight(heightPx);
-
-                            // We still need to update layout params in case other dimension is
-                            // expand, so 0 could be miss interpreted.
-                            LayoutParams lp = view.getLayoutParams();
-                            if (lp == null) {
-                                Log.e(TAG, "LayoutParams was null when updating spacer height");
-                                return;
-                            }
-
-                            lp.height = heightPx;
-                        },
+                        heightDp -> updateLayoutHeightParam(view, heightDp),
                         posId,
                         pipelineMaker);
             }
@@ -2576,6 +2528,49 @@
         }
     }
 
+    private void updateLayoutWidthParam(@NonNull View view, float widthDp) {
+        scheduleLayoutParamsUpdate(
+                view,
+                () -> {
+                    checkNotNull(view.getLayoutParams()).width = safeDpToPx(widthDp);
+                    view.requestLayout();
+                });
+    }
+
+    private void updateLayoutHeightParam(@NonNull View view, float heightDp) {
+        scheduleLayoutParamsUpdate(
+                view,
+                () -> {
+                    checkNotNull(view.getLayoutParams()).height = safeDpToPx(heightDp);
+                    view.requestLayout();
+                });
+    }
+
+    private void scheduleLayoutParamsUpdate(@NonNull View view, Runnable layoutParamsUpdater) {
+        if (view.getLayoutParams() != null) {
+            layoutParamsUpdater.run();
+            return;
+        }
+
+        // View#getLayoutParams() returns null if this view is not attached to a parent ViewGroup.
+        // And once the view is attached to a parent ViewGroup, it guarantees a non-null return
+        // value. Thus, we use the listener to do the update of the layout param the moment that the
+        // view is attached to window.
+        view.addOnAttachStateChangeListener(
+                new View.OnAttachStateChangeListener() {
+
+                    @Override
+                    public void onViewAttachedToWindow(@NonNull View v) {
+                        layoutParamsUpdater.run();
+                        v.removeOnAttachStateChangeListener(this);
+                    }
+
+                    @Override
+                    public void onViewDetachedFromWindow(@NonNull View v) {
+                    }
+                });
+    }
+
     @Nullable
     private InflatedView inflateArcSpacer(
             ParentViewWrapper parentViewWrapper,
diff --git a/wear/protolayout/protolayout-renderer/src/test/java/androidx/wear/protolayout/renderer/inflater/ProtoLayoutInflaterTest.java b/wear/protolayout/protolayout-renderer/src/test/java/androidx/wear/protolayout/renderer/inflater/ProtoLayoutInflaterTest.java
index 04e3f2b..d0fb91c 100644
--- a/wear/protolayout/protolayout-renderer/src/test/java/androidx/wear/protolayout/renderer/inflater/ProtoLayoutInflaterTest.java
+++ b/wear/protolayout/protolayout-renderer/src/test/java/androidx/wear/protolayout/renderer/inflater/ProtoLayoutInflaterTest.java
@@ -708,7 +708,7 @@
 
         // This tests that minimum dimension is correctly set.
         // Dimensions are in DP, but the density is currently 1 in the tests, so this is fine.
-        expect.that(tv.getMinimumWidth()).isEqualTo(width);
+        expect.that(tv.getMeasuredWidth()).isEqualTo(width);
         expect.that(tv.getMeasuredHeight()).isEqualTo(height);
     }
 
@@ -741,13 +741,9 @@
         ViewGroup boxAfterMutation = (ViewGroup) inflatedViewParent.getChildAt(0);
         View spacerAfterMutation = boxAfterMutation.getChildAt(0);
 
-        // Dimensions are in DP, but the density is currently 1 in the tests, so this is fine.
-        expect.that(spacerAfterMutation.getMeasuredWidth()).isEqualTo(0);
-        expect.that(spacerAfterMutation.getMeasuredHeight()).isEqualTo(0);
-
-        // This tests that minimum dimension is correctly set.
-        expect.that(spacerAfterMutation.getMinimumWidth()).isEqualTo(newWidth);
-        expect.that(spacerAfterMutation.getMinimumHeight()).isEqualTo(newHeight);
+        // This tests that the layout dimension is correctly set.
+        expect.that(spacerAfterMutation.getMeasuredWidth()).isEqualTo(newWidth);
+        expect.that(spacerAfterMutation.getMeasuredHeight()).isEqualTo(newHeight);
     }
 
     @Test
@@ -799,7 +795,7 @@
         Renderer renderer = renderer(layout1);
         ViewGroup inflatedViewParent = renderer.inflate();
 
-        Layout layout2 = layoutBoxWithSpacer(newHeight, newWidth, modifiers);
+        Layout layout2 = layoutBoxWithSpacer(newWidth, newHeight, modifiers);
 
         // Compute the mutation.
         ViewGroupMutation mutation =
@@ -815,8 +811,8 @@
         View spacerAfterMutation = boxAfterMutation.getChildAt(0);
 
         // Dimensions are in DP, but the density is currently 1 in the tests, so this is fine.
-        expect.that(spacerAfterMutation.getMeasuredWidth()).isEqualTo(0);
-        expect.that(spacerAfterMutation.getMeasuredHeight()).isEqualTo(0);
+        expect.that(spacerAfterMutation.getMeasuredWidth()).isEqualTo(newWidth);
+        expect.that(spacerAfterMutation.getMeasuredHeight()).isEqualTo(newHeight);
     }
 
     @Test