[go: nahoru, domu]

Refactor ProtoLayoutViewInstance to return ListenableFuture

This will also fix minor bugs with closing/detaching view and
correctly wait for result in TileRenderer.

Also, this updates TileRenderer and its usage to be async.

Bug: 271076323
Test: N/A
Relnote: "ProtoLayoutViewInstance now returns ListenableFuture."
Change-Id: I2f2b9b0229dbf684445e4afad7a115de5892d869
diff --git a/wear/protolayout/protolayout-renderer/api/restricted_current.txt b/wear/protolayout/protolayout-renderer/api/restricted_current.txt
index f78374c..97e844a 100644
--- a/wear/protolayout/protolayout-renderer/api/restricted_current.txt
+++ b/wear/protolayout/protolayout-renderer/api/restricted_current.txt
@@ -5,18 +5,27 @@
     ctor public ProtoLayoutViewInstance(androidx.wear.protolayout.renderer.impl.ProtoLayoutViewInstance.Config);
     method public void close() throws java.lang.Exception;
     method @UiThread public void detach(android.view.ViewGroup);
-    method @UiThread public void renderAndAttach(androidx.wear.protolayout.proto.LayoutElementProto.Layout, androidx.wear.protolayout.proto.ResourceProto.Resources, android.view.ViewGroup);
+    method @UiThread public com.google.common.util.concurrent.ListenableFuture<java.lang.Void!> renderAndAttach(androidx.wear.protolayout.proto.LayoutElementProto.Layout, androidx.wear.protolayout.proto.ResourceProto.Resources, android.view.ViewGroup);
   }
 
   @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static final class ProtoLayoutViewInstance.Config {
     method public com.google.common.util.concurrent.ListeningExecutorService getBgExecutorService();
     method public String getClickableIdExtra();
+    method public androidx.wear.protolayout.renderer.impl.ProtoLayoutViewInstance.LoadActionListener getLoadActionListener();
     method public androidx.wear.protolayout.expression.pipeline.sensor.SensorGateway? getSensorGateway();
     method public androidx.wear.protolayout.expression.pipeline.StateStore? getStateStore();
     method public android.content.Context getUiContext();
     method public com.google.common.util.concurrent.ListeningExecutorService getUiExecutorService();
   }
 
+  @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static final class ProtoLayoutViewInstance.Config.Builder {
+    ctor public ProtoLayoutViewInstance.Config.Builder(android.content.Context, com.google.common.util.concurrent.ListeningExecutorService, com.google.common.util.concurrent.ListeningExecutorService, String);
+    method public androidx.wear.protolayout.renderer.impl.ProtoLayoutViewInstance.Config build();
+    method public androidx.wear.protolayout.renderer.impl.ProtoLayoutViewInstance.Config.Builder setLoadActionListener(androidx.wear.protolayout.renderer.impl.ProtoLayoutViewInstance.LoadActionListener);
+    method public androidx.wear.protolayout.renderer.impl.ProtoLayoutViewInstance.Config.Builder setSensorGateway(androidx.wear.protolayout.expression.pipeline.sensor.SensorGateway);
+    method public androidx.wear.protolayout.renderer.impl.ProtoLayoutViewInstance.Config.Builder setStateStore(androidx.wear.protolayout.expression.pipeline.StateStore);
+  }
+
   public static interface ProtoLayoutViewInstance.LoadActionListener {
     method public void onClick(androidx.wear.protolayout.proto.StateProto.State);
   }
diff --git a/wear/protolayout/protolayout-renderer/src/main/java/androidx/wear/protolayout/renderer/impl/ProtoLayoutViewInstance.java b/wear/protolayout/protolayout-renderer/src/main/java/androidx/wear/protolayout/renderer/impl/ProtoLayoutViewInstance.java
index a655bed..5b2971f 100644
--- a/wear/protolayout/protolayout-renderer/src/main/java/androidx/wear/protolayout/renderer/impl/ProtoLayoutViewInstance.java
+++ b/wear/protolayout/protolayout-renderer/src/main/java/androidx/wear/protolayout/renderer/impl/ProtoLayoutViewInstance.java
@@ -39,7 +39,7 @@
 import androidx.wear.protolayout.expression.pipeline.sensor.SensorGateway;
 import androidx.wear.protolayout.proto.LayoutElementProto.Layout;
 import androidx.wear.protolayout.proto.ResourceProto;
-import androidx.wear.protolayout.proto.StateProto;
+import androidx.wear.protolayout.proto.StateProto.State;
 import androidx.wear.protolayout.renderer.ProtoLayoutTheme;
 import androidx.wear.protolayout.renderer.ProtoLayoutVisibilityState;
 import androidx.wear.protolayout.renderer.dynamicdata.ProtoLayoutDynamicDataPipeline;
@@ -55,6 +55,7 @@
 import com.google.common.util.concurrent.Futures;
 import com.google.common.util.concurrent.ListenableFuture;
 import com.google.common.util.concurrent.ListeningExecutorService;
+import com.google.common.util.concurrent.SettableFuture;
 
 import java.util.concurrent.CancellationException;
 import java.util.concurrent.ExecutionException;
@@ -64,7 +65,6 @@
  * inflated on a background thread, the first time it is attached to the carousel. As much of the
  * inflation as possible will be done in the background, with only the final attachment of the
  * generated layout to a parent container done on the UI thread.
- *
  */
 @RestrictTo(Scope.LIBRARY_GROUP_PREFIX)
 public class ProtoLayoutViewInstance implements AutoCloseable {
@@ -79,7 +79,7 @@
          *
          * @param nextState The state that the next layout should be in.
          */
-        void onClick(@NonNull StateProto.State nextState);
+        void onClick(@NonNull State nextState);
     }
 
     private static final int DEFAULT_MAX_CONCURRENT_RUNNING_ANIMATIONS = 4;
@@ -144,7 +144,6 @@
     /**
      * This is used to provide a {@link ResourceResolvers} object to the {@link
      * ProtoLayoutViewInstance} allowing it to query {@link ResourceProto.Resources} when needed.
-     *
      */
     @RestrictTo(Scope.LIBRARY_GROUP)
     public interface ResourceResolversProvider {
@@ -202,7 +201,6 @@
             return Futures.immediateVoidFuture();
         }
     }
-
     /** Result of a {@link #renderOrComputeMutations} call when a failure has happened. */
     static final class FailedRenderResult implements RenderResult {
         @Override
@@ -219,7 +217,6 @@
             return Futures.immediateVoidFuture();
         }
     }
-
     /**
      * Result of a {@link #renderOrComputeMutations} call when the layout has been inflated into a
      * new parent.
@@ -243,13 +240,16 @@
                 @NonNull ViewGroup parent,
                 @Nullable ViewGroup prevInflateParent,
                 boolean isReattaching) {
-            checkNotNull(
-                    mNewInflateParentData.mInflateResult,
-                    TAG + " - inflated result was null, but inflating into new parent requested.")
-                    .updateDynamicDataPipeline(isReattaching);
+            InflateResult inflateResult =
+                    checkNotNull(
+                            mNewInflateParentData.mInflateResult,
+                            TAG
+                                    + " - inflated result was null, but inflating into new parent"
+                                    + " requested.");
+            inflateResult.updateDynamicDataPipeline(isReattaching);
             parent.removeAllViews();
             parent.addView(
-                    checkNotNull(mNewInflateParentData.mInflateResult).inflateParent,
+                    inflateResult.inflateParent,
                     new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
             return Futures.immediateVoidFuture();
         }
@@ -344,20 +344,14 @@
             return mUiContext;
         }
 
-        /**
-         * Returns the Android Resources object for the renderer package.
-         *
-         */
+        /** Returns the Android Resources object for the renderer package. */
         @RestrictTo(Scope.LIBRARY)
         @NonNull
         public Resources getRendererResources() {
             return mRendererResources;
         }
 
-        /**
-         * Returns provider for resource resolver.
-         *
-         */
+        /** Returns provider for resolving resources. */
         @RestrictTo(Scope.LIBRARY)
         @NonNull
         public ResourceResolversProvider getResourceResolversProvider() {
@@ -382,12 +376,8 @@
             return mStateStore;
         }
 
-        /**
-         * Returns listener for load actions.
-         *
-         */
+        /** Returns listener for load actions. */
         @NonNull
-        @RestrictTo(Scope.LIBRARY)
         public LoadActionListener getLoadActionListener() {
             return mLoadActionListener;
         }
@@ -410,56 +400,38 @@
             return mClickableIdExtra;
         }
 
-        /**
-         * Returns whether animations are enabled.
-         *
-         */
+        /** Returns whether animations are enabled. */
         @RestrictTo(Scope.LIBRARY)
         public boolean getAnimationEnabled() {
             return mAnimationEnabled;
         }
 
-        /**
-         * Returns how many animations can be concurrently run.
-         *
-         */
+        /** Returns how many animations can be concurrently run. */
         @RestrictTo(Scope.LIBRARY)
         public int getRunningAnimationsLimit() {
             return mRunningAnimationsLimit;
         }
 
-        /**
-         * Returns whether updates are enabled.
-         *
-         */
+        /** Returns whether updates are enabled. */
         @RestrictTo(Scope.LIBRARY)
         public boolean getUpdatesEnabled() {
             return mUpdatesEnabled;
         }
 
-        /**
-         * Returns whether adaptive updates are enabled.
-         *
-         */
+        /** Returns whether adaptive updates are enabled. */
         @RestrictTo(Scope.LIBRARY)
         public boolean getAdaptiveUpdateRatesEnabled() {
             return mAdaptiveUpdateRatesEnabled;
         }
 
-        /**
-         * Returns whether view is fully visible.
-         *
-         */
+        /** Returns whether view is fully visible. */
         @RestrictTo(Scope.LIBRARY)
         public boolean getIsViewFullyVisible() {
             return mIsViewFullyVisible;
         }
 
-        /**
-         * Builder for {@link Config}.
-         *
-         */
-        @RestrictTo(Scope.LIBRARY)
+        /** Builder for {@link Config}. */
+        @RestrictTo(Scope.LIBRARY_GROUP_PREFIX)
         public static final class Builder {
             @NonNull private final Context mUiContext;
             @Nullable private Resources mRendererResources;
@@ -497,10 +469,7 @@
                 this.mClickableIdExtra = clickableIdExtra;
             }
 
-            /**
-             * Sets provider for resolving resources.
-             *
-             */
+            /** Sets provider for resolving resources. */
             @NonNull
             @RestrictTo(Scope.LIBRARY)
             public Builder setResourceResolverProvider(
@@ -514,12 +483,10 @@
              * retrieved with {@link
              * android.content.pm.PackageManager#getResourcesForApplication(String)}. If not
              * specified, this is retrieved from the Ui Context.
-             *
              */
             @NonNull
             @RestrictTo(Scope.LIBRARY)
-            public Builder setRendererResources(
-                    @NonNull Resources rendererResources) {
+            public Builder setRendererResources(@NonNull Resources rendererResources) {
                 this.mRendererResources = rendererResources;
                 return this;
             }
@@ -544,10 +511,8 @@
             /**
              * Sets the listener for clicks that will cause contents to be reloaded. Defaults to
              * no-op.
-             *
              */
             @NonNull
-            @RestrictTo(Scope.LIBRARY)
             public Builder setLoadActionListener(@NonNull LoadActionListener loadActionListener) {
                 this.mLoadActionListener = loadActionListener;
                 return this;
@@ -556,7 +521,6 @@
             /**
              * Sets whether animation are enabled. If disabled, none of the animation will be
              * played.
-             *
              */
             @RestrictTo(Scope.LIBRARY)
             @NonNull
@@ -565,10 +529,7 @@
                 return this;
             }
 
-            /**
-             * Sets the limit to how much concurrently running animations are allowed.
-             *
-             */
+            /** Sets the limit to how much concurrently running animations are allowed. */
             @RestrictTo(Scope.LIBRARY)
             @NonNull
             public Builder setRunningAnimationsLimit(int runningAnimationsLimit) {
@@ -576,10 +537,7 @@
                 return this;
             }
 
-            /**
-             * Sets whether sending updates is enabled.
-             *
-             */
+            /** Sets whether sending updates is enabled. */
             @RestrictTo(Scope.LIBRARY)
             @NonNull
             public Builder setUpdatesEnabled(boolean updatesEnabled) {
@@ -587,10 +545,7 @@
                 return this;
             }
 
-            /**
-             * Sets whether adaptive updates rates is enabled.
-             *
-             */
+            /** Sets whether adaptive updates rates is enabled. */
             @RestrictTo(Scope.LIBRARY)
             @NonNull
             public Builder setAdaptiveUpdateRatesEnabled(boolean adaptiveUpdateRatesEnabled) {
@@ -598,10 +553,7 @@
                 return this;
             }
 
-            /**
-             * Sets whether the view is fully visible.
-             *
-             */
+            /** Sets whether the view is fully visible. */
             @RestrictTo(Scope.LIBRARY)
             @NonNull
             public Builder setIsViewFullyVisible(boolean isViewFullyVisible) {
@@ -787,18 +739,29 @@
      * <p>Note also that this method must be called from the UI thread;
      */
     @UiThread
-    @SuppressWarnings(
-            "ReferenceEquality") // layout == prevLayout is intentional (and enough in this case)
-    public void renderAndAttach(
+    @SuppressWarnings({
+        "ReferenceEquality",
+        "ExecutorTaskName"
+    }) // layout == prevLayout is intentional (and enough in this case)
+    @NonNull
+    public ListenableFuture<Void> renderAndAttach(
             @NonNull Layout layout,
             @NonNull ResourceProto.Resources resources,
             @NonNull ViewGroup parent) {
-        if (mAttachParent == parent && layout == mPrevLayout) {
-            return;
+        if (mAttachParent == null) {
+            mAttachParent = parent;
+            mAttachParent.removeAllViews();
+            // Preload it with the previous layout if we have one.
+            if (mInflateParent != null) {
+                mAttachParent.addView(mInflateParent);
+            }
+        } else if (mAttachParent != parent) {
+            throw new IllegalStateException("ProtoLayoutViewInstance is already attached!");
         }
 
-        if (mAttachParent != null && mAttachParent != parent) {
-            throw new IllegalStateException("ProtoLayoutViewInstance is already attached!");
+        if (layout == mPrevLayout && mInflateParent != null) {
+            // Nothing to do.
+            return Futures.immediateVoidFuture();
         }
 
         boolean isReattaching = false;
@@ -807,7 +770,7 @@
                 // There is an ongoing rendering operation. We'll skip this request as a missed
                 // frame.
                 Log.w(TAG, "Skipped layout update: previous layout update hasn't finished yet.");
-                return;
+                return Futures.immediateCancelledFuture();
             } else if (layout == mPrevLayout && mCanReattachWithoutRendering) {
                 isReattaching = true;
             } else {
@@ -815,57 +778,69 @@
             }
         }
 
-        @Nullable ViewGroup prevInflateParent = getOnlyChildViewGroup(parent);
+        @Nullable ViewGroup prevInflateParent = getOnlyChildViewGroup(mAttachParent);
         @Nullable
         RenderedMetadata prevRenderedMetadata =
                 prevInflateParent != null
                         ? ProtoLayoutInflater.getRenderedMetadata(prevInflateParent)
                         : null;
-        mAttachParent = parent;
 
         if (mRenderFuture == null) {
             mPrevLayout = layout;
             mRenderFuture =
-                    mBgExecutorService.submit(
-                            () ->
-                                    renderOrComputeMutations(
-                                            layout, resources, prevRenderedMetadata));
+                    mBgExecutorService.submit(() ->
+                                            renderOrComputeMutations(
+                                                    layout, resources, prevRenderedMetadata));
             mCanReattachWithoutRendering = false;
         }
-        if (!mRenderFuture.isDone()) {
+        SettableFuture<Void> result = SettableFuture.create();
+        if (!checkNotNull(mRenderFuture).isDone()) {
             mRenderFuture.addListener(
                     () -> {
                         // Ensure that this inflater is attached to the same parent as when this
-                        // listener was created. If not, something has re-attached us in the
-                        // time it took for the inflater to execute.
+                        // listener was created. If not, something has re-attached us in the time it
+                        // took for the inflater to execute.
                         if (mAttachParent == parent) {
                             try {
-                                postInflate(
-                                        parent,
-                                        prevInflateParent,
-                                        checkNotNull(mRenderFuture).get(),
-                                        /* isReattaching= */ false,
-                                        layout,
-                                        resources);
-                            } catch (ExecutionException | InterruptedException e) {
+                                result.setFuture(
+                                        postInflate(
+                                                parent,
+                                                prevInflateParent,
+                                                checkNotNull(mRenderFuture).get(),
+                                                /* isReattaching= */ false,
+                                                layout,
+                                                resources));
+                            } catch (ExecutionException
+                                    | InterruptedException
+                                    | CancellationException e) {
                                 Log.e(TAG, "Failed to render layout", e);
+                                result.setException(e);
                             }
+                        } else {
+                            Log.w(
+                                    TAG,
+                                    "Layout is rendered, but inflater is no longer attached to the"
+                                            + " same parent. Cancelling inflation.");
+                            result.cancel(/* mayInterruptIfRunning= */ false);
                         }
                     },
                     mUiExecutorService);
         } else {
             try {
-                postInflate(
-                        parent,
-                        prevInflateParent,
-                        mRenderFuture.get(),
-                        isReattaching,
-                        layout,
-                        resources);
+                result.setFuture(
+                        postInflate(
+                                parent,
+                                prevInflateParent,
+                                mRenderFuture.get(),
+                                isReattaching,
+                                layout,
+                                resources));
             } catch (ExecutionException | InterruptedException | CancellationException e) {
                 Log.e(TAG, "Failed to render layout", e);
+                result.setException(e);
             }
         }
+        return result;
     }
 
     @Nullable
@@ -880,7 +855,9 @@
     }
 
     @UiThread
-    private void postInflate(
+    @SuppressWarnings("ExecutorTaskName")
+    @NonNull
+    private ListenableFuture<Void> postInflate(
             @NonNull ViewGroup parent,
             @Nullable ViewGroup prevInflateParent,
             @NonNull RenderResult renderResult,
@@ -892,40 +869,50 @@
         if (renderResult instanceof InflatedIntoNewParentRenderResult) {
             InflateParentData newInflateParentData =
                     ((InflatedIntoNewParentRenderResult) renderResult).mNewInflateParentData;
-            mInflateParent = checkNotNull(
-                    newInflateParentData.mInflateResult,
-                    TAG + " - inflated result was null, but inflating was requested.")
-                    .inflateParent;
+            mInflateParent =
+                    checkNotNull(
+                                    newInflateParentData.mInflateResult,
+                                    TAG
+                                            + " - inflated result was null, but inflating was"
+                                            + " requested.")
+                            .inflateParent;
         }
 
         ListenableFuture<Void> postInflateFuture =
                 renderResult.postInflate(parent, prevInflateParent, isReattaching);
+        SettableFuture<Void> result = SettableFuture.create();
         if (!postInflateFuture.isDone()) {
             postInflateFuture.addListener(
                     () -> {
                         try {
-                            postInflateFuture.get();
-                        } catch (ExecutionException | InterruptedException e) {
-                            handlePostInflateFailure(
-                                    e, layout, resources, prevInflateParent, parent);
+                            result.set(postInflateFuture.get());
+                        } catch (ExecutionException
+                                | InterruptedException
+                                | CancellationException e) {
+                            result.setFuture(
+                                    handlePostInflateFailure(
+                                            e, layout, resources, prevInflateParent, parent));
                         }
                     },
                     mUiExecutorService);
         } else {
             try {
                 postInflateFuture.get();
+                return Futures.immediateVoidFuture();
             } catch (ExecutionException
                     | InterruptedException
                     | CancellationException
                     | ViewMutationException e) {
-                handlePostInflateFailure(e, layout, resources, prevInflateParent, parent);
+                return handlePostInflateFailure(e, layout, resources, prevInflateParent, parent);
             }
         }
+        return result;
     }
 
     @UiThread
     @SuppressWarnings("ReferenceEquality") // layout == prevLayout is intentional
-    private void handlePostInflateFailure(
+    @NonNull
+    private ListenableFuture<Void> handlePostInflateFailure(
             @NonNull Throwable error,
             @NonNull Layout layout,
             @NonNull ResourceProto.Resources resources,
@@ -940,11 +927,12 @@
                 // Clear rendering metadata and prevLayout to force a full reinflation.
                 ProtoLayoutInflater.clearRenderedMetadata(checkNotNull(prevInflateParent));
                 mPrevLayout = null;
-                renderAndAttach(layout, resources, parent);
+                return renderAndAttach(layout, resources, parent);
             }
         } else {
             Log.e(TAG, "postInflate failed.", error);
         }
+        return Futures.immediateFailedFuture(error);
     }
 
     /**
@@ -958,10 +946,6 @@
             throw new IllegalStateException("Layout is not attached to parent " + parent);
         }
         detachInternal();
-
-        if (mInflateParent != null) {
-            parent.removeView(mInflateParent);
-        }
     }
 
     @UiThread
@@ -970,13 +954,23 @@
             mRenderFuture.cancel(/* mayInterruptIfRunning= */ false);
         }
         setLayoutVisibility(ProtoLayoutVisibilityState.VISIBILITY_STATE_INVISIBLE);
+
+        ViewGroup inflateParent = mInflateParent;
+        if (inflateParent != null) {
+            ViewGroup parent = (ViewGroup) inflateParent.getParent();
+            if (mAttachParent != null && mAttachParent != parent) {
+                Log.w(TAG, "inflateParent was attached to the wrong parent.");
+            }
+            if (parent != null) {
+                parent.removeView(inflateParent);
+            }
+        }
         mAttachParent = null;
     }
 
     /**
      * Sets whether updates are enabled for this layout. When disabled, updates through the data
      * pipeline (e.g. health updates) will be suppressed.
-     *
      */
     @RestrictTo(Scope.LIBRARY)
     @UiThread
@@ -987,9 +981,7 @@
         }
     }
 
-    /** Sets the visibility state for this layout.
-     *
-     */
+    /** Sets the visibility state for this layout. */
     @RestrictTo(Scope.LIBRARY)
     @UiThread
     public void setLayoutVisibility(@ProtoLayoutVisibilityState int visibility) {
@@ -1013,6 +1005,8 @@
     @Override
     public void close() throws Exception {
         detachInternal();
+        mRenderFuture = null;
+        mPrevLayout = null;
         if (mDataPipeline != null) {
             mDataPipeline.close();
         }
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 625a0d7..55a4fc1 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
@@ -21,6 +21,9 @@
 import static androidx.wear.protolayout.renderer.common.ProtoLayoutDiffer.ROOT_NODE_ID;
 import static androidx.wear.protolayout.renderer.common.ProtoLayoutDiffer.getParentNodePosId;
 
+import static com.google.common.util.concurrent.Futures.immediateFailedFuture;
+import static com.google.common.util.concurrent.Futures.immediateVoidFuture;
+
 import static java.lang.Math.max;
 import static java.lang.Math.round;
 
@@ -79,7 +82,6 @@
 import androidx.annotation.Nullable;
 import androidx.annotation.UiThread;
 import androidx.annotation.VisibleForTesting;
-import androidx.concurrent.futures.ResolvableFuture;
 import androidx.core.content.ContextCompat;
 import androidx.core.view.AccessibilityDelegateCompat;
 import androidx.core.view.ViewCompat;
@@ -175,8 +177,8 @@
 import androidx.wear.widget.ArcLayout;
 import androidx.wear.widget.CurvedTextView;
 
-import com.google.common.util.concurrent.Futures;
 import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.SettableFuture;
 
 import java.lang.ref.WeakReference;
 import java.util.ArrayList;
@@ -216,10 +218,9 @@
     /**
      * Default maximum raw byte size for a bitmap drawable.
      *
-     * @see <a href="https://cs.android.com/android/_/android/platform/frameworks/base
-     * /+/d01036ee5893357db577c961119fb85825247f03:graphics/java/android/graphics/
-     * RecordingCanvas.java;l=44;bpv=1;bpt=0;drc=00af5271dabd578397176eda0cd7a66c55fac59a"> The
-     * framework enforced max size</a>
+     * @see <a
+     *     href="https://cs.android.com/android/_/android/platform/frameworks/base/+/d01036ee5893357db577c961119fb85825247f03:graphics/java/android/graphics/RecordingCanvas.java;l=44;bpv=1;bpt=0;drc=00af5271dabd578397176eda0cd7a66c55fac59a">
+     *     The framework enforced max size</a>
      */
     private static final int DEFAULT_MAX_BITMAP_RAW_SIZE = 20 * 1024 * 1024;
 
@@ -241,8 +242,7 @@
     // This is pretty badly named; TruncateAt specifies where to place the ellipsis (or whether to
     // marquee). Disabling truncation with null actually disables the _ellipsis_, but text will
     // still be truncated.
-    @Nullable
-    private static final TruncateAt TEXT_OVERFLOW_DEFAULT = null;
+    @Nullable private static final TruncateAt TEXT_OVERFLOW_DEFAULT = null;
 
     private static final int TEXT_COLOR_DEFAULT = 0xFFFFFFFF;
     private static final int TEXT_MAX_LINES_DEFAULT = 1;
@@ -253,14 +253,12 @@
                     .setWrappedDimension(WrappedDimensionProp.getDefaultInstance())
                     .build();
 
-    @ArcLayout.AnchorType
-    private static final int ARC_ANCHOR_DEFAULT = ArcLayout.ANCHOR_CENTER;
+    @ArcLayout.AnchorType private static final int ARC_ANCHOR_DEFAULT = ArcLayout.ANCHOR_CENTER;
 
     // White
     private static final int LINE_COLOR_DEFAULT = 0xFFFFFFFF;
 
-    static final PendingLayoutParams NO_OP_PENDING_LAYOUT_PARAMS =
-            layoutParams -> layoutParams;
+    static final PendingLayoutParams NO_OP_PENDING_LAYOUT_PARAMS = layoutParams -> layoutParams;
 
     final Context mUiContext;
 
@@ -277,8 +275,7 @@
     private final boolean mAllowLayoutChangingBindsWithoutDefault;
     final String mClickableIdExtra;
 
-    @Nullable
-    final Executor mLoadActionExecutor;
+    @Nullable final Executor mLoadActionExecutor;
     final LoadActionListener mLoadActionListener;
     final boolean mAnimationEnabled;
 
@@ -319,7 +316,7 @@
          * Update the DynamicDataPipeline with new nodes that were stored during the layout update.
          *
          * @param isReattaching if True, this layout is being reattached and will skip content
-         *                      transition animations.
+         *     transition animations.
          */
         @UiThread
         public void updateDynamicDataPipeline(boolean isReattaching) {
@@ -359,17 +356,15 @@
         private int mNumMissingChildren;
 
         /**
-         * @param view               The {@link View} that has been inflated.
-         * @param layoutParams       The {@link LayoutParams} that must be used when attaching the
-         *                           inflated view to a parent.
-         * @param childLayoutParams  The {@link LayoutParams} that must be applied to children
-         *                           carried over from a previous layout.
+         * @param view The {@link View} that has been inflated.
+         * @param layoutParams The {@link LayoutParams} that must be used when attaching the
+         *     inflated view to a parent.
+         * @param childLayoutParams The {@link LayoutParams} that must be applied to children
+         *     carried over from a previous layout.
          * @param numMissingChildren Non-zero if {@code view} is a {@link ViewGroup} whose children
-         *                           have not been added. This means that before using this view
-         *                           in a layout, its children
-         *                           must be copied from the {@link ViewGroup} that represents
-         *                           the previous version of
-         *                           this layout element.
+         *     have not been added. This means that before using this view in a layout, its children
+         *     must be copied from the {@link ViewGroup} that represents the previous version of
+         *     this layout element.
          */
         InflatedView(
                 View view,
@@ -441,8 +436,7 @@
      * the renderer.
      */
     private static final class ParentViewWrapper {
-        @Nullable
-        private final ViewGroup mParent;
+        @Nullable private final ViewGroup mParent;
         private final ViewProperties mParentProps;
 
         ParentViewWrapper(ViewGroup parent, LayoutParams parentLayoutParams) {
@@ -487,24 +481,15 @@
 
     /** Config class for ProtoLayoutInflater */
     public static final class Config {
-        @NonNull
-        private final Context mUiContext;
-        @NonNull
-        private final Layout mLayout;
-        @NonNull
-        private final ResourceResolvers mLayoutResourceResolvers;
-        @Nullable
-        private final Executor mLoadActionExecutor;
-        @NonNull
-        private final LoadActionListener mLoadActionListener;
-        @NonNull
-        private final Resources mRendererResources;
-        @NonNull
-        private final ProtoLayoutTheme mProtoLayoutTheme;
-        @Nullable
-        private final ProtoLayoutDynamicDataPipeline mDataPipeline;
-        @NonNull
-        private final String mClickableIdExtra;
+        @NonNull private final Context mUiContext;
+        @NonNull private final Layout mLayout;
+        @NonNull private final ResourceResolvers mLayoutResourceResolvers;
+        @Nullable private final Executor mLoadActionExecutor;
+        @NonNull private final LoadActionListener mLoadActionListener;
+        @NonNull private final Resources mRendererResources;
+        @NonNull private final ProtoLayoutTheme mProtoLayoutTheme;
+        @Nullable private final ProtoLayoutDynamicDataPipeline mDataPipeline;
+        @NonNull private final String mClickableIdExtra;
         private final boolean mAnimationEnabled;
         private final boolean mAllowLayoutChangingBindsWithoutDefault;
 
@@ -612,33 +597,23 @@
 
         /** Builder for the Config class. */
         public static final class Builder {
-            @NonNull
-            private final Context mUiContext;
-            @NonNull
-            private final Layout mLayout;
-            @NonNull
-            private final ResourceResolvers mLayoutResourceResolvers;
-            @Nullable
-            private Executor mLoadActionExecutor;
-            @Nullable
-            private LoadActionListener mLoadActionListener;
-            @NonNull
-            private Resources mRendererResources;
-            @Nullable
-            private ProtoLayoutTheme mProtoLayoutTheme;
-            @Nullable
-            private ProtoLayoutDynamicDataPipeline mDataPipeline = null;
+            @NonNull private final Context mUiContext;
+            @NonNull private final Layout mLayout;
+            @NonNull private final ResourceResolvers mLayoutResourceResolvers;
+            @Nullable private Executor mLoadActionExecutor;
+            @Nullable private LoadActionListener mLoadActionListener;
+            @NonNull private Resources mRendererResources;
+            @Nullable private ProtoLayoutTheme mProtoLayoutTheme;
+            @Nullable private ProtoLayoutDynamicDataPipeline mDataPipeline = null;
             private boolean mAnimationEnabled = true;
             private boolean mAllowLayoutChangingBindsWithoutDefault = false;
-            @Nullable
-            private String mClickableIdExtra;
+            @Nullable private String mClickableIdExtra;
 
             /**
-             * @param uiContext               A {@link Context} suitable for interacting with UI
-             *                                with.
-             * @param layout                  The layout to be rendered.
+             * @param uiContext A {@link Context} suitable for interacting with UI with.
+             * @param layout The layout to be rendered.
              * @param layoutResourceResolvers Resolvers for the resources used for rendering this
-             *                                layout.
+             *     layout.
              */
             public Builder(
                     @NonNull Context uiContext,
@@ -748,8 +723,7 @@
                 }
 
                 if (mLoadActionListener == null) {
-                    mLoadActionListener = p -> {
-                    };
+                    mLoadActionListener = p -> {};
                 }
                 if (mProtoLayoutTheme == null) {
                     this.mProtoLayoutTheme = ProtoLayoutThemeImpl.defaultTheme(mUiContext);
@@ -1182,7 +1156,8 @@
                                                                         clickable
                                                                                 .getOnClick()
                                                                                 .getLoadAction(),
-                                                                        clickable.getId()))));
+                                                                        clickable
+                                                                                .getId()))));
                 break;
             case VALUE_NOT_SET:
                 break;
@@ -1879,9 +1854,10 @@
         // bottom of FrameLayout#onMeasure).
         //
         // To work around this (without copying the whole of FrameLayout just to change a "1" to
-        // "0"), we add a Space element in if there is one MATCH_PARENT child. This has a tiny cost
-        // to the measure pass, and negligible cost to layout/draw (since it doesn't take part in
-        // those passes).
+        // "0"),
+        // we add a Space element in if there is one MATCH_PARENT child. This has a tiny cost to the
+        // measure pass, and negligible cost to layout/draw (since it doesn't take part in those
+        // passes).
         int numMatchParentChildren = 0;
         for (int i = 0; i < frame.getChildCount(); i++) {
             LayoutParams lp = frame.getChildAt(i).getLayoutParams();
@@ -1923,11 +1899,12 @@
             spaceWrapperLayoutParams.height = LayoutParams.WRAP_CONTENT;
 
             // Technically speaking, this logic isn't 100% accurate. In legacy size-changing mode
-            // (before value_for_layout was introduced), apps may not set value_for_layout. That's
-            // fine; the needsSizeWrapper checks will catch that. It's possible that one dimension
-            // has value_for_layout set though, and the other relies on legacy size changing mode.
-            // We don't deal with that case; if value_for_layout is present on one dimension, and
-            // both are dynamic, then it must be set on both dimensions.
+            // (before
+            // value_for_layout was introduced), apps may not set value_for_layout. That's fine; the
+            // needsSizeWrapper checks will catch that. It's possible that one dimension has
+            // value_for_layout set though, and the other relies on legacy size changing mode. We
+            // don't deal with that case; if value_for_layout is present on one dimension, and both
+            // are dynamic, then it must be set on both dimensions.
             if (spacer.getWidth().getLinearDimension().hasDynamicValue()) {
                 float widthForLayout = spacer.getWidth().getLinearDimension().getValueForLayout();
                 spaceWrapperLayoutParams.width = safeDpToPx(widthForLayout);
@@ -1940,13 +1917,13 @@
 
             int gravity =
                     horizontalAlignmentToGravity(
-                            spacer.getWidth()
-                                    .getLinearDimension()
-                                    .getHorizontalAlignmentForLayout())
+                                    spacer.getWidth()
+                                            .getLinearDimension()
+                                            .getHorizontalAlignmentForLayout())
                             | verticalAlignmentToGravity(
-                            spacer.getHeight()
-                                    .getLinearDimension()
-                                    .getVerticalAlignmentForLayout());
+                                    spacer.getHeight()
+                                            .getLinearDimension()
+                                            .getVerticalAlignmentForLayout());
             FrameLayout.LayoutParams frameLayoutLayoutParams =
                     new FrameLayout.LayoutParams(layoutParams);
             frameLayoutLayoutParams.gravity = gravity;
@@ -2057,30 +2034,31 @@
                     lengthDegrees = max(0, angularLength.getDegrees().getValue());
                     break;
 
-                case EXPANDED_ANGULAR_DIMENSION: {
-                    float weight =
-                            angularLength
-                                    .getExpandedAngularDimension()
-                                    .getLayoutWeight()
-                                    .getValue();
-                    if (weight == 0 && thicknessPx == 0) {
-                        return null;
+                case EXPANDED_ANGULAR_DIMENSION:
+                    {
+                        float weight =
+                                angularLength
+                                        .getExpandedAngularDimension()
+                                        .getLayoutWeight()
+                                        .getValue();
+                        if (weight == 0 && thicknessPx == 0) {
+                            return null;
+                        }
+                        layoutParams.setWeight(weight);
+
+                        space.setThickness(thicknessPx);
+
+                        View wrappedView =
+                                applyModifiersToArcLayoutView(
+                                        space, spacer.getModifiers(), posId, pipelineMaker);
+                        parentViewWrapper.maybeAddView(wrappedView, layoutParams);
+
+                        return new InflatedView(
+                                wrappedView,
+                                parentViewWrapper
+                                        .getParentProperties()
+                                        .applyPendingChildLayoutParams(layoutParams));
                     }
-                    layoutParams.setWeight(weight);
-
-                    space.setThickness(thicknessPx);
-
-                    View wrappedView =
-                            applyModifiersToArcLayoutView(
-                                    space, spacer.getModifiers(), posId, pipelineMaker);
-                    parentViewWrapper.maybeAddView(wrappedView, layoutParams);
-
-                    return new InflatedView(
-                            wrappedView,
-                            parentViewWrapper
-                                    .getParentProperties()
-                                    .applyPendingChildLayoutParams(layoutParams));
-                }
 
                 case INNER_NOT_SET:
                     break;
@@ -2137,12 +2115,13 @@
                 text.getText(),
                 t -> {
                     // Underlines are applied using a Spannable here, rather than setting paint bits
-                    // (or using Paint#setTextUnderline). When multiple fonts are mixed on the same
-                    // line
+                    // (or
+                    // using Paint#setTextUnderline). When multiple fonts are mixed on the same line
                     // (especially when mixing anything with NotoSans-CJK), multiple underlines can
-                    // appear.
-                    // Using UnderlineSpan instead though causes the correct behaviour to happen
-                    // (only a single underline).
+                    // appear. Using UnderlineSpan instead though causes the correct behaviour to
+                    // happen
+                    // (only a
+                    // single underline).
                     SpannableStringBuilder ssb = new SpannableStringBuilder();
                     ssb.append(t);
 
@@ -2357,7 +2336,7 @@
         // Both dimensions can't be ratios.
         if (image.getWidth().getInnerCase() == ImageDimension.InnerCase.PROPORTIONAL_DIMENSION
                 && image.getHeight().getInnerCase()
-                == ImageDimension.InnerCase.PROPORTIONAL_DIMENSION) {
+                        == ImageDimension.InnerCase.PROPORTIONAL_DIMENSION) {
             Log.w(TAG, "Both width and height were proportional for image " + protoResId);
             return null;
         }
@@ -2438,7 +2417,7 @@
 
                     if (trigger != null
                             && trigger.getInnerCase()
-                            == Trigger.InnerCase.ON_CONDITION_MET_TRIGGER) {
+                                    == Trigger.InnerCase.ON_CONDITION_MET_TRIGGER) {
                         OnConditionMetTrigger conditionTrigger = trigger.getOnConditionMetTrigger();
                         pipelineMaker
                                 .get()
@@ -2474,10 +2453,10 @@
             try {
                 if (mLayoutResourceResolvers.hasPlaceholderDrawable(protoResId)) {
                     if (setImageDrawable(
-                            imageView,
-                            mLayoutResourceResolvers.getPlaceholderDrawableOrThrow(
-                                    protoResId),
-                            protoResId)
+                                    imageView,
+                                    mLayoutResourceResolvers.getPlaceholderDrawableOrThrow(
+                                            protoResId),
+                                    protoResId)
                             == null) {
                         Log.w(TAG, "Failed to set the placeholder for " + protoResId);
                     }
@@ -2528,7 +2507,7 @@
      * Set drawable to the image view.
      *
      * @return Returns the drawable if it is successfully retrieved from the drawable future and set
-     * to the image view; otherwise returns null to indicate the failure of setting drawable.
+     *     to the image view; otherwise returns null to indicate the failure of setting drawable.
      */
     @Nullable
     private static Drawable setImageDrawable(
@@ -2545,14 +2524,14 @@
      * Set drawable to the image view.
      *
      * @return Returns the drawable if it is successfully set to the image view; otherwise returns
-     * null to indicate the failure of setting drawable.
+     *     null to indicate the failure of setting drawable.
      */
     @Nullable
     private static Drawable setImageDrawable(
             ImageView imageView, Drawable drawable, String protoResId) {
         if (drawable instanceof BitmapDrawable
                 && ((BitmapDrawable) drawable).getBitmap().getByteCount()
-                > DEFAULT_MAX_BITMAP_RAW_SIZE) {
+                        > DEFAULT_MAX_BITMAP_RAW_SIZE) {
             Log.w(TAG, "Ignoring image " + protoResId + " as it's too large.");
             return null;
         }
@@ -2622,16 +2601,17 @@
                     handleProp(length, lineView::setLineSweepAngleDegrees, posId, pipelineMaker);
                     break;
 
-                case EXPANDED_ANGULAR_DIMENSION: {
-                    ExpandedAngularDimensionProp expandedAngularDimension =
-                            angularLength.getExpandedAngularDimension();
-                    layoutParams.setWeight(
-                            expandedAngularDimension.hasLayoutWeight()
-                                    ? expandedAngularDimension.getLayoutWeight().getValue()
-                                    : 1.0f);
-                    length = DegreesProp.getDefaultInstance();
-                    break;
-                }
+                case EXPANDED_ANGULAR_DIMENSION:
+                    {
+                        ExpandedAngularDimensionProp expandedAngularDimension =
+                                angularLength.getExpandedAngularDimension();
+                        layoutParams.setWeight(
+                                expandedAngularDimension.hasLayoutWeight()
+                                        ? expandedAngularDimension.getLayoutWeight().getValue()
+                                        : 1.0f);
+                        length = DegreesProp.getDefaultInstance();
+                        break;
+                    }
 
                 default:
                     length = DegreesProp.getDefaultInstance();
@@ -3412,7 +3392,7 @@
                 return true;
             case INNER_NOT_SET:
                 return false;
-            default: // TODO(b/178359365): Remove default case
+            default: // TODO(b/276703002): Remove default case
                 return false;
         }
     }
@@ -3447,7 +3427,7 @@
                 return true;
             case INNER_NOT_SET:
                 return false;
-            default: // TODO(b/178359365): Remove default case
+            default: // TODO(b/276703002): Remove default case
                 return false;
         }
     }
@@ -3504,11 +3484,10 @@
      *
      * @param parent The view to attach the layout into.
      * @return The {@link InflateResult} class containing the first child that was inflated,
-     * animations to be played, and new nodes for the dynamic data pipeline. Callers should use
-     * {@link InflateResult#startAnimations} and {@link InflateResult#updateDynamicDataPipeline}
-     * to apply those changes using a UI Thread.
-     * <p>This may be null if the proto is empty the top-level LayoutElement has no inner set,
-     * or the top-level LayoutElement contains an unsupported inner type.
+     *     animations to be played, and new nodes for the dynamic data pipeline. Callers should use
+     *     {@link InflateResult#updateDynamicDataPipeline} to apply those changes using a UI Thread.
+     *     <p>This may be null if the proto is empty the top-level LayoutElement has no inner set,
+     *     or the top-level LayoutElement contains an unsupported inner type.
      */
     @Nullable
     public InflateResult inflate(@NonNull ViewGroup parent) {
@@ -3557,10 +3536,9 @@
      * <p>Can be called from a background thread.
      *
      * @param prevRenderedMetadata The metadata for the previous rendering of this view, either
-     *                             using {@code inflate} or {@code applyMutation}. This can be
-     *                             retrieved by calling {@link
-     *                             #getRenderedMetadata} on the previous layout view parent.
-     * @param targetLayout         The target layout that the mutation should result in.
+     *     using {@code inflate} or {@code applyMutation}. This can be retrieved by calling {@link
+     *     #getRenderedMetadata} on the previous layout view parent.
+     * @param targetLayout The target layout that the mutation should result in.
      * @return The mutation that will produce the target layout.
      */
     @Nullable
@@ -3672,19 +3650,19 @@
         RenderedMetadata prevRenderedMetadata = getRenderedMetadata(parent);
         if (prevRenderedMetadata != null
                 && !ProtoLayoutDiffer.areNodesEquivalent(
-                prevRenderedMetadata.getTreeFingerprint().getRoot(),
-                groupMutation.mPreMutationRootNodeFingerprint)) {
+                        prevRenderedMetadata.getTreeFingerprint().getRoot(),
+                        groupMutation.mPreMutationRootNodeFingerprint)) {
 
             // be considered unequal. Log.e(TAG, "View has changed. Skipping mutation."); return
             // false;
         }
         if (groupMutation.isNoOp()) {
             // Nothing to do.
-            return Futures.immediateVoidFuture();
+            return immediateVoidFuture();
         }
 
         if (groupMutation.mPipelineMaker.isPresent()) {
-            ResolvableFuture<Void> result = ResolvableFuture.create();
+            SettableFuture<Void> result = SettableFuture.create();
             groupMutation
                     .mPipelineMaker
                     .get()
@@ -3703,9 +3681,9 @@
         } else {
             try {
                 applyMutationInternal(parent, groupMutation);
-                return Futures.immediateVoidFuture();
+                return immediateVoidFuture();
             } catch (ViewMutationException ex) {
-                return Futures.immediateFailedFuture(ex);
+                return immediateFailedFuture(ex);
             }
         }
     }
@@ -3763,7 +3741,9 @@
         if (prevMetadataObject instanceof RenderedMetadata) {
             return (RenderedMetadata) prevMetadataObject;
         } else {
-            Log.w(TAG, "Incompatible prevMetadataObject");
+            if (prevMetadataObject != null) {
+                Log.w(TAG, "Incompatible prevMetadataObject");
+            }
             return null;
         }
     }
@@ -3900,10 +3880,11 @@
                         break;
                     }
                     mLoadActionExecutor.execute(
-                            () ->
-                                    mLoadActionListener.onClick(
-                                            buildState(
-                                                    action.getLoadAction(), mClickable.getId())));
+                                    () ->
+                                        mLoadActionListener.onClick(
+                                                buildState(
+                                                        action.getLoadAction(),
+                                                        mClickable.getId())));
                     break;
                 case VALUE_NOT_SET:
                     break;
@@ -3984,11 +3965,9 @@
         }
 
         @Override
-        public void startScroll(int startX, int startY, int dx, int dy) {
-        }
+        public void startScroll(int startX, int startY, int dx, int dy) {}
 
         @Override
-        public void startScroll(int startX, int startY, int dx, int dy, int duration) {
-        }
+        public void startScroll(int startX, int startY, int dx, int dy, int duration) {}
     }
 }
diff --git a/wear/protolayout/protolayout-renderer/src/test/java/androidx/wear/protolayout/renderer/impl/ProtoLayoutViewInstanceTest.java b/wear/protolayout/protolayout-renderer/src/test/java/androidx/wear/protolayout/renderer/impl/ProtoLayoutViewInstanceTest.java
index 93d21d6..ca3f94e 100644
--- a/wear/protolayout/protolayout-renderer/src/test/java/androidx/wear/protolayout/renderer/impl/ProtoLayoutViewInstanceTest.java
+++ b/wear/protolayout/protolayout-renderer/src/test/java/androidx/wear/protolayout/renderer/impl/ProtoLayoutViewInstanceTest.java
@@ -79,55 +79,129 @@
     }
 
     @Test
-    public void adaptiveUpdateRatesDisabled_attach_reinflatesCompletely() {
+    public void adaptiveUpdateRatesDisabled_attach_reinflatesCompletely() throws Exception {
         setupInstance(/* adaptiveUpdateRatesEnabled= */ false);
-        mInstanceUnderTest.renderAndAttach(
-                layout(column(text(TEXT1), text(TEXT2))), RESOURCES, mRootContainer);
+        ListenableFuture<Void> result =
+                mInstanceUnderTest.renderAndAttach(
+                        layout(column(text(TEXT1), text(TEXT2))), RESOURCES, mRootContainer);
         shadowOf(Looper.getMainLooper()).idle();
+
+        assertNoException(result);
+
         List<View> layout1 = findViewsWithText(mRootContainer, TEXT1);
         assertThat(layout1).hasSize(1);
 
-        mInstanceUnderTest.renderAndAttach(
-                layout(column(text(TEXT1), text(TEXT3))), RESOURCES, mRootContainer);
+        result =
+                mInstanceUnderTest.renderAndAttach(
+                        layout(column(text(TEXT1), text(TEXT3))), RESOURCES, mRootContainer);
         shadowOf(Looper.getMainLooper()).idle();
 
+        assertNoException(result);
         assertThat(findViewsWithText(mRootContainer, TEXT1)).containsNoneIn(layout1);
         assertThat(findViewsWithText(mRootContainer, TEXT3)).isNotEmpty();
     }
 
     @Test
-    public void adaptiveUpdateRatesEnabled_attach_appliesDiffOnly() {
+    public void adaptiveUpdateRatesEnabled_attach_appliesDiffOnly() throws Exception {
         setupInstance(/* adaptiveUpdateRatesEnabled= */ true);
-        mInstanceUnderTest.renderAndAttach(
-                layout(column(text(TEXT1), text(TEXT2))), RESOURCES, mRootContainer);
+        ListenableFuture<Void> result =
+                mInstanceUnderTest.renderAndAttach(
+                        layout(column(text(TEXT1), text(TEXT2))), RESOURCES, mRootContainer);
         shadowOf(Looper.getMainLooper()).idle();
+
+        assertNoException(result);
+
         List<View> layout1 = findViewsWithText(mRootContainer, TEXT1);
         assertThat(layout1).hasSize(1);
 
-        mInstanceUnderTest.renderAndAttach(
-                layout(column(text(TEXT1), text(TEXT3))), RESOURCES, mRootContainer);
+        result =
+                mInstanceUnderTest.renderAndAttach(
+                        layout(column(text(TEXT1), text(TEXT3))), RESOURCES, mRootContainer);
         shadowOf(Looper.getMainLooper()).idle();
 
+        assertNoException(result);
         // Assert that only the modified text is reinflated.
         assertThat(findViewsWithText(mRootContainer, TEXT1)).containsExactlyElementsIn(layout1);
         assertThat(findViewsWithText(mRootContainer, TEXT3)).isNotEmpty();
     }
 
     @Test
-    public void adaptiveUpdateRatesEnabled_attach_withDynamicValue_appliesDiffOnly() {
+    public void reattach_usesCachedLayoutForDiffUpdate() throws Exception {
+        setupInstance(/* adaptiveUpdateRatesEnabled= */ true);
+        ListenableFuture<Void> result =
+                mInstanceUnderTest.renderAndAttach(
+                        layout(column(text(TEXT1), text(TEXT2))), RESOURCES, mRootContainer);
+        shadowOf(Looper.getMainLooper()).idle();
+
+        assertNoException(result);
+        List<View> layout1 = findViewsWithText(mRootContainer, TEXT1);
+        assertThat(layout1).hasSize(1);
+
+        mInstanceUnderTest.detach(mRootContainer);
+
+        result =
+                mInstanceUnderTest.renderAndAttach(
+                        layout(column(text(TEXT1), text(TEXT3))), RESOURCES, mRootContainer);
+        shadowOf(Looper.getMainLooper()).idle();
+
+        assertNoException(result);
+        // Assert that only the modified text is reinflated.
+        assertThat(findViewsWithText(mRootContainer, TEXT1)).containsExactlyElementsIn(layout1);
+        assertThat(findViewsWithText(mRootContainer, TEXT3)).isNotEmpty();
+    }
+
+    @Test
+    public void adaptiveUpdateRatesEnabled_applyingDiffToDetachedContainer_returnsNothing()
+            throws Exception {
+        setupInstance(/* adaptiveUpdateRatesEnabled= */ true);
+
+        // First one that does the full layout update.
+        ListenableFuture<Void> result =
+                mInstanceUnderTest.renderAndAttach(
+                        layout(column(text(TEXT1), text(TEXT2))), RESOURCES, mRootContainer);
+        shadowOf(Looper.getMainLooper()).idle();
+
+        assertNoException(result);
+
+        List<View> layout1 = findViewsWithText(mRootContainer, TEXT1);
+        assertThat(layout1).hasSize(1);
+
+        // Second one that applies mutation only.
+        result =
+                mInstanceUnderTest.renderAndAttach(
+                        layout(column(text(TEXT1), text(TEXT3))), RESOURCES, mRootContainer);
+        // Detach so it can't apply update.
+        mInstanceUnderTest.detach(mRootContainer);
+        shadowOf(Looper.getMainLooper()).idle();
+
+        assertThat(result.isCancelled()).isTrue();
+        assertThat(mRootContainer.getChildCount()).isEqualTo(0);
+    }
+
+    @Test
+    public void adaptiveUpdateRatesEnabled_attach_withDynamicValue_appliesDiffOnly()
+            throws Exception {
         setupInstance(/* adaptiveUpdateRatesEnabled= */ true);
 
         // Render the first layout.
         Layout layout1 = layout(column(dynamicFixedText(TEXT1), dynamicFixedText(TEXT2)));
-        mInstanceUnderTest.renderAndAttach(layout1, RESOURCES, mRootContainer);
+        ListenableFuture<Void> result =
+                mInstanceUnderTest.renderAndAttach(layout1, RESOURCES, mRootContainer);
         shadowOf(Looper.getMainLooper()).idle();
+
+        assertNoException(result);
+
         List<View> textView1 = findViewsWithText(mRootContainer, TEXT1);
         assertThat(textView1).hasSize(1);
         assertThat(findViewsWithText(mRootContainer, TEXT2)).hasSize(1);
 
         Layout layout2 = layout(column(dynamicFixedText(TEXT1), dynamicFixedText(TEXT3)));
-        mInstanceUnderTest.renderAndAttach(layout2, RESOURCES, mRootContainer);
+        result = mInstanceUnderTest.renderAndAttach(layout2, RESOURCES, mRootContainer);
+        // Make sure future is computing result.
+        assertThat(result.isDone()).isFalse();
         shadowOf(Looper.getMainLooper()).idle();
+
+        assertNoException(result);
         // Assert that only the modified text is reinflated.
         assertThat(findViewsWithText(mRootContainer, TEXT1)).containsExactlyElementsIn(textView1);
         assertThat(findViewsWithText(mRootContainer, TEXT2)).isEmpty();
@@ -135,28 +209,34 @@
     }
 
     @Test
-    public void adaptiveUpdateRatesEnabled_ongoingRendering_skipsNewLayout() {
+    public void adaptiveUpdateRatesEnabled_ongoingRendering_skipsNewLayout() throws Exception {
         FrameLayout container = new FrameLayout(mApplicationContext);
         setupInstance(/* adaptiveUpdateRatesEnabled= */ true);
-        mInstanceUnderTest.renderAndAttach(
-                layout(column(text(TEXT1), text(TEXT2))), RESOURCES, container);
+        ListenableFuture<Void> result1 =
+                mInstanceUnderTest.renderAndAttach(
+                        layout(column(text(TEXT1), text(TEXT2))), RESOURCES, container);
+        assertThat(result1.isDone()).isFalse();
 
-        mInstanceUnderTest.renderAndAttach(
-                layout(column(text(TEXT1), text(TEXT3))), RESOURCES, container);
+        ListenableFuture<Void> result2 =
+                mInstanceUnderTest.renderAndAttach(
+                        layout(column(text(TEXT1), text(TEXT3))), RESOURCES, container);
         shadowOf(Looper.getMainLooper()).idle();
 
+        assertNoException(result1);
+        assertThat(result2.isCancelled()).isTrue();
         // Assert that only the modified text is reinflated.
         assertThat(findViewsWithText(container, TEXT2)).hasSize(1);
         assertThat(findViewsWithText(container, TEXT3)).isEmpty();
     }
 
     @Test
-    public void attachingToANewContainer_withoutDetach_throws() {
+    public void attachingToANewContainer_withoutDetach_throws() throws Exception {
         FrameLayout container1 = new FrameLayout(mApplicationContext);
         FrameLayout container2 = new FrameLayout(mApplicationContext);
         setupInstance(/* adaptiveUpdateRatesEnabled= */ true);
-        mInstanceUnderTest.renderAndAttach(
-                layout(column(text(TEXT1), text(TEXT2))), RESOURCES, container1);
+        ListenableFuture<Void> result =
+                mInstanceUnderTest.renderAndAttach(
+                        layout(column(text(TEXT1), text(TEXT2))), RESOURCES, container1);
 
         assertThrows(
                 IllegalStateException.class,
@@ -164,64 +244,96 @@
                         mInstanceUnderTest.renderAndAttach(
                                 layout(column(text(TEXT1), text(TEXT2))), RESOURCES, container2));
         shadowOf(Looper.getMainLooper()).idle();
+
+        // Check the result from first attach.
+        assertNoException(result);
     }
 
     @Test
-    public void renderingToADetachedContainer_isNoOp() {
+    public void renderingToADetachedContainer_isNoOp() throws Exception {
         FrameLayout container1 = new FrameLayout(mApplicationContext);
         FrameLayout container2 = new FrameLayout(mApplicationContext);
         setupInstance(/* adaptiveUpdateRatesEnabled= */ true);
-        mInstanceUnderTest.renderAndAttach(layout(text(TEXT1)), RESOURCES, container1);
+        ListenableFuture<Void> result1 =
+                mInstanceUnderTest.renderAndAttach(layout(text(TEXT1)), RESOURCES, container1);
         mInstanceUnderTest.detach(container1);
 
-        mInstanceUnderTest.renderAndAttach(layout(text(TEXT1)), RESOURCES, container2);
+        ListenableFuture<Void> result2 =
+                mInstanceUnderTest.renderAndAttach(layout(text(TEXT1)), RESOURCES, container2);
         shadowOf(Looper.getMainLooper()).idle();
 
+        assertThat(result1.isCancelled()).isTrue();
+        assertThat(result2.isDone()).isTrue();
+        assertNoException(result2);
         assertThat(findViewsWithText(container1, TEXT1)).isEmpty();
         assertThat(findViewsWithText(container2, TEXT1)).hasSize(1);
     }
 
     @Test
-    public void adaptiveUpdateRatesDisabled_sameLayoutReference_subsequentRendering_isNoOp() {
+    public void adaptiveUpdateRatesDisabled_sameLayoutReference_subsequentRendering_isNoOp()
+            throws Exception {
         Layout layout = layout(text(TEXT1));
         setupInstance(/* adaptiveUpdateRatesEnabled= */ false);
-        mInstanceUnderTest.renderAndAttach(layout, RESOURCES, mRootContainer);
+        ListenableFuture<Void> result =
+                mInstanceUnderTest.renderAndAttach(layout, RESOURCES, mRootContainer);
         shadowOf(Looper.getMainLooper()).idle();
 
-        mInstanceUnderTest.renderAndAttach(layout, RESOURCES, mRootContainer);
+        assertNoException(result);
 
+        result = mInstanceUnderTest.renderAndAttach(layout, RESOURCES, mRootContainer);
+
+        shadowOf(Looper.getMainLooper()).idle();
+        assertNoException(result);
         assertThat(shadowOf(Looper.getMainLooper()).isIdle()).isTrue();
     }
 
     @Test
-    public void adaptiveUpdateRatesEnabled_afterNoChange_reattach_sameLayoutReference_rerenders() {
+    public void adaptiveUpdateRatesEnabled_afterNoChange_reattach_sameLayoutReference_isNoOp()
+            throws Exception {
         Layout layout1 = layout(text(TEXT1));
         Layout layout2 = layout(text(TEXT1));
         setupInstance(/* adaptiveUpdateRatesEnabled= */ true);
-        mInstanceUnderTest.renderAndAttach(layout1, RESOURCES, mRootContainer);
+        ListenableFuture<Void> result =
+                mInstanceUnderTest.renderAndAttach(layout1, RESOURCES, mRootContainer);
         shadowOf(Looper.getMainLooper()).idle();
+
+        assertNoException(result);
+
         // Make sure we have an UnchangedRenderResult
-        mInstanceUnderTest.renderAndAttach(layout2, RESOURCES, mRootContainer);
+        result = mInstanceUnderTest.renderAndAttach(layout2, RESOURCES, mRootContainer);
         shadowOf(Looper.getMainLooper()).idle();
+
+        assertNoException(result);
+        assertThat(findViewsWithText(mRootContainer, TEXT1)).hasSize(1);
+
         mInstanceUnderTest.detach(mRootContainer);
+        assertThat(findViewsWithText(mRootContainer, TEXT1)).isEmpty();
         shadowOf(Looper.getMainLooper()).idle();
 
-        mInstanceUnderTest.renderAndAttach(layout2, RESOURCES, mRootContainer);
+        result = mInstanceUnderTest.renderAndAttach(layout2, RESOURCES, mRootContainer);
 
-        assertThat(shadowOf(Looper.getMainLooper()).isIdle()).isFalse();
+        assertThat(result.isDone()).isTrue();
+        assertNoException(result);
+        assertThat(findViewsWithText(mRootContainer, TEXT1)).hasSize(1);
     }
 
     @Test
-    public void layoutViewIsCachedWhenDetached() {
+    public void fullInflationResultCanBeReused() throws Exception {
         setupInstance(/* adaptiveUpdateRatesEnabled= */ false);
         Layout layout = layout(text(TEXT1));
-        mInstanceUnderTest.renderAndAttach(layout, RESOURCES, mRootContainer);
+        ListenableFuture<Void> result =
+                mInstanceUnderTest.renderAndAttach(layout, RESOURCES, mRootContainer);
         shadowOf(Looper.getMainLooper()).idle();
+
+        assertNoException(result);
+
         ListenableFuture<?> renderFuture = mInstanceUnderTest.mRenderFuture;
 
         mInstanceUnderTest.detach(mRootContainer);
-        mInstanceUnderTest.renderAndAttach(layout, RESOURCES, mRootContainer);
+        result = mInstanceUnderTest.renderAndAttach(layout, RESOURCES, mRootContainer);
 
+        shadowOf(Looper.getMainLooper()).idle();
+        assertNoException(result);
         assertThat(mInstanceUnderTest.mRenderFuture).isSameInstanceAs(renderFuture);
     }
 
@@ -230,14 +342,52 @@
             throws Exception {
         Layout layout = layout(text(TEXT1));
         setupInstance(/* adaptiveUpdateRatesEnabled= */ false);
-        mInstanceUnderTest.renderAndAttach(layout, RESOURCES, mRootContainer);
+        ListenableFuture<Void> result =
+                mInstanceUnderTest.renderAndAttach(layout, RESOURCES, mRootContainer);
         shadowOf(Looper.getMainLooper()).idle();
+        assertNoException(result);
+        List<View> textViews1 = findViewsWithText(mRootContainer, TEXT1);
+        assertThat(textViews1).hasSize(1);
+
+        mInstanceUnderTest.close();
+        result = mInstanceUnderTest.renderAndAttach(layout, RESOURCES, mRootContainer);
+
+        assertThat(shadowOf(Looper.getMainLooper()).isIdle()).isFalse();
+        shadowOf(Looper.getMainLooper()).idle();
+        assertNoException(result);
+        List<View> textViews2 = findViewsWithText(mRootContainer, TEXT1);
+        assertThat(textViews2).hasSize(1);
+        assertThat(textViews1.get(0)).isNotSameInstanceAs(textViews2.get(0));
+    }
+
+    @Test
+    public void detach_clearsHostView() throws Exception {
+        Layout layout = layout(text(TEXT1));
+        setupInstance(/* adaptiveUpdateRatesEnabled= */ true);
+        ListenableFuture<Void> result =
+                mInstanceUnderTest.renderAndAttach(layout, RESOURCES, mRootContainer);
+        shadowOf(Looper.getMainLooper()).idle();
+        assertNoException(result);
+        assertThat(findViewsWithText(mRootContainer, TEXT1)).hasSize(1);
+
+        mInstanceUnderTest.detach(mRootContainer);
+
+        assertThat(mRootContainer.getChildCount()).isEqualTo(0);
+    }
+
+    @Test
+    public void close_clearsHostView() throws Exception {
+        Layout layout = layout(text(TEXT1));
+        setupInstance(/* adaptiveUpdateRatesEnabled= */ true);
+        ListenableFuture<Void> result =
+                mInstanceUnderTest.renderAndAttach(layout, RESOURCES, mRootContainer);
+        shadowOf(Looper.getMainLooper()).idle();
+        assertNoException(result);
+        assertThat(findViewsWithText(mRootContainer, TEXT1)).hasSize(1);
 
         mInstanceUnderTest.close();
 
-        mInstanceUnderTest.renderAndAttach(layout, RESOURCES, mRootContainer);
-
-        assertThat(shadowOf(Looper.getMainLooper()).isIdle()).isFalse();
+        assertThat(mRootContainer.getChildCount()).isEqualTo(0);
     }
 
     private void setupInstance(boolean adaptiveUpdateRatesEnabled) {
@@ -248,13 +398,12 @@
 
         ProtoLayoutViewInstance.Config config =
                 new Config.Builder(
-                        mApplicationContext,
-                        listeningExecutorService,
-                        listeningExecutorService,
-                        /* clickableIdExtra= */ "CLICKABLE_ID_EXTRA")
+                                mApplicationContext,
+                                listeningExecutorService,
+                                listeningExecutorService,
+                                /* clickableIdExtra= */ "CLICKABLE_ID_EXTRA")
                         .setStateStore(new StateStore(ImmutableMap.of()))
-                        .setLoadActionListener(nextState -> {
-                        })
+                        .setLoadActionListener(nextState -> {})
                         .setAnimationEnabled(true)
                         .setRunningAnimationsLimit(Integer.MAX_VALUE)
                         .setUpdatesEnabled(true)
@@ -270,6 +419,11 @@
         return views;
     }
 
+    private static void assertNoException(ListenableFuture<Void> result) throws Exception {
+        // Assert that result hasn't thrown exception.
+        result.get();
+    }
+
     static class FakeExecutorService extends AbstractExecutorService {
 
         private final Handler mHandler;
@@ -279,8 +433,7 @@
         }
 
         @Override
-        public void shutdown() {
-        }
+        public void shutdown() {}
 
         @Override
         public List<Runnable> shutdownNow() {
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 5a20188..64ea708 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
@@ -21,6 +21,7 @@
 import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
 import static androidx.wear.protolayout.proto.ModifiersProto.SlideParentSnapOption.SLIDE_PARENT_SNAP_TO_INSIDE;
 import static androidx.wear.protolayout.proto.ModifiersProto.SlideParentSnapOption.SLIDE_PARENT_SNAP_TO_OUTSIDE;
+import static androidx.wear.protolayout.renderer.R.id.clickable_id_tag;
 import static androidx.wear.protolayout.renderer.helper.TestDsl.arc;
 import static androidx.wear.protolayout.renderer.helper.TestDsl.arcText;
 import static androidx.wear.protolayout.renderer.helper.TestDsl.box;
@@ -215,8 +216,7 @@
     private static final int SCREEN_WIDTH = 400;
     private static final int SCREEN_HEIGHT = 400;
 
-    @Rule
-    public final Expect expect = Expect.create();
+    @Rule public final Expect expect = Expect.create();
 
     private final StateStore mStateStore = new StateStore(ImmutableMap.of());
     private ProtoLayoutDynamicDataPipeline mDataPipeline;
@@ -279,8 +279,8 @@
     public void inflate_textView_withObsoleteSemanticsContentDescription() {
         String textContents = "Hello World";
         String textDescription = "Hello World Text Element";
-        Semantics.Builder semantics =
-                Semantics.newBuilder().setObsoleteContentDescription(textDescription);
+        Semantics semantics =
+                Semantics.newBuilder().setObsoleteContentDescription(textDescription).build();
         LayoutElement root =
                 LayoutElement.newBuilder()
                         .setText(
@@ -318,10 +318,11 @@
         String staticDescription = "StaticDescription";
 
         StringProp descriptionProp = string(staticDescription).build();
-        Semantics.Builder semantics =
+        Semantics semantics =
                 Semantics.newBuilder()
                         .setObsoleteContentDescription("ObsoleteContentDescription")
-                        .setContentDescription(descriptionProp);
+                        .setContentDescription(descriptionProp)
+                        .build();
         LayoutElement root =
                 LayoutElement.newBuilder()
                         .setText(
@@ -381,10 +382,11 @@
                                         .build())
                         .build();
 
-        Semantics.Builder semantics =
+        Semantics semantics =
                 Semantics.newBuilder()
                         .setStateDescription(stateDescriptionProp)
-                        .setContentDescription(contentDescriptionProp);
+                        .setContentDescription(contentDescriptionProp)
+                        .build();
         LayoutElement root =
                 LayoutElement.newBuilder()
                         .setText(
@@ -453,8 +455,10 @@
 
     @Test
     public void inflate_box_withIllegalSize() {
-        LayoutElement.Builder textElement =
-                LayoutElement.newBuilder().setText(Text.newBuilder().setText(string("foo")));
+        LayoutElement textElement =
+                LayoutElement.newBuilder()
+                        .setText(Text.newBuilder().setText(string("foo")))
+                        .build();
         LayoutElement root =
                 LayoutElement.newBuilder()
                         .setBox(
@@ -466,10 +470,10 @@
                                                         .setBox(
                                                                 // Inner box's width set to
                                                                 // "expand". Having a single
-                                                                // "expand" element in a "wrap"
-                                                                // element is an undefined state, so
-                                                                // the outer box should not be
-                                                                // displayed.
+                                                                // "expand"
+                                                                // element in a "wrap" element is an
+                                                                // undefined state, so the outer box
+                                                                // should not be displayed.
                                                                 Box.newBuilder()
                                                                         .setWidth(expand())
                                                                         .addContents(textElement))))
@@ -484,10 +488,11 @@
     @Test
     public void inflate_box_withSemanticsModifier() {
         String textDescription = "this is a button";
-        Semantics.Builder semantics =
+        Semantics semantics =
                 Semantics.newBuilder()
                         .setContentDescription(string(textDescription))
-                        .setRole(SemanticsRole.SEMANTICS_ROLE_BUTTON);
+                        .setRole(SemanticsRole.SEMANTICS_ROLE_BUTTON)
+                        .build();
         String text = "some button";
         LayoutElement root =
                 LayoutElement.newBuilder()
@@ -518,11 +523,12 @@
     public void inflate_box_withSemanticsStateDescription() {
         String textDescription = "this is a switch";
         String offState = "off";
-        Semantics.Builder semantics =
+        Semantics semantics =
                 Semantics.newBuilder()
                         .setContentDescription(string(textDescription))
                         .setStateDescription(string(offState))
-                        .setRole(SemanticsRole.SEMANTICS_ROLE_SWITCH);
+                        .setRole(SemanticsRole.SEMANTICS_ROLE_SWITCH)
+                        .build();
         String text = "a switch";
         LayoutElement root =
                 LayoutElement.newBuilder()
@@ -759,7 +765,7 @@
         TextView tv = (TextView) rootLayout.getChildAt(0);
 
         // The clickable view must have the same tag as the corresponding layout clickable.
-        expect.that(tv.getTag(R.id.clickable_id_tag)).isEqualTo("foo");
+        expect.that(tv.getTag(clickable_id_tag)).isEqualTo("foo");
 
         // Ensure that the text still went through properly.
         expect.that(tv.getText().toString()).isEqualTo(textContents);
@@ -908,9 +914,9 @@
         State.Builder receivedState = State.newBuilder();
         FrameLayout rootLayout =
                 renderer(
-                        newRendererConfigBuilder(
-                                fingerprintedLayout(root), resourceResolvers())
-                                .setLoadActionListener(receivedState::mergeFrom))
+                                newRendererConfigBuilder(
+                                                fingerprintedLayout(root), resourceResolvers())
+                                        .setLoadActionListener(receivedState::mergeFrom))
                         .inflate();
 
         // Should be just a text view as the root.
@@ -920,7 +926,7 @@
         TextView tv = (TextView) rootLayout.getChildAt(0);
 
         // The clickable view must have the same tag as the corresponding layout clickable.
-        expect.that(tv.getTag(R.id.clickable_id_tag)).isEqualTo("foo");
+        expect.that(tv.getTag(clickable_id_tag)).isEqualTo("foo");
 
         // Ensure that the text still went through properly.
         expect.that(tv.getText().toString()).isEqualTo(textContents);
@@ -1092,11 +1098,9 @@
                                 Arc.newBuilder()
                                         .setAnchorAngle(degrees(0).build())
                                         .addContents(
-                                                ArcLayoutElement.newBuilder()
-                                                        .setSpacer(
-                                                                ArcSpacer.newBuilder()
-                                                                        .setLength(degrees(90))
-                                                                        .setThickness(dp(20)))))
+                                                arcLayoutElement(
+                                                        ArcSpacer.newBuilder()
+                                                                .setLength(degrees(90)))))
                         .build();
 
         FrameLayout rootLayout = renderer(fingerprintedLayout(root)).inflate();
@@ -1125,12 +1129,9 @@
                                         .setAnchorAngle(degrees(0).build())
                                         .setMaxAngle(DegreesProp.newBuilder().setValue(90f).build())
                                         .addContents(
-                                                ArcLayoutElement.newBuilder()
-                                                        .setSpacer(
-                                                                ArcSpacer.newBuilder()
-                                                                        .setAngularLength(
-                                                                                spacerLength)
-                                                                        .setThickness(dp(20))))
+                                                arcLayoutElement(
+                                                        ArcSpacer.newBuilder()
+                                                                .setAngularLength(spacerLength)))
                                         .addContents(
                                                 ArcLayoutElement.newBuilder()
                                                         .setLine(
@@ -1155,6 +1156,11 @@
         assertThat(line.getSweepAngleDegrees()).isEqualTo(60f);
     }
 
+    @NonNull
+    private static ArcLayoutElement.Builder arcLayoutElement(ArcSpacer.Builder setAngularLength) {
+        return ArcLayoutElement.newBuilder().setSpacer(setAngularLength.setThickness(dp(20)));
+    }
+
     @Test
     public void inflate_row() {
         final String protoResId = "android";
@@ -1720,7 +1726,7 @@
 
     @Test
     public void inflate_spannable_onClickCanFire() {
-        StringProp.Builder text = string("Hello" + " World");
+        StringProp.Builder text = string("Hello World");
         LayoutElement root =
                 LayoutElement.newBuilder()
                         .setSpannable(
@@ -1737,11 +1743,11 @@
         List<Boolean> hasFiredList = new ArrayList<>();
         FrameLayout rootLayout =
                 renderer(
-                        newRendererConfigBuilder(
-                                fingerprintedLayout(root), resourceResolvers())
-                                .setLoadActionListener(p -> hasFiredList.add(true))
-                                .setProtoLayoutTheme(
-                                        loadTheme(R.style.MyProtoLayoutSansSerifTheme)))
+                                newRendererConfigBuilder(
+                                                fingerprintedLayout(root), resourceResolvers())
+                                        .setLoadActionListener(p -> hasFiredList.add(true))
+                                        .setProtoLayoutTheme(
+                                                loadTheme(R.style.MyProtoLayoutSansSerifTheme)))
                         .inflate();
 
         TextView tv = (TextView) rootLayout.getChildAt(0);
@@ -1951,9 +1957,11 @@
                                                         .setImage(
                                                                 Image.newBuilder()
                                                                         .setWidth(
-                                                                                linImageDim(dp(24)))
+                                                                                linImageDim(
+                                                                                        dp(24f)))
                                                                         .setHeight(
-                                                                                linImageDim(dp(24)))
+                                                                                linImageDim(
+                                                                                        dp(24f)))
                                                                         .setResourceId(
                                                                                 string("android"))))
                                         .addContents(LayoutElement.newBuilder().setImage(image)))
@@ -1961,9 +1969,9 @@
 
         FrameLayout rootLayout =
                 renderer(
-                        newRendererConfigBuilder(fingerprintedLayout(root))
-                                .setProtoLayoutTheme(
-                                        loadTheme(R.style.MyProtoLayoutSansSerifTheme)))
+                                newRendererConfigBuilder(fingerprintedLayout(root))
+                                        .setProtoLayoutTheme(
+                                                loadTheme(R.style.MyProtoLayoutSansSerifTheme)))
                         .inflate();
 
         // Outer box should be 24dp
@@ -1991,76 +1999,6 @@
         expect.that(image2.getHeight()).isEqualTo(24);
     }
 
-    @Test
-    public void inflate_image_undefinedSizeIgnoresIntrinsicSize() {
-        // This can happen in the case that a layout is ever inflated into a Scrolling layout. In
-        // that case, the scrolling layout will measure all children with height = UNDEFINED, which
-        // can lead to an Image still using its intrinsic size.
-        String resId = "large_image_120dp";
-        LayoutElement root =
-                LayoutElement.newBuilder()
-                        .setBox(
-                                Box.newBuilder()
-                                        .setWidth(wrap())
-                                        .setHeight(wrap())
-                                        .addContents(
-                                                LayoutElement.newBuilder()
-                                                        .setImage(
-                                                                Image.newBuilder()
-                                                                        .setWidth(
-                                                                                linImageDim(dp(24)))
-                                                                        .setHeight(
-                                                                                linImageDim(dp(24)))
-                                                                        .setResourceId(
-                                                                                string("android"))))
-                                        .addContents(
-                                                LayoutElement.newBuilder()
-                                                        .setImage(
-                                                                Image.newBuilder()
-                                                                        .setWidth(expandImage())
-                                                                        .setHeight(expandImage())
-                                                                        .setResourceId(
-                                                                                string(resId)))))
-                        .build();
-
-        FrameLayout rootLayout =
-                renderer(
-                        newRendererConfigBuilder(fingerprintedLayout(root))
-                                .setProtoLayoutTheme(
-                                        loadTheme(R.style.MyProtoLayoutSansSerifTheme)))
-                        .inflate();
-
-        // Re-measure the root layout with an UNDEFINED constraint...
-        int screenWidth = MeasureSpec.makeMeasureSpec(SCREEN_WIDTH, MeasureSpec.EXACTLY);
-        int screenHeight = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
-        rootLayout.measure(screenWidth, screenHeight);
-        rootLayout.layout(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);
-
-        // Outer box should be 24dp
-        FrameLayout firstBox = (FrameLayout) rootLayout.getChildAt(0);
-        expect.that(firstBox.getWidth()).isEqualTo(24);
-        expect.that(firstBox.getHeight()).isEqualTo(24);
-
-        // Both children (images) should have the same dimensions as the FrameLayout.
-        RatioViewWrapper rvw1 = (RatioViewWrapper) firstBox.getChildAt(0);
-        RatioViewWrapper rvw2 = (RatioViewWrapper) firstBox.getChildAt(1);
-
-        expect.that(rvw1.getWidth()).isEqualTo(24);
-        expect.that(rvw1.getHeight()).isEqualTo(24);
-
-        expect.that(rvw2.getWidth()).isEqualTo(24);
-        expect.that(rvw2.getHeight()).isEqualTo(24);
-
-        ImageViewWithoutIntrinsicSizes image1 = (ImageViewWithoutIntrinsicSizes) rvw1.getChildAt(0);
-        ImageViewWithoutIntrinsicSizes image2 = (ImageViewWithoutIntrinsicSizes) rvw2.getChildAt(0);
-
-        expect.that(image1.getWidth()).isEqualTo(24);
-        expect.that(image1.getHeight()).isEqualTo(24);
-
-        expect.that(image2.getWidth()).isEqualTo(24);
-        expect.that(image2.getHeight()).isEqualTo(24);
-    }
-
     @NonNull
     private static ImageDimension.Builder linImageDim(DpProp.Builder builderForValue) {
         return ImageDimension.newBuilder().setLinearDimension(builderForValue);
@@ -2073,6 +2011,78 @@
     }
 
     @Test
+    public void inflate_image_undefinedSizeIgnoresIntrinsicSize() {
+        // This can happen in the case that a layout is ever inflated into a Scrolling layout. In
+        // that case, the scrolling layout will measure all children with height = UNDEFINED, which
+        // can lead to an Image still using its intrinsic size.
+        String resId = "large_image_120dp";
+        LayoutElement root =
+                LayoutElement.newBuilder()
+                        .setBox(
+                                Box.newBuilder()
+                                        .setWidth(wrap())
+                                        .setHeight(wrap())
+                                        .addContents(
+                                                LayoutElement.newBuilder()
+                                                        .setImage(
+                                                                Image.newBuilder()
+                                                                        .setWidth(
+                                                                                linImageDim(
+                                                                                        dp(24f)))
+                                                                        .setHeight(
+                                                                                linImageDim(
+                                                                                        dp(24f)))
+                                                                        .setResourceId(
+                                                                                string("android"))))
+                                        .addContents(
+                                                LayoutElement.newBuilder()
+                                                        .setImage(
+                                                                Image.newBuilder()
+                                                                        .setWidth(expandImage())
+                                                                        .setHeight(expandImage())
+                                                                        .setResourceId(
+                                                                                string(resId)))))
+                        .build();
+
+        FrameLayout rootLayout =
+                renderer(
+                                newRendererConfigBuilder(fingerprintedLayout(root))
+                                        .setProtoLayoutTheme(
+                                                loadTheme(R.style.MyProtoLayoutSansSerifTheme)))
+                        .inflate();
+
+        // Re-measure the root layout with an UNDEFINED constraint...
+        int screenWidth = MeasureSpec.makeMeasureSpec(SCREEN_WIDTH, MeasureSpec.EXACTLY);
+        int screenHeight = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
+        rootLayout.measure(screenWidth, screenHeight);
+        rootLayout.layout(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);
+
+        // Outer box should be 24dp
+        FrameLayout firstBox = (FrameLayout) rootLayout.getChildAt(0);
+        expect.that(firstBox.getWidth()).isEqualTo(24);
+        expect.that(firstBox.getHeight()).isEqualTo(24);
+
+        // Both children (images) should have the same dimensions as the FrameLayout.
+        RatioViewWrapper rvw1 = (RatioViewWrapper) firstBox.getChildAt(0);
+        RatioViewWrapper rvw2 = (RatioViewWrapper) firstBox.getChildAt(1);
+
+        expect.that(rvw1.getWidth()).isEqualTo(24);
+        expect.that(rvw1.getHeight()).isEqualTo(24);
+
+        expect.that(rvw2.getWidth()).isEqualTo(24);
+        expect.that(rvw2.getHeight()).isEqualTo(24);
+
+        ImageViewWithoutIntrinsicSizes image1 = (ImageViewWithoutIntrinsicSizes) rvw1.getChildAt(0);
+        ImageViewWithoutIntrinsicSizes image2 = (ImageViewWithoutIntrinsicSizes) rvw2.getChildAt(0);
+
+        expect.that(image1.getWidth()).isEqualTo(24);
+        expect.that(image1.getHeight()).isEqualTo(24);
+
+        expect.that(image2.getWidth()).isEqualTo(24);
+        expect.that(image2.getHeight()).isEqualTo(24);
+    }
+
+    @Test
     public void inflate_arcLine_usesValueForLayout() {
         DynamicFloat arcLength =
                 DynamicFloat.newBuilder().setFixed(FixedFloat.newBuilder().setValue(45f)).build();
@@ -2203,6 +2213,19 @@
         assertThat(line.getMaxSweepAngleDegrees()).isEqualTo(0);
     }
 
+    @NonNull
+    private static DegreesProp.Builder degreesDynamic(DynamicFloat arcLength) {
+        return DegreesProp.newBuilder().setDynamicValue(arcLength);
+    }
+
+    @NonNull
+    private static DegreesProp.Builder degreesDynamic(
+            DynamicFloat arcLength, float valueForLayout) {
+        return DegreesProp.newBuilder()
+                .setValueForLayout(valueForLayout)
+                .setDynamicValue(arcLength);
+    }
+
     @Test
     public void inflate_arcLine_withoutValueForLayout_noLegacyMode_usesArcLength() {
         DynamicFloat arcLength =
@@ -2228,8 +2251,8 @@
 
         FrameLayout rootLayout =
                 renderer(
-                        newRendererConfigBuilder(fingerprintedLayout(root))
-                                .setAllowLayoutChangingBindsWithoutDefault(true))
+                                newRendererConfigBuilder(fingerprintedLayout(root))
+                                        .setAllowLayoutChangingBindsWithoutDefault(true))
                         .inflate();
 
         shadowOf(Looper.getMainLooper()).idle();
@@ -2240,19 +2263,6 @@
         assertThat(line.getLineSweepAngleDegrees()).isEqualTo(45f);
     }
 
-    @NonNull
-    private static DegreesProp.Builder degreesDynamic(DynamicFloat arcLength) {
-        return DegreesProp.newBuilder().setDynamicValue(arcLength);
-    }
-
-    @NonNull
-    private static DegreesProp.Builder degreesDynamic(
-            DynamicFloat arcLength, float valueForLayout) {
-        return DegreesProp.newBuilder()
-                .setValueForLayout(valueForLayout)
-                .setDynamicValue(arcLength);
-    }
-
     @Test
     public void inflate_text_dynamicColor_updatesColor() {
         mStateStore.setStateEntryValuesProto(
@@ -2341,10 +2351,10 @@
     public void inflateThenMutate_withChangeToText_causesUpdate() {
         Layout layout1 =
                 layout(
-                        column(// 1
+                        column( // 1
                                 text("Hello"), // 1.1
                                 text("World") // 1.2
-                        ));
+                                ));
 
         // Check that we have the initial layout correctly rendered
         Renderer renderer = renderer(layout1);
@@ -2359,10 +2369,10 @@
         // Produce a new layout with only one Text element changed.
         Layout layout2 =
                 layout(
-                        column(// 1
+                        column( // 1
                                 text("Hello"), // 1.1
                                 text("Mars") // 1.2
-                        ));
+                                ));
 
         // Compute the mutation
         ViewGroupMutation mutation =
@@ -2390,17 +2400,17 @@
     public void inflateThenMutate_withChangeToImageAndText_causesUpdate() {
         Layout layout1 =
                 layout(
-                        column(// 1
+                        column( // 1
                                 text("Hello"), // 1.1
-                                row(// 1.2
-                                        image(// 1.2.1
+                                row( // 1.2
+                                        image( // 1.2.1
                                                 props -> {
                                                     props.heightDp = 50;
                                                     props.widthDp = 50;
                                                 },
                                                 "android"),
                                         text("World") // 1.2.2
-                                )));
+                                        )));
 
         // Check that we have the initial layout correctly rendered
         Renderer renderer = renderer(layout1);
@@ -2424,17 +2434,17 @@
         // Produce a new layout with one Text element and one Image changed.
         Layout layout2 =
                 layout(
-                        column(// 1
+                        column( // 1
                                 text("Hello"), // 1.1
-                                row(// 1.2
-                                        image(// 1.2.1
+                                row( // 1.2
+                                        image( // 1.2.1
                                                 props -> {
                                                     props.heightDp = 50;
                                                     props.widthDp = 50;
                                                 },
                                                 "large_image_120dp"),
                                         text("Mars") // 1.2.2
-                                )));
+                                        )));
 
         // Compute the mutation
         ViewGroupMutation mutation =
@@ -2473,11 +2483,11 @@
     public void inflateThenMutate_withChangeToProps_causesUpdate() {
         Layout layout1 =
                 layout(
-                        column(// 1
+                        column( // 1
                                 props -> props.widthDp = 55,
                                 text("Hello"), // 1.1
                                 text("World") // 1.2
-                        ));
+                                ));
 
         // Check that we have the initial layout correctly rendered
         Renderer renderer = renderer(layout1);
@@ -2493,11 +2503,11 @@
         // Produce a new layout with only the props of the container changed.
         Layout layout2 =
                 layout(
-                        column(// 1
+                        column( // 1
                                 props -> props.widthDp = 123,
                                 text("Hello"), // 1.1
                                 text("World") // 1.2
-                        ));
+                                ));
 
         // Compute the mutation
         ViewGroupMutation mutation =
@@ -2525,11 +2535,11 @@
     public void inflateThenMutate_withChangeToPropsAndOneChild_doesntUpdateAllChildren() {
         Layout layout1 =
                 layout(
-                        column(// 1
+                        column( // 1
                                 props -> props.widthDp = 55,
                                 text("Hello"), // 1.1
                                 text("World") // 1.2
-                        ));
+                                ));
 
         // Check that we have the initial layout correctly rendered
         Renderer renderer = renderer(layout1);
@@ -2545,11 +2555,11 @@
         // Produce a new layout with the props of the container and one child changed.
         Layout layout2 =
                 layout(
-                        column(// 1
+                        column( // 1
                                 props -> props.widthDp = 123,
                                 text("Hello"), // 1.1
                                 text("MARS") // 1.2
-                        ));
+                                ));
 
         // Compute the mutation
         ViewGroupMutation mutation =
@@ -2576,10 +2586,10 @@
     public void inflateThenMutate_withNoChange_producesNoOpMutation() {
         Layout layout =
                 layout(
-                        column(// 1
+                        column( // 1
                                 text("Hello"), // 1.1
                                 text("World") // 1.2
-                        ));
+                                ));
 
         // Check that we have the initial layout correctly rendered
         Renderer renderer = renderer(layout);
@@ -2617,10 +2627,10 @@
     public void inflateThenMutate_withDifferentNumberOfChildren_causesUpdate() {
         Layout layout1 =
                 layout(
-                        column(// 1
+                        column( // 1
                                 text("Hello"), // 1.1
                                 text("World") // 1.2
-                        ));
+                                ));
 
         // Check that we have the initial layout correctly rendered
         Renderer renderer = renderer(layout1);
@@ -2635,12 +2645,12 @@
 
         Layout layout2 =
                 layout(
-                        column(// 1
+                        column( // 1
                                 text("Hello"), // 1.1
                                 text("World"), // 1.2
                                 text("and"), // 1.3
                                 text("Mars") // 1.4
-                        ));
+                                ));
 
         // Compute the mutation
         ViewGroupMutation mutation =
@@ -2668,10 +2678,10 @@
     public void inflateThenMutate_withDynamicText_dataPipelineIsUpdated() {
         Layout layout1 =
                 layout(
-                        column(// 1
+                        column( // 1
                                 dynamicFixedText("Hello"), // 1.1
                                 dynamicFixedText("World") // 1.2
-                        ));
+                                ));
 
         // Check that we have the initial layout correctly rendered
         Renderer renderer = renderer(layout1);
@@ -2691,10 +2701,10 @@
 
         Layout layout2 =
                 layout(
-                        column(// 1
+                        column( // 1
                                 dynamicFixedText("Hello"), // 1.1
                                 dynamicFixedText("Mars") // 1.2
-                        ));
+                                ));
 
         // Compute the mutation
         ViewGroupMutation mutation =
@@ -2726,11 +2736,11 @@
     public void inflateThenMutate_withSelfMutation_dataPipelineIsPreserved() {
         Layout layout1 =
                 layout(
-                        column(// 1
+                        column( // 1
                                 props -> props.widthDp = 10,
                                 dynamicFixedText("Hello"), // 1.1
                                 dynamicFixedText("World") // 1.2
-                        ));
+                                ));
 
         // Check that we have the initial layout correctly rendered
         Renderer renderer = renderer(layout1);
@@ -2749,11 +2759,11 @@
         // Produce a new layout with the column width changed.
         Layout layout2 =
                 layout(
-                        column(// 1
+                        column( // 1
                                 props -> props.widthDp = 20,
                                 dynamicFixedText("Hello"), // 1.1
                                 dynamicFixedText("World") // 1.2
-                        ));
+                                ));
 
         // Compute the mutation
         ViewGroupMutation mutation =
@@ -2786,10 +2796,10 @@
     public void reInflate_dataPipelineIsReset() {
         Layout layout =
                 layout(
-                        column(// 1
+                        column( // 1
                                 dynamicFixedText("Hello"), // 1.1
                                 dynamicFixedText("World") // 1.2
-                        ));
+                                ));
 
         // Check that we have the initial layout correctly rendered
         Renderer renderer = renderer(layout);
@@ -2828,10 +2838,10 @@
     public void inflateArcThenMutate_withChangeToText_causesUpdate() {
         Layout layout1 =
                 layout(
-                        arc(// 1
+                        arc( // 1
                                 arcText("Hello"), // 1.1
                                 arcText("World") // 1.2
-                        ));
+                                ));
 
         // Check that we have the initial layout correctly rendered
         Renderer renderer = renderer(layout1);
@@ -2847,10 +2857,10 @@
         // Produce a new layout with only one Text element changed.
         Layout layout2 =
                 layout(
-                        arc(// 1
+                        arc( // 1
                                 arcText("Hello"), // 1.1
                                 arcText("Mars") // 1.2
-                        ));
+                                ));
 
         // Compute the mutation
         ViewGroupMutation mutation =
@@ -2880,10 +2890,10 @@
     public void inflateArcThenMutate_withChangeToProps_causesUpdate() throws Exception {
         Layout layout1 =
                 layout(
-                        arc(// 1
+                        arc( // 1
                                 arcText("Hello"), // 1.1
                                 arcText("World") // 1.2
-                        ));
+                                ));
 
         // Check the premutation layout
         Renderer renderer = renderer(layout1);
@@ -2899,11 +2909,11 @@
 
         Layout layout2 =
                 layout(
-                        arc(// 1
+                        arc( // 1
                                 props -> props.anchorAngleDegrees = 35,
                                 arcText("Hello"), // 1.1
                                 arcText("World") // 1.2
-                        ));
+                                ));
 
         // Compute the mutation
         ViewGroupMutation mutation =
@@ -2932,22 +2942,22 @@
     public void viewChangesWhileComputingMutation_applyMutationFails() throws Exception {
         Layout layout1 =
                 layout(
-                        arc(// 1
+                        arc( // 1
                                 arcText("Hello"), // 1.1
                                 arcText("World") // 1.2
-                        ));
+                                ));
         Layout layout2 =
                 layout(
-                        arc(// 1
+                        arc( // 1
                                 props -> props.anchorAngleDegrees = 35,
                                 arcText("Hello"), // 1.1
                                 arcText("World") // 1.2
-                        ));
+                                ));
         Layout layout3 =
                 layout(
-                        arc(// 1
+                        arc( // 1
                                 arcText("Hello") // 1.1
-                        ));
+                                ));
         // Check the premutation layout
         Renderer renderer = renderer(layout1);
         ViewGroup inflatedViewParent1 = renderer.inflate();
@@ -2969,10 +2979,10 @@
     public void inflateArcThenMutate_withDifferentNumberOfChildren_causesUpdate() {
         Layout layout1 =
                 layout(
-                        arc(// 1
+                        arc( // 1
                                 arcText("Hello"), // 1.1
                                 arcText("World") // 1.2
-                        ));
+                                ));
 
         // Check the premutation layout
         Renderer renderer = renderer(layout1);
@@ -2987,12 +2997,12 @@
 
         Layout layout2 =
                 layout(
-                        arc(// 1
+                        arc( // 1
                                 arcText("Hello"), // 1.1
                                 arcText("World"), // 1.2
                                 arcText("and"), // 1.3
                                 arcText("Mars") // 1.4
-                        ));
+                                ));
 
         // Compute the mutation
         ViewGroupMutation mutation =
@@ -3021,10 +3031,10 @@
     public void inflateAndMutateTwice_causesTwoUpdates() throws Exception {
         Layout layout1 =
                 layout(
-                        column(// 1
+                        column( // 1
                                 text("Hello"), // 1.1
                                 text("World") // 1.2
-                        ));
+                                ));
 
         // Do the initial inflation.
         Renderer renderer = renderer(layout1);
@@ -3037,10 +3047,10 @@
 
         Layout layout2 =
                 layout(
-                        column(// 1
+                        column( // 1
                                 text("Goodbye"), // 1.1
                                 text("World") // 1.2
-                        ));
+                                ));
 
         // Apply first mutation
         ViewGroupMutation mutation1 =
@@ -3058,10 +3068,10 @@
 
         Layout layout3 =
                 layout(
-                        column(// 1
+                        column( // 1
                                 text("Hello"), // 1.1
                                 text("Mars") // 1.2
-                        ));
+                                ));
 
         // Apply second mutation
         ViewGroupMutation mutation2 =
@@ -3082,10 +3092,10 @@
     public void inflateArcThenMutate_withNoChange_producesNoOpMutation() {
         Layout layout =
                 layout(
-                        arc(// 1
+                        arc( // 1
                                 arcText("Hello"), // 1.1
                                 arcText("World") // 1.2
-                        ));
+                                ));
 
         // Check that we have the initial layout correctly rendered
         Renderer renderer = renderer(layout);
@@ -3114,7 +3124,7 @@
     public void boxWithChild_childChanges_appliesGravityToUpdatedChild() throws Exception {
         Layout layout1 =
                 layout(
-                        box(// 1
+                        box( // 1
                                 boxProps -> {
                                     boxProps.horizontalAlignment =
                                             HorizontalAlignment.HORIZONTAL_ALIGN_CENTER;
@@ -3122,12 +3132,12 @@
                                             VerticalAlignment.VERTICAL_ALIGN_CENTER;
                                 },
                                 text("Hello") // 1.1
-                        ));
+                                ));
         Renderer renderer = renderer(layout1);
         ViewGroup inflatedViewParent = renderer.inflate();
         Layout layout2 =
                 layout(
-                        box(// 1
+                        box( // 1
                                 boxProps -> {
                                     boxProps.horizontalAlignment =
                                             HorizontalAlignment.HORIZONTAL_ALIGN_CENTER;
@@ -3135,7 +3145,7 @@
                                             VerticalAlignment.VERTICAL_ALIGN_CENTER;
                                 },
                                 text("World") // 1.1
-                        ));
+                                ));
 
         ViewGroupMutation mutation =
                 renderer.mRenderer.computeMutation(
@@ -3156,7 +3166,7 @@
     public void boxWithChild_boxChanges_appliesNewGravityToChild() throws Exception {
         Layout layout1 =
                 layout(
-                        box(// 1
+                        box( // 1
                                 boxProps -> {
                                     boxProps.horizontalAlignment =
                                             HorizontalAlignment.HORIZONTAL_ALIGN_CENTER;
@@ -3164,12 +3174,12 @@
                                             VerticalAlignment.VERTICAL_ALIGN_CENTER;
                                 },
                                 text("Hello") // 1.1
-                        ));
+                                ));
         Renderer renderer = renderer(layout1);
         ViewGroup inflatedViewParent = renderer.inflate();
         Layout layout2 =
                 layout(
-                        box(// 1
+                        box( // 1
                                 boxProps -> {
                                     // A different set of alignments.
                                     boxProps.horizontalAlignment =
@@ -3178,7 +3188,7 @@
                                             VerticalAlignment.VERTICAL_ALIGN_BOTTOM;
                                 },
                                 text("Hello") // 1.1
-                        ));
+                                ));
 
         ViewGroupMutation mutation =
                 renderer.mRenderer.computeMutation(
@@ -3199,7 +3209,7 @@
     public void boxWithChild_bothChange_appliesNewGravityToUpdatedChild() throws Exception {
         Layout layout1 =
                 layout(
-                        box(// 1
+                        box( // 1
                                 boxProps -> {
                                     boxProps.horizontalAlignment =
                                             HorizontalAlignment.HORIZONTAL_ALIGN_CENTER;
@@ -3207,13 +3217,13 @@
                                             VerticalAlignment.VERTICAL_ALIGN_CENTER;
                                 },
                                 text("Hello") // 1.1
-                        ));
+                                ));
         // Do the initial inflation.
         Renderer renderer = renderer(layout1);
         ViewGroup inflatedViewParent = renderer.inflate();
         Layout layout2 =
                 layout(
-                        box(// 1
+                        box( // 1
                                 boxProps -> {
                                     // A different set of alignments.
                                     boxProps.horizontalAlignment =
@@ -3222,7 +3232,7 @@
                                             VerticalAlignment.VERTICAL_ALIGN_BOTTOM;
                                 },
                                 text("World") // 1.1
-                        ));
+                                ));
 
         ViewGroupMutation mutation =
                 renderer.mRenderer.computeMutation(
@@ -3268,10 +3278,9 @@
     ProtoLayoutInflater.Config.Builder newRendererConfigBuilder(
             Layout layout, ResourceResolvers.Builder resourceResolvers) {
         return new ProtoLayoutInflater.Config.Builder(
-                getApplicationContext(), layout, resourceResolvers.build())
+                        getApplicationContext(), layout, resourceResolvers.build())
                 .setClickableIdExtra(EXTRA_CLICKABLE_ID)
-                .setLoadActionListener(p -> {
-                })
+                .setLoadActionListener(p -> {})
                 .setLoadActionExecutor(ContextCompat.getMainExecutor(getApplicationContext()));
     }
 
@@ -3412,6 +3421,7 @@
                 .build();
     }
 
+    @NonNull
     private static Trigger onVisibleTrigger() {
         return Trigger.newBuilder()
                 .setOnVisibleTrigger(OnVisibleTrigger.getDefaultInstance())
@@ -3461,6 +3471,16 @@
         expect.that(linearLayoutParams.weight).isEqualTo(10.0f);
     }
 
+    @NonNull
+    private static ContainerDimension expandWeight() {
+        return ContainerDimension.newBuilder()
+                .setExpandedDimension(
+                        ExpandedDimensionProp.newBuilder()
+                                .setLayoutWeight(FloatProp.newBuilder().setValue(10.0f).build())
+                                .build())
+                .build();
+    }
+
     @Test
     public void inflate_column_withLayoutWeight() {
         final String protoResId = "android";
@@ -3529,15 +3549,6 @@
         expect.that(linearLayoutParams.weight).isEqualTo(10.0f);
     }
 
-    private static ContainerDimension expandWeight() {
-        return ContainerDimension.newBuilder()
-                .setExpandedDimension(
-                        ExpandedDimensionProp.newBuilder()
-                                .setLayoutWeight(FloatProp.newBuilder().setValue(10.0f).build())
-                                .build())
-                .build();
-    }
-
     @Test
     public void enterTransition_noQuota_notPlayed() throws Exception {
         Renderer renderer =
@@ -3881,9 +3892,9 @@
         Renderer renderer =
                 renderer(
                         newRendererConfigBuilder(
-                                fingerprintedLayout(
-                                        getTextElementWithExitAnimation(
-                                                "Hello", /* iterations= */ 1)))
+                                        fingerprintedLayout(
+                                                getTextElementWithExitAnimation(
+                                                        "Hello", /* iterations= */ 1)))
                                 .setAnimationEnabled(false));
         mDataPipeline.setFullyVisible(true);
         FrameLayout inflatedViewParent = renderer.inflate();
@@ -4124,8 +4135,8 @@
                                         .setEnterTransition(
                                                 EnterTransition.newBuilder()
                                                         .setFadeIn(
-                                                                FadeInTransition.newBuilder()
-                                                                        .build())
+                                                                FadeInTransition
+                                                                        .getDefaultInstance())
                                                         .setSlideIn(
                                                                 SlideInTransition.newBuilder()
                                                                         .build())),
diff --git a/wear/tiles/tiles-material/src/androidTest/java/androidx/wear/tiles/material/testapp/GoldenTestActivity.java b/wear/tiles/tiles-material/src/androidTest/java/androidx/wear/tiles/material/testapp/GoldenTestActivity.java
index 3b6ef5b1..4f8ca09 100644
--- a/wear/tiles/tiles-material/src/androidTest/java/androidx/wear/tiles/material/testapp/GoldenTestActivity.java
+++ b/wear/tiles/tiles-material/src/androidTest/java/androidx/wear/tiles/material/testapp/GoldenTestActivity.java
@@ -43,6 +43,7 @@
 import androidx.wear.tiles.renderer.TileRenderer;
 
 import java.util.concurrent.Executor;
+import java.util.concurrent.TimeUnit;
 
 public class GoldenTestActivity extends Activity {
     private static final String ICON_ID = "tile_icon";
@@ -76,11 +77,16 @@
         Resources resources = generateResources();
         TileRenderer renderer = new TileRenderer(appContext, mainExecutor, i -> {});
 
-        View firstChild = renderer.inflate(layout, resources, root);
+       try {
+           View firstChild =
+                   renderer.inflateAsync(layout, resources, root).get(30, TimeUnit.MILLISECONDS);
 
-        // Simulate what the thing outside the renderer should do. Center the contents.
-        LayoutParams layoutParams = (LayoutParams) firstChild.getLayoutParams();
-        layoutParams.gravity = Gravity.CENTER;
+           // Simulate what the thing outside the renderer should do. Center the contents.
+           LayoutParams layoutParams = (LayoutParams) firstChild.getLayoutParams();
+           layoutParams.gravity = Gravity.CENTER;
+       } catch (Exception e) {
+           throw new IllegalStateException("Rendering of layout hasn't finished in time.", e);
+       }
 
         // Set the activity to be full screen so when we crop the Bitmap we don't get time bar etc.
         requestWindowFeature(Window.FEATURE_NO_TITLE);
diff --git a/wear/tiles/tiles-renderer/api/current.txt b/wear/tiles/tiles-renderer/api/current.txt
index 670ef3a..431fc0c 100644
--- a/wear/tiles/tiles-renderer/api/current.txt
+++ b/wear/tiles/tiles-renderer/api/current.txt
@@ -46,7 +46,7 @@
     ctor @Deprecated public TileRenderer(android.content.Context, androidx.wear.tiles.LayoutElementBuilders.Layout, @StyleRes int, androidx.wear.tiles.ResourceBuilders.Resources, java.util.concurrent.Executor, androidx.wear.tiles.renderer.TileRenderer.LoadActionListener);
     ctor public TileRenderer(android.content.Context, java.util.concurrent.Executor, java.util.function.Consumer<androidx.wear.protolayout.StateBuilders.State!>);
     method @Deprecated public android.view.View? inflate(android.view.ViewGroup);
-    method public android.view.View? inflate(androidx.wear.protolayout.LayoutElementBuilders.Layout, androidx.wear.protolayout.ResourceBuilders.Resources, android.view.ViewGroup);
+    method public com.google.common.util.concurrent.ListenableFuture<android.view.View!> inflateAsync(androidx.wear.protolayout.LayoutElementBuilders.Layout, androidx.wear.protolayout.ResourceBuilders.Resources, android.view.ViewGroup);
   }
 
   @Deprecated public static interface TileRenderer.LoadActionListener {
diff --git a/wear/tiles/tiles-renderer/api/public_plus_experimental_current.txt b/wear/tiles/tiles-renderer/api/public_plus_experimental_current.txt
index 670ef3a..431fc0c 100644
--- a/wear/tiles/tiles-renderer/api/public_plus_experimental_current.txt
+++ b/wear/tiles/tiles-renderer/api/public_plus_experimental_current.txt
@@ -46,7 +46,7 @@
     ctor @Deprecated public TileRenderer(android.content.Context, androidx.wear.tiles.LayoutElementBuilders.Layout, @StyleRes int, androidx.wear.tiles.ResourceBuilders.Resources, java.util.concurrent.Executor, androidx.wear.tiles.renderer.TileRenderer.LoadActionListener);
     ctor public TileRenderer(android.content.Context, java.util.concurrent.Executor, java.util.function.Consumer<androidx.wear.protolayout.StateBuilders.State!>);
     method @Deprecated public android.view.View? inflate(android.view.ViewGroup);
-    method public android.view.View? inflate(androidx.wear.protolayout.LayoutElementBuilders.Layout, androidx.wear.protolayout.ResourceBuilders.Resources, android.view.ViewGroup);
+    method public com.google.common.util.concurrent.ListenableFuture<android.view.View!> inflateAsync(androidx.wear.protolayout.LayoutElementBuilders.Layout, androidx.wear.protolayout.ResourceBuilders.Resources, android.view.ViewGroup);
   }
 
   @Deprecated public static interface TileRenderer.LoadActionListener {
diff --git a/wear/tiles/tiles-renderer/api/restricted_current.txt b/wear/tiles/tiles-renderer/api/restricted_current.txt
index 670ef3a..431fc0c 100644
--- a/wear/tiles/tiles-renderer/api/restricted_current.txt
+++ b/wear/tiles/tiles-renderer/api/restricted_current.txt
@@ -46,7 +46,7 @@
     ctor @Deprecated public TileRenderer(android.content.Context, androidx.wear.tiles.LayoutElementBuilders.Layout, @StyleRes int, androidx.wear.tiles.ResourceBuilders.Resources, java.util.concurrent.Executor, androidx.wear.tiles.renderer.TileRenderer.LoadActionListener);
     ctor public TileRenderer(android.content.Context, java.util.concurrent.Executor, java.util.function.Consumer<androidx.wear.protolayout.StateBuilders.State!>);
     method @Deprecated public android.view.View? inflate(android.view.ViewGroup);
-    method public android.view.View? inflate(androidx.wear.protolayout.LayoutElementBuilders.Layout, androidx.wear.protolayout.ResourceBuilders.Resources, android.view.ViewGroup);
+    method public com.google.common.util.concurrent.ListenableFuture<android.view.View!> inflateAsync(androidx.wear.protolayout.LayoutElementBuilders.Layout, androidx.wear.protolayout.ResourceBuilders.Resources, android.view.ViewGroup);
   }
 
   @Deprecated public static interface TileRenderer.LoadActionListener {
diff --git a/wear/tiles/tiles-renderer/src/androidTest/java/androidx/wear/tiles/renderer/test/TileRendererGoldenTest.java b/wear/tiles/tiles-renderer/src/androidTest/java/androidx/wear/tiles/renderer/test/TileRendererGoldenTest.java
index 8d16cea..a80d71a 100644
--- a/wear/tiles/tiles-renderer/src/androidTest/java/androidx/wear/tiles/renderer/test/TileRendererGoldenTest.java
+++ b/wear/tiles/tiles-renderer/src/androidTest/java/androidx/wear/tiles/renderer/test/TileRendererGoldenTest.java
@@ -53,6 +53,7 @@
 import java.io.InputStreamReader;
 import java.util.Arrays;
 import java.util.Collection;
+import java.util.concurrent.TimeUnit;
 
 @RunWith(Parameterized.class)
 @LargeTest
@@ -239,9 +240,10 @@
                         ContextCompat.getMainExecutor(getApplicationContext()),
                         i -> {});
 
-        View firstChild = renderer.inflate(LayoutElementBuilders.Layout.fromProto(
+        View firstChild = renderer.inflateAsync(LayoutElementBuilders.Layout.fromProto(
                 Layout.newBuilder().setRoot(rootElement).build()),
-                ResourceBuilders.Resources.fromProto(generateResources()), mainFrame);
+                ResourceBuilders.Resources.fromProto(generateResources()), mainFrame)
+                .get(30, TimeUnit.MILLISECONDS);
 
         if (firstChild == null) {
             throw new RuntimeException("Failed to inflate " + expectedKey);
diff --git a/wear/tiles/tiles-renderer/src/main/java/androidx/wear/tiles/manager/TileUiClient.kt b/wear/tiles/tiles-renderer/src/main/java/androidx/wear/tiles/manager/TileUiClient.kt
index 525dfcf6..5681e15 100644
--- a/wear/tiles/tiles-renderer/src/main/java/androidx/wear/tiles/manager/TileUiClient.kt
+++ b/wear/tiles/tiles-renderer/src/main/java/androidx/wear/tiles/manager/TileUiClient.kt
@@ -30,16 +30,17 @@
 import androidx.annotation.MainThread
 import androidx.concurrent.futures.await
 import androidx.core.content.ContextCompat
-import androidx.wear.tiles.DeviceParametersBuilders
 import androidx.wear.protolayout.LayoutElementBuilders
 import androidx.wear.protolayout.ResourceBuilders
 import androidx.wear.protolayout.StateBuilders
+import androidx.wear.tiles.DeviceParametersBuilders
 import androidx.wear.tiles.RequestBuilders
 import androidx.wear.tiles.TimelineBuilders
 import androidx.wear.tiles.checkers.TimelineChecker
 import androidx.wear.tiles.connection.DefaultTileClient
 import androidx.wear.tiles.renderer.TileRenderer
 import androidx.wear.tiles.timeline.TilesTimelineManager
+import java.util.concurrent.Executors
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.Job
@@ -48,7 +49,6 @@
 import kotlinx.coroutines.isActive
 import kotlinx.coroutines.launch
 import kotlinx.coroutines.withContext
-import java.util.concurrent.Executors
 
 /**
  * UI client for a single tile. This handles binding to a Tile Service, and inflating the given
@@ -199,13 +199,15 @@
             ContextCompat.getMainExecutor(context),
             { state -> coroutineScope.launch { requestTile(state) } }
         )
-        renderer.inflate(
+        val result = renderer.inflateAsync(
             LayoutElementBuilders.Layout.fromProto(layout.toProto()),
             tileResources!!,
             parentView
-        )?.apply {
-            (layoutParams as FrameLayout.LayoutParams).gravity = Gravity.CENTER
-        }
+        )
+        result.addListener(
+            { (result.get().layoutParams as FrameLayout.LayoutParams).gravity = Gravity.CENTER },
+            ContextCompat.getMainExecutor(context)
+        )
     }
 
     private fun registerBroadcastReceiver() {
diff --git a/wear/tiles/tiles-renderer/src/main/java/androidx/wear/tiles/renderer/TileRenderer.java b/wear/tiles/tiles-renderer/src/main/java/androidx/wear/tiles/renderer/TileRenderer.java
index e3a8ce1..1a8c76c 100644
--- a/wear/tiles/tiles-renderer/src/main/java/androidx/wear/tiles/renderer/TileRenderer.java
+++ b/wear/tiles/tiles-renderer/src/main/java/androidx/wear/tiles/renderer/TileRenderer.java
@@ -35,11 +35,16 @@
 import androidx.wear.tiles.TileService;
 
 import com.google.common.collect.ImmutableMap;
+import com.google.common.util.concurrent.FluentFuture;
+import com.google.common.util.concurrent.ListenableFuture;
 import com.google.common.util.concurrent.ListeningExecutorService;
 import com.google.common.util.concurrent.MoreExecutors;
 
+import java.util.concurrent.CancellationException;
+import java.util.concurrent.ExecutionException;
 import java.util.concurrent.Executor;
 import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
 import java.util.function.Consumer;
 
 /**
@@ -80,8 +85,8 @@
      * @param loadActionExecutor Executor for {@code loadActionListener}.
      * @param loadActionListener Listener for clicks that will cause the contents to be reloaded.
      * @deprecated Use {@link #TileRenderer(Context, Executor, Consumer)} which accepts Layout and
-     *     Resources in {@link #inflate(LayoutElementBuilders.Layout, ResourceBuilders.Resources,
-     *     ViewGroup)} method.
+     *     Resources in {@link #inflateAsync(LayoutElementBuilders.Layout,
+     *     ResourceBuilders.Resources, ViewGroup)} method.
      */
     @Deprecated
     public TileRenderer(
@@ -109,8 +114,8 @@
      * @param loadActionExecutor Executor for {@code loadActionListener}.
      * @param loadActionListener Listener for clicks that will cause the contents to be reloaded.
      * @deprecated Use {@link #TileRenderer(Context, Executor, Consumer)} which accepts Layout and
-     *     Resources in {@link #inflate(LayoutElementBuilders.Layout, ResourceBuilders.Resources,
-     *     ViewGroup)} method.
+     *     Resources in {@link #inflateAsync(LayoutElementBuilders.Layout,
+     *     ResourceBuilders.Resources, ViewGroup)} method.
      */
     @Deprecated
     public TileRenderer(
@@ -187,9 +192,9 @@
      * @return The first child that was inflated. This may be null if the Layout is empty or the
      *     top-level LayoutElement has no inner set, or the top-level LayoutElement contains an
      *     unsupported inner type.
-     * @deprecated Use {@link #inflate(LayoutElementBuilders.Layout, ResourceBuilders.Resources,
-     *     ViewGroup)} instead. Note: This method only works with the deprecated constructors that
-     *     accept Layout and Resources.
+     * @deprecated Use {@link #inflateAsync(LayoutElementBuilders.Layout,
+     *     ResourceBuilders.Resources, ViewGroup)} instead. Note: This method only works with the
+     *     deprecated constructors that accept Layout and Resources.
      */
     @Deprecated
     @Nullable
@@ -197,44 +202,45 @@
         String errorMessage =
                 "This method only works with the deprecated constructors that accept Layout and"
                     + " Resources.";
-        return inflateLayout(
-                checkNotNull(mLayout, errorMessage),
-                checkNotNull(mResources, errorMessage),
-                parent);
+        try {
+            // Waiting for the result from future for backwards compatibility.
+            return inflateLayout(
+                    checkNotNull(mLayout, errorMessage),
+                    checkNotNull(mResources, errorMessage),
+                    parent).get(10, TimeUnit.SECONDS);
+        } catch (ExecutionException | InterruptedException | CancellationException |
+                 TimeoutException e) {
+            // Wrap checked exceptions to avoid changing the method signature.
+            throw new RuntimeException("Rendering tile has not successfully finished.", e);
+        }
     }
 
     /**
      * Inflates a Tile into {@code parent}.
      *
-     * @param layout The portion of the Tile to render.
+     * @param layout    The portion of the Tile to render.
      * @param resources The resources for the Tile.
-     * @param parent The view to attach the tile into.
-     * @return The first child that was inflated. This may be null if the Layout is empty or the
-     *     top-level LayoutElement has no inner set, or the top-level LayoutElement contains an
-     *     unsupported inner type.
+     * @param parent    The view to attach the tile into.
+     * @return The future with the first child that was inflated. This may be null if the Layout is
+     * empty or the top-level LayoutElement has no inner set, or the top-level LayoutElement
+     * contains an
+     * unsupported inner type.
      */
-    @Nullable
-    public View inflate(
+    @NonNull
+    public ListenableFuture<View> inflateAsync(
             @NonNull LayoutElementBuilders.Layout layout,
             @NonNull ResourceBuilders.Resources resources,
             @NonNull ViewGroup parent) {
         return inflateLayout(layout.toProto(), resources.toProto(), parent);
     }
 
-    @Nullable
-    private View inflateLayout(
+    @NonNull
+    private ListenableFuture<View> inflateLayout(
             @NonNull LayoutElementProto.Layout layout,
             @NonNull ResourceProto.Resources resources,
             @NonNull ViewGroup parent) {
-        mInstance.renderAndAttach(layout, resources, parent);
-        boolean finished;
-        try {
-            mUiExecutor.shutdown();
-            finished = mUiExecutor.awaitTermination(30, TimeUnit.SECONDS);
-        } catch (InterruptedException e) {
-            throw new RuntimeException("Rendering tile has not successfully finished.");
-        }
-        // TODO(b/271076323): Update when renderAndAttach returns result.
-        return finished ? parent.getChildAt(0) : null;
+        ListenableFuture<Void> result = mInstance.renderAndAttach(layout, resources, parent);
+            return FluentFuture.from(result)
+                    .transform(ignored -> parent.getChildAt(0), mUiExecutor);
     }
 }
diff --git a/wear/tiles/tiles-tooling/src/main/java/androidx/wear/tiles/tooling/TileServiceViewAdapter.kt b/wear/tiles/tiles-tooling/src/main/java/androidx/wear/tiles/tooling/TileServiceViewAdapter.kt
index 20202d9..ccc7b55 100644
--- a/wear/tiles/tiles-tooling/src/main/java/androidx/wear/tiles/tooling/TileServiceViewAdapter.kt
+++ b/wear/tiles/tiles-tooling/src/main/java/androidx/wear/tiles/tooling/TileServiceViewAdapter.kt
@@ -51,12 +51,13 @@
     while (currentClass != null) {
         try {
             return currentClass.getDeclaredMethod(name, *parameterTypes)
-        } catch (_: NoSuchMethodException) { }
+        } catch (_: NoSuchMethodException) {}
         currentClass = currentClass.superclass
     }
     val methodSignature = "$name(${parameterTypes.joinToString { ", " }})"
     throw NoSuchMethodException(
-        "Could not find method $methodSignature neither in $this nor in its superclasses.")
+        "Could not find method $methodSignature neither in $this nor in its superclasses."
+    )
 }
 
 /**
@@ -85,17 +86,17 @@
 
         // tileService.attachBaseContext(context)
         val attachBaseContextMethod =
-            tileServiceClass
-                .findMethod("attachBaseContext", Context::class.java)
-                .apply { isAccessible = true }
+            tileServiceClass.findMethod("attachBaseContext", Context::class.java).apply {
+                isAccessible = true
+            }
         attachBaseContextMethod.invoke(tileService, context)
 
         val deviceParams = context.buildDeviceParameters()
-        val tileRequest = RequestBuilders.TileRequest
-            .Builder()
-            .setState(StateBuilders.State.Builder().build())
-            .setDeviceParameters(deviceParams)
-            .build()
+        val tileRequest =
+            RequestBuilders.TileRequest.Builder()
+                .setState(StateBuilders.State.Builder().build())
+                .setDeviceParameters(deviceParams)
+                .build()
 
         // val tile = tileService.onTileRequest(tileRequest)
         val >
@@ -103,14 +104,15 @@
                 .findMethod("onTileRequest", RequestBuilders.TileRequest::class.java)
                 .apply { isAccessible = true }
         val tile =
-            (onTileRequestMethod.invoke(tileService, tileRequest) as
-                ListenableFuture<TileBuilders.Tile>).get(1, TimeUnit.SECONDS)
+            (onTileRequestMethod.invoke(tileService, tileRequest)
+                    as ListenableFuture<TileBuilders.Tile>)
+                .get(1, TimeUnit.SECONDS)
 
-        val resourceRequest = RequestBuilders.ResourcesRequest
-            .Builder()
-            .setVersion(tile.resourcesVersion)
-            .setDeviceParameters(deviceParams)
-            .build()
+        val resourceRequest =
+            RequestBuilders.ResourcesRequest.Builder()
+                .setVersion(tile.resourcesVersion)
+                .setDeviceParameters(deviceParams)
+                .build()
 
         // val resources = tileService.onResourcesRequest(resourceRequest).get(1, TimeUnit.SECONDS)
         val >
@@ -119,20 +121,22 @@
                 .apply { isAccessible = true }
         val resources =
             ResourceBuilders.Resources.fromProto(
-                (onResourcesRequestMethod.invoke(tileService, resourceRequest) as
-                    ListenableFuture<androidx.wear.tiles.ResourceBuilders.Resources>)
-                    .get(1, TimeUnit.SECONDS).toProto()
+                (onResourcesRequestMethod.invoke(tileService, resourceRequest)
+                        as ListenableFuture<androidx.wear.tiles.ResourceBuilders.Resources>)
+                    .get(1, TimeUnit.SECONDS)
+                    .toProto()
             )
 
         val layout = tile.timeline?.getCurrentLayout()
         if (layout != null) {
-            val renderer = TileRenderer(
-                context,
+            val renderer = TileRenderer(context, ContextCompat.getMainExecutor(context)) {}
+            val result = renderer.inflateAsync(layout, resources, this)
+            result.addListener(
+                {
+                    (result.get().layoutParams as FrameLayout.LayoutParams).gravity = Gravity.CENTER
+                },
                 ContextCompat.getMainExecutor(context)
-            ) { }
-            renderer.inflate(layout, resources, this)?.apply {
-                (layoutParams as FrameLayout.LayoutParams).gravity = Gravity.CENTER
-            }
+            )
         }
     }
 }
@@ -140,24 +144,20 @@
 internal fun TimelineBuilders.Timeline?.getCurrentLayout(): LayoutElementBuilders.Layout? {
     val now = System.currentTimeMillis()
     return this?.let {
-        val cache = TilesTimelineCache(it)
-        cache.findTimelineEntryForTime(now) ?: cache.findClosestTimelineEntry(now)
-    }?.layout?.let {
-        LayoutElementBuilders.Layout.fromProto(it.toProto())
-    }
+            val cache = TilesTimelineCache(it)
+            cache.findTimelineEntryForTime(now) ?: cache.findClosestTimelineEntry(now)
+        }
+        ?.layout
+        ?.let { LayoutElementBuilders.Layout.fromProto(it.toProto()) }
 }
 
-/**
- * Creates an instance of [DeviceParametersBuilders.DeviceParameters] from the [Context].
- */
+/** Creates an instance of [DeviceParametersBuilders.DeviceParameters] from the [Context]. */
 internal fun Context.buildDeviceParameters(): DeviceParametersBuilders.DeviceParameters {
     val displayMetrics = resources.displayMetrics
     val isScreenRound = resources.configuration.isScreenRound
     return DeviceParametersBuilders.DeviceParameters.Builder()
-        .setScreenWidthDp(
-            (displayMetrics.widthPixels / displayMetrics.density).roundToInt())
-        .setScreenHeightDp(
-            (displayMetrics.heightPixels / displayMetrics.density).roundToInt())
+        .setScreenWidthDp((displayMetrics.widthPixels / displayMetrics.density).roundToInt())
+        .setScreenHeightDp((displayMetrics.heightPixels / displayMetrics.density).roundToInt())
         .setScreenDensity(displayMetrics.density)
         .setScreenShape(
             if (isScreenRound) DeviceParametersBuilders.SCREEN_SHAPE_ROUND
@@ -165,4 +165,4 @@
         )
         .setDevicePlatform(DeviceParametersBuilders.DEVICE_PLATFORM_WEAR_OS)
         .build()
-}
\ No newline at end of file
+}