Merge "Allow invalidations during the draw path" into androidx-main
diff --git a/buildSrc/src/main/kotlin/androidx/build/LibraryVersions.kt b/buildSrc/src/main/kotlin/androidx/build/LibraryVersions.kt
index eee3524..78bd208 100644
--- a/buildSrc/src/main/kotlin/androidx/build/LibraryVersions.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/LibraryVersions.kt
@@ -48,7 +48,7 @@
     val CONTENTPAGER = Version("1.1.0-alpha01")
     val COMPOSE = Version(System.getenv("COMPOSE_CUSTOM_VERSION") ?: "1.0.0-beta08")
     val COORDINATORLAYOUT = Version("1.2.0-alpha01")
-    val CORE = Version("1.6.0-beta02")
+    val CORE = Version("1.7.0-alpha01")
     val CORE_ANIMATION = Version("1.0.0-alpha03")
     val CORE_ANIMATION_TESTING = Version("1.0.0-alpha03")
     val CORE_APPDIGEST = Version("1.0.0-alpha01")
diff --git a/car/app/app/api/current.txt b/car/app/app/api/current.txt
index f7ec0ae9..3a71b2b 100644
--- a/car/app/app/api/current.txt
+++ b/car/app/app/api/current.txt
@@ -499,6 +499,7 @@
   }
 
   @androidx.car.app.annotations.CarProtocol public final class MessageTemplate implements androidx.car.app.model.Template {
+    method @androidx.car.app.annotations.RequiresCarApi(2) public androidx.car.app.model.ActionStrip? getActionStrip();
     method public java.util.List<androidx.car.app.model.Action!> getActions();
     method public androidx.car.app.model.CarText? getDebugMessage();
     method public androidx.car.app.model.Action? getHeaderAction();
@@ -513,6 +514,7 @@
     ctor public MessageTemplate.Builder(androidx.car.app.model.CarText);
     method public androidx.car.app.model.MessageTemplate.Builder addAction(androidx.car.app.model.Action);
     method public androidx.car.app.model.MessageTemplate build();
+    method @androidx.car.app.annotations.RequiresCarApi(2) public androidx.car.app.model.MessageTemplate.Builder setActionStrip(androidx.car.app.model.ActionStrip);
     method public androidx.car.app.model.MessageTemplate.Builder setDebugMessage(Throwable);
     method public androidx.car.app.model.MessageTemplate.Builder setDebugMessage(String);
     method public androidx.car.app.model.MessageTemplate.Builder setHeaderAction(androidx.car.app.model.Action);
diff --git a/car/app/app/api/public_plus_experimental_current.txt b/car/app/app/api/public_plus_experimental_current.txt
index 9ddca75..dbcb362 100644
--- a/car/app/app/api/public_plus_experimental_current.txt
+++ b/car/app/app/api/public_plus_experimental_current.txt
@@ -502,6 +502,7 @@
   }
 
   @androidx.car.app.annotations.CarProtocol public final class MessageTemplate implements androidx.car.app.model.Template {
+    method @androidx.car.app.annotations.RequiresCarApi(2) public androidx.car.app.model.ActionStrip? getActionStrip();
     method public java.util.List<androidx.car.app.model.Action!> getActions();
     method public androidx.car.app.model.CarText? getDebugMessage();
     method public androidx.car.app.model.Action? getHeaderAction();
@@ -516,6 +517,7 @@
     ctor public MessageTemplate.Builder(androidx.car.app.model.CarText);
     method public androidx.car.app.model.MessageTemplate.Builder addAction(androidx.car.app.model.Action);
     method public androidx.car.app.model.MessageTemplate build();
+    method @androidx.car.app.annotations.RequiresCarApi(2) public androidx.car.app.model.MessageTemplate.Builder setActionStrip(androidx.car.app.model.ActionStrip);
     method public androidx.car.app.model.MessageTemplate.Builder setDebugMessage(Throwable);
     method public androidx.car.app.model.MessageTemplate.Builder setDebugMessage(String);
     method public androidx.car.app.model.MessageTemplate.Builder setHeaderAction(androidx.car.app.model.Action);
diff --git a/car/app/app/api/restricted_current.txt b/car/app/app/api/restricted_current.txt
index f7ec0ae9..3a71b2b 100644
--- a/car/app/app/api/restricted_current.txt
+++ b/car/app/app/api/restricted_current.txt
@@ -499,6 +499,7 @@
   }
 
   @androidx.car.app.annotations.CarProtocol public final class MessageTemplate implements androidx.car.app.model.Template {
+    method @androidx.car.app.annotations.RequiresCarApi(2) public androidx.car.app.model.ActionStrip? getActionStrip();
     method public java.util.List<androidx.car.app.model.Action!> getActions();
     method public androidx.car.app.model.CarText? getDebugMessage();
     method public androidx.car.app.model.Action? getHeaderAction();
@@ -513,6 +514,7 @@
     ctor public MessageTemplate.Builder(androidx.car.app.model.CarText);
     method public androidx.car.app.model.MessageTemplate.Builder addAction(androidx.car.app.model.Action);
     method public androidx.car.app.model.MessageTemplate build();
+    method @androidx.car.app.annotations.RequiresCarApi(2) public androidx.car.app.model.MessageTemplate.Builder setActionStrip(androidx.car.app.model.ActionStrip);
     method public androidx.car.app.model.MessageTemplate.Builder setDebugMessage(Throwable);
     method public androidx.car.app.model.MessageTemplate.Builder setDebugMessage(String);
     method public androidx.car.app.model.MessageTemplate.Builder setHeaderAction(androidx.car.app.model.Action);
diff --git a/car/app/app/src/main/java/androidx/car/app/model/MessageTemplate.java b/car/app/app/src/main/java/androidx/car/app/model/MessageTemplate.java
index a45261c..0434b6e 100644
--- a/car/app/app/src/main/java/androidx/car/app/model/MessageTemplate.java
+++ b/car/app/app/src/main/java/androidx/car/app/model/MessageTemplate.java
@@ -18,6 +18,7 @@
 
 import static androidx.car.app.model.constraints.ActionsConstraints.ACTIONS_CONSTRAINTS_BODY;
 import static androidx.car.app.model.constraints.ActionsConstraints.ACTIONS_CONSTRAINTS_HEADER;
+import static androidx.car.app.model.constraints.ActionsConstraints.ACTIONS_CONSTRAINTS_SIMPLE;
 
 import static java.util.Objects.hash;
 import static java.util.Objects.requireNonNull;
@@ -67,6 +68,9 @@
     private final Action mHeaderAction;
     @Keep
     private final List<Action> mActionList;
+    @Keep
+    @Nullable
+    private final ActionStrip mActionStrip;
 
     /**
      * Returns whether the template is loading.
@@ -100,6 +104,17 @@
     }
 
     /**
+     * Returns the {@link ActionStrip} for this template or {@code null} if not set.
+     *
+     * @see Builder#setActionStrip(ActionStrip)
+     */
+    @RequiresCarApi(2)
+    @Nullable
+    public ActionStrip getActionStrip() {
+        return mActionStrip;
+    }
+
+    /**
      * Returns the message to display in the template.
      *
      * @see Builder#Builder(CharSequence)
@@ -148,7 +163,8 @@
 
     @Override
     public int hashCode() {
-        return hash(mIsLoading, mTitle, mMessage, mDebugMessage, mHeaderAction, mActionList, mIcon);
+        return hash(mIsLoading, mTitle, mMessage, mDebugMessage, mHeaderAction, mActionList, mIcon,
+                mActionStrip);
     }
 
     @Override
@@ -167,7 +183,8 @@
                 && Objects.equals(mDebugMessage, otherTemplate.mDebugMessage)
                 && Objects.equals(mHeaderAction, otherTemplate.mHeaderAction)
                 && Objects.equals(mActionList, otherTemplate.mActionList)
-                && Objects.equals(mIcon, otherTemplate.mIcon);
+                && Objects.equals(mIcon, otherTemplate.mIcon)
+                && Objects.equals(mActionStrip, otherTemplate.mActionStrip);
     }
 
     MessageTemplate(Builder builder) {
@@ -177,6 +194,7 @@
         mDebugMessage = builder.mDebugMessage;
         mIcon = builder.mIcon;
         mHeaderAction = builder.mHeaderAction;
+        mActionStrip = builder.mActionStrip;
         mActionList = CollectionUtils.unmodifiableCopy(builder.mActionList);
     }
 
@@ -188,6 +206,7 @@
         mDebugMessage = null;
         mIcon = null;
         mHeaderAction = null;
+        mActionStrip = null;
         mActionList = Collections.emptyList();
     }
 
@@ -203,6 +222,8 @@
         CarIcon mIcon;
         @Nullable
         Action mHeaderAction;
+        @Nullable
+        ActionStrip mActionStrip;
         List<Action> mActionList = new ArrayList<>();
         @Nullable
         Throwable mDebugCause;
@@ -323,6 +344,29 @@
         }
 
         /**
+         * Sets the {@link ActionStrip} for this template or {@code null} to not display an {@link
+         * ActionStrip}.
+         *
+         * <p>Unless set with this method, the template will not have an action strip.
+         *
+         * <h4>Requirements</h4>
+         *
+         * This template allows up to 2 {@link Action}s in its {@link ActionStrip}. Of the 2 allowed
+         * {@link Action}s, one of them can contain a title as set via
+         * {@link Action.Builder#setTitle}. Otherwise, only {@link Action}s with icons are allowed.
+         *
+         * @throws IllegalArgumentException if {@code actionStrip} does not meet the requirements
+         * @throws NullPointerException     if {@code actionStrip} is {@code null}
+         */
+        @RequiresCarApi(2)
+        @NonNull
+        public Builder setActionStrip(@NonNull ActionStrip actionStrip) {
+            ACTIONS_CONSTRAINTS_SIMPLE.validateOrThrow(requireNonNull(actionStrip).getActions());
+            mActionStrip = actionStrip;
+            return this;
+        }
+
+        /**
          * Adds an {@link Action} to display along with the message.
          *
          * <h4>Requirements</h4>
diff --git a/car/app/app/src/test/java/androidx/car/app/model/MessageTemplateTest.java b/car/app/app/src/test/java/androidx/car/app/model/MessageTemplateTest.java
index 35721a3..01ff47c 100644
--- a/car/app/app/src/test/java/androidx/car/app/model/MessageTemplateTest.java
+++ b/car/app/app/src/test/java/androidx/car/app/model/MessageTemplateTest.java
@@ -47,6 +47,7 @@
     private final String mMessage = "foo";
     private final Action mAction = Action.BACK;
     private final CarIcon mIcon = CarIcon.ALERT;
+    private final ActionStrip mActionStrip = new ActionStrip.Builder().addAction(mAction).build();
 
     @Test
     public void emptyMessage_throws() {
@@ -101,6 +102,7 @@
         assertThat(template.getIcon()).isNull();
         assertThat(template.getHeaderAction()).isNull();
         assertThat(template.getActions()).isEmpty();
+        assertThat(template.getActionStrip()).isNull();
         assertThat(template.getDebugMessage()).isNull();
     }
 
@@ -131,6 +133,7 @@
                         .setDebugMessage(exception)
                         .setIcon(icon)
                         .addAction(action)
+                        .setActionStrip(mActionStrip)
                         .build();
 
         assertThat(template.getMessage().toString()).isEqualTo(mMessage);
@@ -140,6 +143,7 @@
         assertThat(template.getIcon()).isEqualTo(icon);
         assertThat(template.getHeaderAction()).isEqualTo(Action.BACK);
         assertThat(template.getActions()).containsExactly(action);
+        assertThat(template.getActionStrip()).isEqualTo(mActionStrip);
     }
 
     @Test
@@ -170,6 +174,7 @@
                         .setDebugMessage(mCause)
                         .setHeaderAction(Action.BACK)
                         .addAction(mAction)
+                        .setActionStrip(mActionStrip)
                         .setIcon(mIcon)
                         .build();
         MessageTemplate template2 =
@@ -179,6 +184,7 @@
                         .setDebugMessage(mCause)
                         .setHeaderAction(Action.BACK)
                         .addAction(mAction)
+                        .setActionStrip(mActionStrip)
                         .setIcon(mIcon)
                         .build();
 
@@ -193,6 +199,7 @@
                         .setDebugMessage(mDebugMessage)
                         .setDebugMessage(mCause)
                         .addAction(mAction)
+                        .setActionStrip(mActionStrip)
                         .setIcon(mIcon)
                         .build();
         MessageTemplate template2 =
@@ -201,6 +208,7 @@
                         .setDebugMessage("yo")
                         .setDebugMessage(mCause)
                         .addAction(mAction)
+                        .setActionStrip(mActionStrip)
                         .setIcon(mIcon)
                         .build();
 
@@ -215,6 +223,7 @@
                         .setDebugMessage(mDebugMessage)
                         .setDebugMessage(mCause)
                         .addAction(mAction)
+                        .setActionStrip(mActionStrip)
                         .setIcon(mIcon)
                         .build();
         MessageTemplate template2 =
@@ -223,6 +232,7 @@
                         .setDebugMessage(mDebugMessage)
                         .setDebugMessage(new IllegalStateException("something else bad"))
                         .addAction(mAction)
+                        .setActionStrip(mActionStrip)
                         .setIcon(mIcon)
                         .build();
 
@@ -237,6 +247,7 @@
                         .setDebugMessage(mDebugMessage)
                         .setDebugMessage(mCause)
                         .addAction(mAction)
+                        .setActionStrip(mActionStrip)
                         .setIcon(mIcon)
                         .build();
         MessageTemplate template2 =
@@ -245,6 +256,7 @@
                         .setDebugMessage(mDebugMessage)
                         .setDebugMessage(mCause)
                         .addAction(mAction)
+                        .setActionStrip(mActionStrip)
                         .setIcon(mIcon)
                         .build();
 
@@ -260,6 +272,7 @@
                         .setDebugMessage(mCause)
                         .setHeaderAction(Action.BACK)
                         .addAction(mAction)
+                        .setActionStrip(mActionStrip)
                         .setIcon(mIcon)
                         .build();
         MessageTemplate template2 =
@@ -269,6 +282,7 @@
                         .setDebugMessage(mCause)
                         .setHeaderAction(Action.APP_ICON)
                         .addAction(mAction)
+                        .setActionStrip(mActionStrip)
                         .setIcon(mIcon)
                         .build();
 
@@ -283,6 +297,7 @@
                         .setDebugMessage(mDebugMessage)
                         .setDebugMessage(mCause)
                         .addAction(mAction)
+                        .setActionStrip(mActionStrip)
                         .setIcon(mIcon)
                         .build();
         MessageTemplate template2 =
@@ -292,6 +307,34 @@
                         .setDebugMessage(mCause)
                         .addAction(mAction)
                         .addAction(mAction)
+                        .setActionStrip(mActionStrip)
+                        .setIcon(mIcon)
+                        .build();
+
+        assertThat(template1).isNotEqualTo(template2);
+    }
+
+    @Test
+    public void notEquals_differentActionStrip() {
+        MessageTemplate template1 =
+                new MessageTemplate.Builder(mMessage)
+                        .setTitle(mTitle)
+                        .setDebugMessage(mDebugMessage)
+                        .setDebugMessage(mCause)
+                        .addAction(mAction)
+                        .setActionStrip(mActionStrip)
+                        .setIcon(mIcon)
+                        .build();
+        MessageTemplate template2 =
+                new MessageTemplate.Builder(mMessage)
+                        .setTitle(mTitle)
+                        .setDebugMessage(mDebugMessage)
+                        .setDebugMessage(mCause)
+                        .addAction(mAction)
+                        .setActionStrip(new ActionStrip.Builder()
+                                .addAction(Action.BACK)
+                                .addAction(Action.APP_ICON)
+                                .build())
                         .setIcon(mIcon)
                         .build();
 
@@ -306,6 +349,7 @@
                         .setDebugMessage(mDebugMessage)
                         .setDebugMessage(mCause)
                         .addAction(mAction)
+                        .setActionStrip(mActionStrip)
                         .setIcon(mIcon)
                         .build();
         MessageTemplate template2 =
@@ -314,6 +358,7 @@
                         .setDebugMessage(mDebugMessage)
                         .setDebugMessage(mCause)
                         .addAction(mAction)
+                        .setActionStrip(mActionStrip)
                         .setIcon(CarIcon.ERROR)
                         .build();
 
@@ -328,6 +373,7 @@
                         .setDebugMessage(mDebugMessage)
                         .setDebugMessage(mCause)
                         .addAction(mAction)
+                        .setActionStrip(mActionStrip)
                         .setIcon(mIcon)
                         .build();
         MessageTemplate template2 =
@@ -336,6 +382,7 @@
                         .setDebugMessage(mDebugMessage)
                         .setDebugMessage(mCause)
                         .addAction(mAction)
+                        .setActionStrip(mActionStrip)
                         .setIcon(mIcon)
                         .build();
 
diff --git a/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/ControlFlowTransformTests.kt b/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/ControlFlowTransformTests.kt
index 943df22..159fae8 100644
--- a/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/ControlFlowTransformTests.kt
+++ b/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/ControlFlowTransformTests.kt
@@ -3845,4 +3845,33 @@
             fun Text(text: String) { }
         """
     )
+
+    @Test
+    fun testReadOnlyComposableWithEarlyReturn() = controlFlow(
+        source = """
+            import androidx.compose.runtime.ReadOnlyComposable
+
+            @ReadOnlyComposable
+            @Composable
+            fun getSomeValue(a: Int): Int {
+                if (a < 100) return 0
+                return 1
+            }
+        """,
+        """
+            @ReadOnlyComposable
+            @Composable
+            fun getSomeValue(a: Int, %composer: Composer?, %changed: Int): Int {
+              sourceInformationMarkerStart(%composer, <>, "C(getSomeValue):Test.kt")
+              if (a < 100) {
+                val tmp1_return = 0
+                sourceInformationMarkerEnd(%composer)
+                return tmp1_return
+              }
+              val tmp0 = 1
+              sourceInformationMarkerEnd(%composer)
+              return tmp0
+            }
+        """
+    )
 }
diff --git a/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/ComposableFunctionBodyTransformer.kt b/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/ComposableFunctionBodyTransformer.kt
index 4164418..5ef38c8 100644
--- a/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/ComposableFunctionBodyTransformer.kt
+++ b/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/ComposableFunctionBodyTransformer.kt
@@ -846,6 +846,15 @@
                 returnVar?.let { irReturn(declaration.symbol, irGet(it)) }
             )
         )
+        if (
+            elideGroups &&
+            collectSourceInformation &&
+            !declaration.descriptor.hasExplicitGroupsAnnotation()
+        ) {
+            scope.realizeEndCalls {
+                irSourceInformationMarkerEnd(body)
+            }
+        }
 
         return declaration
     }
@@ -3572,7 +3581,7 @@
                 }
             }
 
-            protected open fun realizeEndCalls(makeEnd: () -> IrExpression) {
+            open fun realizeEndCalls(makeEnd: () -> IrExpression) {
                 extraEndLocations.forEach {
                     it(makeEnd())
                 }
diff --git a/compose/desktop/desktop/samples/src/jvmMain/kotlin/androidx/compose/desktop/examples/example1/Main.jvm.kt b/compose/desktop/desktop/samples/src/jvmMain/kotlin/androidx/compose/desktop/examples/example1/Main.jvm.kt
index 0d6099f..55405aca 100644
--- a/compose/desktop/desktop/samples/src/jvmMain/kotlin/androidx/compose/desktop/examples/example1/Main.jvm.kt
+++ b/compose/desktop/desktop/samples/src/jvmMain/kotlin/androidx/compose/desktop/examples/example1/Main.jvm.kt
@@ -498,16 +498,16 @@
 private fun RightColumn(modifier: Modifier) = Box {
     val state = rememberLazyListState()
     val itemCount = 100000
-    val itemHeight = 20.dp
 
     LazyColumn(modifier.graphicsLayer(alpha = 0.5f), state = state) {
         items((1..itemCount).toList()) { x ->
+            val itemHeight = 20.dp + 20.dp * Math.random().toFloat()
             Text(x.toString(), Modifier.graphicsLayer(alpha = 0.5f).height(itemHeight))
         }
     }
 
     VerticalScrollbar(
-        rememberScrollbarAdapter(state, itemCount, itemHeight),
+        rememberScrollbarAdapter(state),
         Modifier.align(Alignment.CenterEnd)
     )
 }
diff --git a/compose/foundation/foundation/src/desktopMain/kotlin/androidx/compose/foundation/Scrollbar.desktop.kt b/compose/foundation/foundation/src/desktopMain/kotlin/androidx/compose/foundation/Scrollbar.desktop.kt
index 6dd2571..926af66 100644
--- a/compose/foundation/foundation/src/desktopMain/kotlin/androidx/compose/foundation/Scrollbar.desktop.kt
+++ b/compose/foundation/foundation/src/desktopMain/kotlin/androidx/compose/foundation/Scrollbar.desktop.kt
@@ -22,6 +22,7 @@
 import androidx.compose.foundation.gestures.detectTapAndPress
 import androidx.compose.foundation.gestures.drag
 import androidx.compose.foundation.gestures.forEachGesture
+import androidx.compose.foundation.gestures.scrollBy
 import androidx.compose.foundation.interaction.DragInteraction
 import androidx.compose.foundation.interaction.MutableInteractionSource
 import androidx.compose.foundation.layout.Box
@@ -59,6 +60,7 @@
 import androidx.compose.ui.unit.dp
 import kotlinx.coroutines.delay
 import kotlinx.coroutines.runBlocking
+import kotlin.math.abs
 import kotlin.math.roundToInt
 import kotlin.math.sign
 
@@ -117,6 +119,10 @@
  *
  * @param adapter [ScrollbarAdapter] that will be used to communicate with scrollable component
  * @param modifier the modifier to apply to this layout
+ * @param reverseLayout reverse the direction of scrolling and layout, when `true`
+ * and [LazyListState.firstVisibleItemIndex] == 0 then scrollbar
+ * will be at the bottom of the container.
+ * It is usually used in pair with `LazyColumn(reverseLayout = true)`
  * @param style [ScrollbarStyle] to define visual style of scrollbar
  * @param interactionSource [MutableInteractionSource] that will be used to dispatch
  * [DragInteraction.Start] when this Scrollbar is being dragged.
@@ -125,11 +131,13 @@
 fun VerticalScrollbar(
     adapter: ScrollbarAdapter,
     modifier: Modifier = Modifier,
+    reverseLayout: Boolean = false,
     style: ScrollbarStyle = LocalScrollbarStyle.current,
     interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }
 ) = Scrollbar(
     adapter,
     modifier,
+    reverseLayout,
     style,
     interactionSource,
     isVertical = true
@@ -157,6 +165,10 @@
  *
  * @param adapter [ScrollbarAdapter] that will be used to communicate with scrollable component
  * @param modifier the modifier to apply to this layout
+ * @param reverseLayout reverse the direction of scrolling and layout, when `true`
+ * and [LazyListState.firstVisibleItemIndex] == 0 then scrollbar
+ * will be at the end of the container.
+ * It is usually used in pair with `LazyRow(reverseLayout = true)`
  * @param style [ScrollbarStyle] to define visual style of scrollbar
  * @param interactionSource [MutableInteractionSource] that will be used to dispatch
  * [DragInteraction.Start] when this Scrollbar is being dragged.
@@ -165,11 +177,13 @@
 fun HorizontalScrollbar(
     adapter: ScrollbarAdapter,
     modifier: Modifier = Modifier,
+    reverseLayout: Boolean = false,
     style: ScrollbarStyle = LocalScrollbarStyle.current,
     interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }
 ) = Scrollbar(
     adapter,
     modifier,
+    reverseLayout,
     style,
     interactionSource,
     isVertical = false
@@ -181,6 +195,7 @@
 private fun Scrollbar(
     adapter: ScrollbarAdapter,
     modifier: Modifier = Modifier,
+    reverseLayout: Boolean,
     style: ScrollbarStyle,
     interactionSource: MutableInteractionSource,
     isVertical: Boolean
@@ -205,8 +220,8 @@
     }
 
     val minimalHeight = style.minimalHeight.toPx()
-    val sliderAdapter = remember(adapter, containerSize, minimalHeight) {
-        SliderAdapter(adapter, containerSize, minimalHeight)
+    val sliderAdapter = remember(adapter, containerSize, minimalHeight, reverseLayout) {
+        SliderAdapter(adapter, containerSize, minimalHeight, reverseLayout)
     }
 
     val scrollThickness = style.thickness.roundToPx()
@@ -335,18 +350,34 @@
  * Create and [remember] [ScrollbarAdapter] for lazy scrollable container and current instance of
  * [scrollState] and item configuration
  */
+@Suppress("UNUSED_PARAMETER")
 @ExperimentalFoundationApi
 @Composable
+@Deprecated(
+    "itemCount and averageItemSize are calculated automatically. Use " +
+        "another overload rememberScrollbarAdapter without passing them",
+    ReplaceWith("rememberScrollbarAdapter(scrollState)")
+)
 fun rememberScrollbarAdapter(
     scrollState: LazyListState,
     itemCount: Int,
     averageItemSize: Dp
 ): ScrollbarAdapter {
-    val averageItemSizePx = with(LocalDensity.current) {
-        averageItemSize.toPx()
+    return remember(scrollState) {
+        ScrollbarAdapter(scrollState)
     }
-    return remember(scrollState, itemCount, averageItemSizePx) {
-        ScrollbarAdapter(scrollState, itemCount, averageItemSizePx)
+}
+
+/**
+ * Create and [remember] [ScrollbarAdapter] for lazy scrollable container and current instance of
+ * [scrollState]
+ */
+@Composable
+fun rememberScrollbarAdapter(
+    scrollState: LazyListState,
+): ScrollbarAdapter {
+    return remember(scrollState) {
+        ScrollbarAdapter(scrollState)
     }
 }
 
@@ -386,22 +417,16 @@
         scrollState.maxValue.toFloat()
 }
 
-// TODO(demin): if item height is different then slider will have wrong
-//  position when we dragging it (we can drag it to the beginning, but content will not be at the
-//  beginning). We can implement adaptive scrollbar height after b/170472532
-
 /**
- * Experimental ScrollbarAdapter for lazy lists. Doesn't work stable with non-fixed item height.
+ * ScrollbarAdapter for lazy lists.
  *
  * [scrollState] is instance of [LazyListState] which is used by scrollable component
  *
- * Scrollbar size and position will be calculated by passed [itemCount] and [averageItemSize]
+ * Scrollbar size and position will be dynamically changed on the current visible content.
  *
  * Example:
  *     Box(Modifier.fillMaxSize()) {
  *         val state = rememberLazyListState()
- *         val itemCount = 100
- *         val itemHeight = 20.dp
  *
  *         LazyColumn(state = state) {
  *             ...
@@ -409,34 +434,39 @@
  *
  *         VerticalScrollbar(
  *             Modifier.align(Alignment.CenterEnd),
- *             rememberScrollbarAdapter(state, itemCount, itemHeight)
+ *             rememberScrollbarAdapter(state)
  *         )
  *     }
  */
-@ExperimentalFoundationApi
 fun ScrollbarAdapter(
-    scrollState: LazyListState,
-    itemCount: Int,
-    averageItemSize: Float
+    scrollState: LazyListState
 ): ScrollbarAdapter = LazyScrollbarAdapter(
-    scrollState, itemCount, averageItemSize
+    scrollState
 )
 
 private class LazyScrollbarAdapter(
-    private val scrollState: LazyListState,
-    private val itemCount: Int,
-    private val averageItemSize: Float
+    private val scrollState: LazyListState
 ) : ScrollbarAdapter {
-    init {
-        require(itemCount >= 0f) { "itemCount should be non-negative ($itemCount)" }
-        require(averageItemSize > 0f) { "averageItemSize should be positive ($averageItemSize)" }
-    }
-
     override val scrollOffset: Float
         get() = scrollState.firstVisibleItemIndex * averageItemSize +
             scrollState.firstVisibleItemScrollOffset
 
     override suspend fun scrollTo(containerSize: Int, scrollOffset: Float) {
+        val distance = scrollOffset - [email protected]
+
+        // if we scroll less than containerSize we need to use scrollBy function to avoid
+        // undesirable scroll jumps (when an item size is different)
+        //
+        // if we scroll more than containerSize we should immediately jump to this position
+        // without recreating all items between the current and the new position
+        if (abs(distance) <= containerSize) {
+            scrollState.scrollBy(distance)
+        } else {
+            snapTo(containerSize, scrollOffset)
+        }
+    }
+
+    private suspend fun snapTo(containerSize: Int, scrollOffset: Float) {
         // In case of very big values, we can catch an overflow, so convert values to double and
         // coerce them
 //        val averageItemSize = 26.000002f
@@ -463,6 +493,18 @@
 
     override fun maxScrollOffset(containerSize: Int) =
         averageItemSize * itemCount - containerSize
+
+    private val itemCount get() = scrollState.layoutInfo.totalItemsCount
+
+    private val averageItemSize by derivedStateOf {
+        scrollState
+            .layoutInfo
+            .visibleItemsInfo
+            .asSequence()
+            .map { it.size }
+            .average()
+            .toFloat()
+    }
 }
 
 /**
@@ -497,7 +539,8 @@
 private class SliderAdapter(
     val adapter: ScrollbarAdapter,
     val containerSize: Int,
-    val minHeight: Float
+    val minHeight: Float,
+    val reverseLayout: Boolean
 ) {
     private val contentSize get() = adapter.maxScrollOffset(containerSize) + containerSize
     private val visiblePart get() = containerSize.toFloat() / contentSize
@@ -514,7 +557,7 @@
             return if (extraContentSpace == 0f) 1f else extraScrollbarSpace / extraContentSpace
         }
 
-    var position: Float
+    private var rawPosition: Float
         get() = scrollScale * adapter.scrollOffset
         set(value) {
             runBlocking {
@@ -522,6 +565,16 @@
             }
         }
 
+    var position: Float
+        get() = if (reverseLayout) containerSize - size - rawPosition else rawPosition
+        set(value) {
+            rawPosition = if (reverseLayout) {
+                containerSize - size - value
+            } else {
+                value
+            }
+        }
+
     val bounds get() = position..position + size
 }
 
diff --git a/compose/foundation/foundation/src/desktopTest/kotlin/androidx/compose/foundation/ScrollbarTest.kt b/compose/foundation/foundation/src/desktopTest/kotlin/androidx/compose/foundation/ScrollbarTest.kt
index 4b1be7e..02ac894 100644
--- a/compose/foundation/foundation/src/desktopTest/kotlin/androidx/compose/foundation/ScrollbarTest.kt
+++ b/compose/foundation/foundation/src/desktopTest/kotlin/androidx/compose/foundation/ScrollbarTest.kt
@@ -314,6 +314,35 @@
     @Suppress("SameParameterValue")
     @OptIn(ExperimentalFoundationApi::class)
     @Test(timeout = 3000)
+    fun `scroll in reversed lazy list`() {
+        runBlocking(Dispatchers.Main) {
+            lateinit var state: LazyListState
+
+            rule.setContent {
+                state = rememberLazyListState()
+                LazyTestBox(
+                    state,
+                    size = 100.dp,
+                    childSize = 20.dp,
+                    childCount = 20,
+                    scrollbarWidth = 10.dp,
+                    reverseLayout = true
+                )
+            }
+            rule.awaitIdle()
+
+            rule.onNodeWithTag("scrollbar").performGesture {
+                instantSwipe(start = Offset(0f, 99f), end = Offset(0f, 88f))
+            }
+            rule.awaitIdle()
+            assertEquals(2, state.firstVisibleItemIndex)
+            assertEquals(4, state.firstVisibleItemScrollOffset)
+        }
+    }
+
+    @Suppress("SameParameterValue")
+    @OptIn(ExperimentalFoundationApi::class)
+    @Test(timeout = 3000)
     fun `scroll by more than one page in lazy list`() {
         runBlocking(Dispatchers.Main) {
             lateinit var state: LazyListState
@@ -456,11 +485,13 @@
         childSize: Dp,
         childCount: Int,
         scrollbarWidth: Dp,
+        reverseLayout: Boolean = false
     ) = withTestEnvironment {
         Box(Modifier.size(size)) {
             LazyColumn(
                 Modifier.fillMaxSize().testTag("column"),
-                state
+                state,
+                reverseLayout = reverseLayout
             ) {
                 items((0 until childCount).toList()) {
                     Box(Modifier.size(childSize).testTag("box$it"))
@@ -468,7 +499,8 @@
             }
 
             VerticalScrollbar(
-                adapter = rememberScrollbarAdapter(state, childCount, childSize),
+                adapter = rememberScrollbarAdapter(state),
+                reverseLayout = reverseLayout,
                 modifier = Modifier
                     .width(scrollbarWidth)
                     .fillMaxHeight()
diff --git a/compose/integration-tests/demos/src/main/AndroidManifest.xml b/compose/integration-tests/demos/src/main/AndroidManifest.xml
index 6c31162..12e95e6 100644
--- a/compose/integration-tests/demos/src/main/AndroidManifest.xml
+++ b/compose/integration-tests/demos/src/main/AndroidManifest.xml
@@ -38,7 +38,5 @@
             android:name=".DemoSettingsActivity"
             android:label="Demo Theme Settings"
             android:theme="@style/Theme.AppCompat.Light.DarkActionBar" />
-
-        <meta-data android:value="true" android:name="COMPOSE_TEXT_FIELD_UNDO_ENABLED"/>
     </application>
 </manifest>
diff --git a/compose/material/material-ripple/src/androidMain/kotlin/androidx/compose/material/ripple/RippleContainer.android.kt b/compose/material/material-ripple/src/androidMain/kotlin/androidx/compose/material/ripple/RippleContainer.android.kt
index 840f932..6571a8c 100644
--- a/compose/material/material-ripple/src/androidMain/kotlin/androidx/compose/material/ripple/RippleContainer.android.kt
+++ b/compose/material/material-ripple/src/androidMain/kotlin/androidx/compose/material/ripple/RippleContainer.android.kt
@@ -18,6 +18,7 @@
 
 import android.content.Context
 import android.view.ViewGroup
+import androidx.compose.ui.R
 
 /**
  * A root-level container [ViewGroup] that manages creating and assigning [RippleHostView]s used
@@ -64,6 +65,9 @@
         // Since we now have an unused ripple host, the next index should be 1 - the unused host
         // will be used first.
         nextHostIndex = 1
+
+        // Hide this view and its children in tools:
+        setTag(R.id.hide_in_inspector_tag, true)
     }
 
     override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
diff --git a/compose/ui/ui-graphics/src/desktopMain/kotlin/androidx/compose/ui/graphics/DesktopCanvas.desktop.kt b/compose/ui/ui-graphics/src/desktopMain/kotlin/androidx/compose/ui/graphics/DesktopCanvas.desktop.kt
index c2539ea..6b80b7c 100644
--- a/compose/ui/ui-graphics/src/desktopMain/kotlin/androidx/compose/ui/graphics/DesktopCanvas.desktop.kt
+++ b/compose/ui/ui-graphics/src/desktopMain/kotlin/androidx/compose/ui/graphics/DesktopCanvas.desktop.kt
@@ -19,11 +19,17 @@
 import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.geometry.Rect
 import androidx.compose.ui.geometry.RoundRect
+import androidx.compose.ui.geometry.Size
 import androidx.compose.ui.unit.IntOffset
 import androidx.compose.ui.unit.IntSize
 import androidx.compose.ui.util.fastForEach
+import org.jetbrains.skija.CubicResampler
+import org.jetbrains.skija.FilterMipmap
+import org.jetbrains.skija.FilterMode
 import org.jetbrains.skija.Image
 import org.jetbrains.skija.Matrix44
+import org.jetbrains.skija.MipmapMode
+import org.jetbrains.skija.SamplingMode
 import org.jetbrains.skija.impl.Native
 import org.jetbrains.skija.ClipMode as SkijaClipMode
 import org.jetbrains.skija.RRect as SkijaRRect
@@ -164,14 +170,8 @@
     }
 
     override fun drawImage(image: ImageBitmap, topLeftOffset: Offset, paint: Paint) {
-        val bitmap = image.asDesktopBitmap()
-        Image.makeFromBitmap(bitmap).use { skijaImage ->
-            skija.drawImage(
-                skijaImage,
-                topLeftOffset.x, topLeftOffset.y,
-                paint.skija
-            )
-        }
+        val size = Size(image.width.toFloat(), image.height.toFloat())
+        drawImageRect(image, Offset.Zero, size, topLeftOffset, size, paint)
     }
 
     override fun drawImageRect(
@@ -182,23 +182,44 @@
         dstSize: IntSize,
         paint: Paint
     ) {
+        drawImageRect(
+            image,
+            Offset(srcOffset.x.toFloat(), srcOffset.y.toFloat()),
+            Size(srcSize.width.toFloat(), srcSize.height.toFloat()),
+            Offset(dstOffset.x.toFloat(), dstOffset.y.toFloat()),
+            Size(dstSize.width.toFloat(), dstSize.height.toFloat()),
+            paint
+        )
+    }
+
+    // TODO(demin): probably this method should be in the common Canvas
+    private fun drawImageRect(
+        image: ImageBitmap,
+        srcOffset: Offset,
+        srcSize: Size,
+        dstOffset: Offset,
+        dstSize: Size,
+        paint: Paint
+    ) {
         val bitmap = image.asDesktopBitmap()
         Image.makeFromBitmap(bitmap).use { skijaImage ->
             skija.drawImageRect(
                 skijaImage,
                 SkijaRect.makeXYWH(
-                    srcOffset.x.toFloat(),
-                    srcOffset.y.toFloat(),
-                    srcSize.width.toFloat(),
-                    srcSize.height.toFloat()
+                    srcOffset.x,
+                    srcOffset.y,
+                    srcSize.width,
+                    srcSize.height
                 ),
                 SkijaRect.makeXYWH(
-                    dstOffset.x.toFloat(),
-                    dstOffset.y.toFloat(),
-                    dstSize.width.toFloat(),
-                    dstSize.height.toFloat()
+                    dstOffset.x,
+                    dstOffset.y,
+                    dstSize.width,
+                    dstSize.height
                 ),
-                paint.skija
+                paint.filterQuality.toSkija(),
+                paint.skija,
+                true
             )
         }
     }
@@ -356,4 +377,14 @@
         this[2, 3],
         this[3, 3]
     )
+
+    // These constants are chosen to correspond the old implementation of SkFilterQuality:
+    // https://github.com/google/skia/blob/1f193df9b393d50da39570dab77a0bb5d28ec8ef/src/image/SkImage.cpp#L809
+    // https://github.com/google/skia/blob/1f193df9b393d50da39570dab77a0bb5d28ec8ef/include/core/SkSamplingOptions.h#L86
+    private fun FilterQuality.toSkija(): SamplingMode = when (this) {
+        FilterQuality.Low -> FilterMipmap(FilterMode.LINEAR, MipmapMode.NONE)
+        FilterQuality.Medium -> FilterMipmap(FilterMode.LINEAR, MipmapMode.NEAREST)
+        FilterQuality.High -> CubicResampler(1 / 3.0f, 1 / 3.0f)
+        else -> FilterMipmap(FilterMode.NEAREST, MipmapMode.NONE)
+    }
 }
\ No newline at end of file
diff --git a/compose/ui/ui-graphics/src/desktopMain/kotlin/androidx/compose/ui/graphics/DesktopPaint.desktop.kt b/compose/ui/ui-graphics/src/desktopMain/kotlin/androidx/compose/ui/graphics/DesktopPaint.desktop.kt
index 9148102d..3242cf1 100644
--- a/compose/ui/ui-graphics/src/desktopMain/kotlin/androidx/compose/ui/graphics/DesktopPaint.desktop.kt
+++ b/compose/ui/ui-graphics/src/desktopMain/kotlin/androidx/compose/ui/graphics/DesktopPaint.desktop.kt
@@ -16,7 +16,6 @@
 
 package androidx.compose.ui.graphics
 
-import org.jetbrains.skija.FilterQuality as SkijaFilterQuality
 import org.jetbrains.skija.PaintMode as SkijaPaintMode
 import org.jetbrains.skija.PaintStrokeCap as SkijaPaintStrokeCap
 import org.jetbrains.skija.PaintStrokeJoin as SkijaPaintStrokeJoin
@@ -28,10 +27,6 @@
 class DesktopPaint : Paint {
     internal val skija = org.jetbrains.skija.Paint()
 
-    constructor() {
-        filterQuality = FilterQuality.Medium
-    }
-
     override fun asFrameworkPaint(): NativePaint = skija
 
     override var alpha: Float
@@ -88,11 +83,7 @@
             field = value
         }
 
-    override var filterQuality: FilterQuality = FilterQuality.None
-        set(value) {
-            skija.filterQuality = value.toSkija()
-            field = value
-        }
+    override var filterQuality: FilterQuality = FilterQuality.Medium
 
     override var shader: Shader? = null
         set(value) {
@@ -131,14 +122,6 @@
         StrokeJoin.Bevel -> SkijaPaintStrokeJoin.BEVEL
         else -> SkijaPaintStrokeJoin.MITER
     }
-
-    private fun FilterQuality.toSkija() = when (this) {
-        FilterQuality.None -> SkijaFilterQuality.NONE
-        FilterQuality.Low -> SkijaFilterQuality.LOW
-        FilterQuality.Medium -> SkijaFilterQuality.MEDIUM
-        FilterQuality.High -> SkijaFilterQuality.HIGH
-        else -> SkijaFilterQuality.NONE
-    }
 }
 
 actual fun BlendMode.isSupported(): Boolean = true
\ No newline at end of file
diff --git a/compose/ui/ui-graphics/src/desktopTest/kotlin/androidx/compose/ui/graphics/DesktopPaintTest.kt b/compose/ui/ui-graphics/src/desktopTest/kotlin/androidx/compose/ui/graphics/DesktopPaintTest.kt
index aa73daa2..462a5ba 100644
--- a/compose/ui/ui-graphics/src/desktopTest/kotlin/androidx/compose/ui/graphics/DesktopPaintTest.kt
+++ b/compose/ui/ui-graphics/src/desktopTest/kotlin/androidx/compose/ui/graphics/DesktopPaintTest.kt
@@ -17,7 +17,6 @@
 package androidx.compose.ui.graphics
 
 import androidx.compose.ui.geometry.Offset
-import androidx.compose.ui.platform.DesktopPlatform
 import androidx.compose.ui.unit.IntOffset
 import androidx.compose.ui.unit.IntSize
 import org.junit.Assert.assertEquals
@@ -90,7 +89,7 @@
 
     @Test
     fun filterQuality() {
-        assumeTrue(DesktopPlatform.Current == DesktopPlatform.MacOS)
+        assumeTrue(isWindows || isLinux)
 
         canvas.drawImageRect(
             image = imageFromResource("androidx/compose/desktop/test.png"),
@@ -127,6 +126,46 @@
     }
 
     @Test
+    fun `filterQuality with scaled Canvas`() {
+        assumeTrue(isWindows || isLinux)
+
+        canvas.scale(2f, 2f)
+
+        canvas.drawImageRect(
+            image = imageFromResource("androidx/compose/desktop/test.png"),
+            srcOffset = IntOffset(0, 2),
+            srcSize = IntSize(2, 4),
+            dstOffset = IntOffset(0, 2),
+            dstSize = IntSize(2, 6),
+            paint = redPaint.apply {
+                filterQuality = FilterQuality.None
+            }
+        )
+        canvas.drawImageRect(
+            image = imageFromResource("androidx/compose/desktop/test.png"),
+            srcOffset = IntOffset(0, 2),
+            srcSize = IntSize(2, 4),
+            dstOffset = IntOffset(2, 2),
+            dstSize = IntSize(2, 6),
+            paint = redPaint.apply {
+                filterQuality = FilterQuality.Low
+            }
+        )
+        canvas.drawImageRect(
+            image = imageFromResource("androidx/compose/desktop/test.png"),
+            srcOffset = IntOffset(0, 2),
+            srcSize = IntSize(2, 4),
+            dstOffset = IntOffset(4, 2),
+            dstSize = IntSize(2, 6),
+            paint = redPaint.apply {
+                filterQuality = FilterQuality.High
+            }
+        )
+
+        screenshotRule.snap(surface)
+    }
+
+    @Test
     fun linearGradientShader() {
         canvas.drawRect(left = 0f, top = 0f, right = 16f, bottom = 16f, paint = redPaint)
 
diff --git a/compose/ui/ui-graphics/src/desktopTest/kotlin/androidx/compose/ui/graphics/TestUtils.kt b/compose/ui/ui-graphics/src/desktopTest/kotlin/androidx/compose/ui/graphics/TestUtils.kt
new file mode 100644
index 0000000..c98c362
--- /dev/null
+++ b/compose/ui/ui-graphics/src/desktopTest/kotlin/androidx/compose/ui/graphics/TestUtils.kt
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.ui.graphics
+
+private val os = System.getProperty("os.name").lowercase()
+internal val isLinux = os.startsWith("linux")
+internal val isWindows = os.startsWith("win")
+internal val isMacOs = os.startsWith("mac")
\ No newline at end of file
diff --git a/compose/ui/ui-inspection/src/androidTest/AndroidManifest.xml b/compose/ui/ui-inspection/src/androidTest/AndroidManifest.xml
index 58b8a25..bc217cf 100644
--- a/compose/ui/ui-inspection/src/androidTest/AndroidManifest.xml
+++ b/compose/ui/ui-inspection/src/androidTest/AndroidManifest.xml
@@ -19,6 +19,7 @@
         <activity android:name="androidx.compose.ui.inspection.testdata.AndroidViewTestActivity" />
         <activity android:name="androidx.compose.ui.inspection.testdata.DialogTestActivity" />
         <activity android:name="androidx.compose.ui.inspection.testdata.ParametersTestActivity" />
+        <activity android:name="androidx.compose.ui.inspection.testdata.RippleTestActivity" />
         <activity android:name="androidx.compose.ui.inspection.testdata.TestActivity"
             android:theme="@style/TestTheme"/>
     </application>
diff --git a/compose/ui/ui-inspection/src/androidTest/java/androidx/compose/ui/inspection/RippleTest.kt b/compose/ui/ui-inspection/src/androidTest/java/androidx/compose/ui/inspection/RippleTest.kt
new file mode 100644
index 0000000..34efc1c
--- /dev/null
+++ b/compose/ui/ui-inspection/src/androidTest/java/androidx/compose/ui/inspection/RippleTest.kt
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.ui.inspection
+
+import android.view.ViewGroup
+import androidx.compose.ui.inspection.framework.getChildren
+import androidx.compose.ui.inspection.rules.ComposeInspectionRule
+import androidx.compose.ui.inspection.rules.sendCommand
+import androidx.compose.ui.inspection.testdata.RippleTestActivity
+import androidx.compose.ui.inspection.util.GetComposablesCommand
+import androidx.compose.ui.inspection.util.ThreadUtils
+import androidx.test.filters.LargeTest
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.runBlocking
+import org.junit.Rule
+import org.junit.Test
+
+@LargeTest
+class RippleTest {
+    @get:Rule
+    val rule = ComposeInspectionRule(RippleTestActivity::class)
+
+    @Test
+    fun rippleViewsAreMarked(): Unit = runBlocking {
+        val app = rule.inspectorTester.sendCommand(GetComposablesCommand(rule.rootId))
+            .getComposablesResponse
+        val composeViewChildren = ThreadUtils.runOnMainThread {
+            val composeView = rule.rootsForTest.single() as ViewGroup
+            composeView.getChildren().map { it.uniqueDrawingId }
+        }.get()
+        val toSkip = app.rootsList.single().viewsToSkipList
+        assertThat(composeViewChildren).containsExactlyElementsIn(toSkip)
+        assertThat(toSkip).hasSize(1)
+    }
+}
diff --git a/compose/ui/ui-inspection/src/androidTest/java/androidx/compose/ui/inspection/testdata/RippleTestActivity.kt b/compose/ui/ui-inspection/src/androidTest/java/androidx/compose/ui/inspection/testdata/RippleTestActivity.kt
new file mode 100644
index 0000000..04f415b
--- /dev/null
+++ b/compose/ui/ui-inspection/src/androidTest/java/androidx/compose/ui/inspection/testdata/RippleTestActivity.kt
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.ui.inspection.testdata
+
+import android.os.Bundle
+import androidx.activity.ComponentActivity
+import androidx.activity.compose.setContent
+import androidx.compose.foundation.LocalIndication
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.indication
+import androidx.compose.foundation.interaction.MutableInteractionSource
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.requiredHeight
+import androidx.compose.material.Text
+import androidx.compose.material.ripple.rememberRipple
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.unit.dp
+
+class RippleTestActivity : ComponentActivity() {
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+        setContent {
+            Column {
+                val interactionSource = remember { MutableInteractionSource() }
+                Text(
+                    text = "Click me with indication",
+                    modifier = Modifier
+                        .clickable(
+                            interactionSource = interactionSource,
+                            indication = rememberRipple()
+                        ) { /* do something */ }
+                        .padding(10.dp)
+                )
+                Spacer(Modifier.requiredHeight(10.dp))
+                Text(
+                    text = "I show indication with clicking on other Text composable",
+                    modifier = Modifier
+                        .indication(interactionSource, LocalIndication.current)
+                        .padding(10.dp)
+                )
+            }
+        }
+    }
+}
diff --git a/compose/ui/ui-inspection/src/main/java/androidx/compose/ui/inspection/ComposeLayoutInspector.kt b/compose/ui/ui-inspection/src/main/java/androidx/compose/ui/inspection/ComposeLayoutInspector.kt
index 1d02de5..6999d46 100644
--- a/compose/ui/ui-inspection/src/main/java/androidx/compose/ui/inspection/ComposeLayoutInspector.kt
+++ b/compose/ui/ui-inspection/src/main/java/androidx/compose/ui/inspection/ComposeLayoutInspector.kt
@@ -74,6 +74,7 @@
         val rootView: View,
         val viewParent: View,
         val nodes: List<InspectorNode>,
+        val viewsToSkip: List<Long>
     ) {
         /** The cached nodes as a map from node id to InspectorNode */
         val lookup = nodes.flatMap { it.flatten() }.associateBy { it.id }
@@ -149,8 +150,11 @@
                 addAllStrings(stringTable.toStringEntries())
                 addRoots(
                     ComposableRoot.newBuilder().apply {
-                        viewId = data?.viewParent?.uniqueDrawingId ?: 0L
-                        addAllNodes(composeNodes)
+                        if (data != null) {
+                            viewId = data.viewParent.uniqueDrawingId
+                            addAllNodes(composeNodes)
+                            addAllViewsToSkip(data.viewsToSkip)
+                        }
                     }
                 )
             }.build()
@@ -281,7 +285,7 @@
         val data = ThreadUtils.runOnMainThread {
             layoutInspectorTree.resetAccumulativeState()
             val data = getAndroidComposeViews(rootViewId, skipSystemComposables, generation).map {
-                CacheData(it.rootView, it.viewParent, it.createNodes())
+                CacheData(it.rootView, it.viewParent, it.createNodes(), it.viewsToSkip)
             }
             layoutInspectorTree.resetAccumulativeState()
             data
diff --git a/compose/ui/ui-inspection/src/main/java/androidx/compose/ui/inspection/compose/AndroidComposeViewWrapper.kt b/compose/ui/ui-inspection/src/main/java/androidx/compose/ui/inspection/compose/AndroidComposeViewWrapper.kt
index 5b11b69..c6b88ce 100644
--- a/compose/ui/ui-inspection/src/main/java/androidx/compose/ui/inspection/compose/AndroidComposeViewWrapper.kt
+++ b/compose/ui/ui-inspection/src/main/java/androidx/compose/ui/inspection/compose/AndroidComposeViewWrapper.kt
@@ -18,7 +18,10 @@
 
 import android.content.res.Resources
 import android.view.View
+import android.view.ViewGroup
+import androidx.compose.ui.R
 import androidx.compose.ui.inspection.framework.ancestors
+import androidx.compose.ui.inspection.framework.getChildren
 import androidx.compose.ui.inspection.framework.isRoot
 import androidx.compose.ui.inspection.inspector.InspectorNode
 import androidx.compose.ui.inspection.inspector.LayoutInspectorTree
@@ -71,7 +74,7 @@
 class AndroidComposeViewWrapper(
     private val layoutInspectorTree: LayoutInspectorTree,
     val rootView: View,
-    private val composeView: View,
+    private val composeView: ViewGroup,
     skipSystemComposables: Boolean
 ) {
     companion object {
@@ -85,7 +88,7 @@
                 AndroidComposeViewWrapper(
                     layoutInspectorTree,
                     rootView,
-                    composeView,
+                    composeView as ViewGroup,
                     skipSystemComposables
                 )
             } else {
@@ -103,6 +106,11 @@
         if (!skipSystemComposables) composeView
         else composeView.ancestors().first { !it.isSystemView() || it.isRoot() }
 
+    val viewsToSkip: List<Long> =
+        composeView.getChildren()
+            .filter { it.getTag(R.id.hide_in_inspector_tag) != null }
+            .map { it.uniqueDrawingId }
+
     private val inspectorNodes = layoutInspectorTree.apply {
         this.hideSystemNodes = skipSystemComposables
     }.convert(composeView)
diff --git a/compose/ui/ui-inspection/src/main/proto/compose_layout_inspection.proto b/compose/ui/ui-inspection/src/main/proto/compose_layout_inspection.proto
index 2cc5b3f..cbad50f6 100644
--- a/compose/ui/ui-inspection/src/main/proto/compose_layout_inspection.proto
+++ b/compose/ui/ui-inspection/src/main/proto/compose_layout_inspection.proto
@@ -65,6 +65,8 @@
     int64 view_id = 1;
     // All composables owned by this view (usually just one but could be more)
     repeated ComposableNode nodes = 2;
+    // All views owned by this view that should be hidden
+    repeated int64 views_to_skip = 3;
 }
 
 message ComposableNode {
diff --git a/compose/ui/ui-text/src/desktopTest/kotlin/androidx/compose/ui/text/TestUtils.kt b/compose/ui/ui-text/src/desktopTest/kotlin/androidx/compose/ui/text/TestUtils.kt
index c5ad284..e283426 100644
--- a/compose/ui/ui-text/src/desktopTest/kotlin/androidx/compose/ui/text/TestUtils.kt
+++ b/compose/ui/ui-text/src/desktopTest/kotlin/androidx/compose/ui/text/TestUtils.kt
@@ -16,9 +16,7 @@
 
 package androidx.compose.ui.text
 
-import java.util.Locale
-
-private val os = System.getProperty("os.name").lowercase(Locale.US)
+private val os = System.getProperty("os.name").lowercase()
 internal val isLinux = os.startsWith("linux")
 internal val isWindows = os.startsWith("win")
 internal val isMacOs = os.startsWith("mac")
\ No newline at end of file
diff --git a/compose/ui/ui/api/1.0.0-beta08.txt b/compose/ui/ui/api/1.0.0-beta08.txt
index 90caf3a..ef6a13d 100644
--- a/compose/ui/ui/api/1.0.0-beta08.txt
+++ b/compose/ui/ui/api/1.0.0-beta08.txt
@@ -2575,13 +2575,17 @@
   }
 
   @androidx.compose.runtime.Immutable public final class PopupProperties {
-    ctor public PopupProperties(optional boolean focusable, optional boolean dismissOnBackPress, optional boolean dismissOnClickOutside, optional androidx.compose.ui.window.SecureFlagPolicy securePolicy);
+    ctor public PopupProperties(optional boolean focusable, optional boolean dismissOnBackPress, optional boolean dismissOnClickOutside, optional androidx.compose.ui.window.SecureFlagPolicy securePolicy, optional boolean excludeFromSystemGesture, optional boolean clippingEnabled);
+    method public boolean getClippingEnabled();
     method public boolean getDismissOnBackPress();
     method public boolean getDismissOnClickOutside();
+    method public boolean getExcludeFromSystemGesture();
     method public boolean getFocusable();
     method public androidx.compose.ui.window.SecureFlagPolicy getSecurePolicy();
+    property public final boolean clippingEnabled;
     property public final boolean dismissOnBackPress;
     property public final boolean dismissOnClickOutside;
+    property public final boolean excludeFromSystemGesture;
     property public final boolean focusable;
     property public final androidx.compose.ui.window.SecureFlagPolicy securePolicy;
   }
diff --git a/compose/ui/ui/api/current.ignore b/compose/ui/ui/api/current.ignore
index 266ec45..f30e81b 100644
--- a/compose/ui/ui/api/current.ignore
+++ b/compose/ui/ui/api/current.ignore
@@ -109,3 +109,5 @@
     Removed method androidx.compose.ui.input.pointer.PointerInputChange.getType()
 RemovedMethod: androidx.compose.ui.semantics.SemanticsPropertiesKt#setImeAction(androidx.compose.ui.semantics.SemanticsPropertyReceiver, androidx.compose.ui.text.input.ImeAction):
     Removed method androidx.compose.ui.semantics.SemanticsPropertiesKt.setImeAction(androidx.compose.ui.semantics.SemanticsPropertyReceiver,androidx.compose.ui.text.input.ImeAction)
+RemovedMethod: androidx.compose.ui.window.PopupProperties#PopupProperties(boolean, boolean, boolean, androidx.compose.ui.window.SecureFlagPolicy):
+    Removed constructor androidx.compose.ui.window.PopupProperties(boolean,boolean,boolean,androidx.compose.ui.window.SecureFlagPolicy)
diff --git a/compose/ui/ui/api/current.txt b/compose/ui/ui/api/current.txt
index 90caf3a..ef6a13d 100644
--- a/compose/ui/ui/api/current.txt
+++ b/compose/ui/ui/api/current.txt
@@ -2575,13 +2575,17 @@
   }
 
   @androidx.compose.runtime.Immutable public final class PopupProperties {
-    ctor public PopupProperties(optional boolean focusable, optional boolean dismissOnBackPress, optional boolean dismissOnClickOutside, optional androidx.compose.ui.window.SecureFlagPolicy securePolicy);
+    ctor public PopupProperties(optional boolean focusable, optional boolean dismissOnBackPress, optional boolean dismissOnClickOutside, optional androidx.compose.ui.window.SecureFlagPolicy securePolicy, optional boolean excludeFromSystemGesture, optional boolean clippingEnabled);
+    method public boolean getClippingEnabled();
     method public boolean getDismissOnBackPress();
     method public boolean getDismissOnClickOutside();
+    method public boolean getExcludeFromSystemGesture();
     method public boolean getFocusable();
     method public androidx.compose.ui.window.SecureFlagPolicy getSecurePolicy();
+    property public final boolean clippingEnabled;
     property public final boolean dismissOnBackPress;
     property public final boolean dismissOnClickOutside;
+    property public final boolean excludeFromSystemGesture;
     property public final boolean focusable;
     property public final androidx.compose.ui.window.SecureFlagPolicy securePolicy;
   }
diff --git a/compose/ui/ui/api/public_plus_experimental_1.0.0-beta08.txt b/compose/ui/ui/api/public_plus_experimental_1.0.0-beta08.txt
index 580e82d..e989440 100644
--- a/compose/ui/ui/api/public_plus_experimental_1.0.0-beta08.txt
+++ b/compose/ui/ui/api/public_plus_experimental_1.0.0-beta08.txt
@@ -2729,18 +2729,17 @@
   }
 
   @androidx.compose.runtime.Immutable public final class PopupProperties {
-    ctor @androidx.compose.ui.ExperimentalComposeUiApi public PopupProperties(optional boolean focusable, optional boolean dismissOnBackPress, optional boolean dismissOnClickOutside, optional androidx.compose.ui.window.SecureFlagPolicy securePolicy, optional boolean excludeFromSystemGesture, optional boolean clippingEnabled);
-    ctor public PopupProperties(optional boolean focusable, optional boolean dismissOnBackPress, optional boolean dismissOnClickOutside, optional androidx.compose.ui.window.SecureFlagPolicy securePolicy);
-    method @androidx.compose.ui.ExperimentalComposeUiApi public boolean getClippingEnabled();
+    ctor public PopupProperties(optional boolean focusable, optional boolean dismissOnBackPress, optional boolean dismissOnClickOutside, optional androidx.compose.ui.window.SecureFlagPolicy securePolicy, optional boolean excludeFromSystemGesture, optional boolean clippingEnabled);
+    method public boolean getClippingEnabled();
     method public boolean getDismissOnBackPress();
     method public boolean getDismissOnClickOutside();
-    method @androidx.compose.ui.ExperimentalComposeUiApi public boolean getExcludeFromSystemGesture();
+    method public boolean getExcludeFromSystemGesture();
     method public boolean getFocusable();
     method public androidx.compose.ui.window.SecureFlagPolicy getSecurePolicy();
-    property @androidx.compose.ui.ExperimentalComposeUiApi public final boolean clippingEnabled;
+    property public final boolean clippingEnabled;
     property public final boolean dismissOnBackPress;
     property public final boolean dismissOnClickOutside;
-    property @androidx.compose.ui.ExperimentalComposeUiApi public final boolean excludeFromSystemGesture;
+    property public final boolean excludeFromSystemGesture;
     property public final boolean focusable;
     property public final androidx.compose.ui.window.SecureFlagPolicy securePolicy;
   }
diff --git a/compose/ui/ui/api/public_plus_experimental_current.txt b/compose/ui/ui/api/public_plus_experimental_current.txt
index 580e82d..e989440 100644
--- a/compose/ui/ui/api/public_plus_experimental_current.txt
+++ b/compose/ui/ui/api/public_plus_experimental_current.txt
@@ -2729,18 +2729,17 @@
   }
 
   @androidx.compose.runtime.Immutable public final class PopupProperties {
-    ctor @androidx.compose.ui.ExperimentalComposeUiApi public PopupProperties(optional boolean focusable, optional boolean dismissOnBackPress, optional boolean dismissOnClickOutside, optional androidx.compose.ui.window.SecureFlagPolicy securePolicy, optional boolean excludeFromSystemGesture, optional boolean clippingEnabled);
-    ctor public PopupProperties(optional boolean focusable, optional boolean dismissOnBackPress, optional boolean dismissOnClickOutside, optional androidx.compose.ui.window.SecureFlagPolicy securePolicy);
-    method @androidx.compose.ui.ExperimentalComposeUiApi public boolean getClippingEnabled();
+    ctor public PopupProperties(optional boolean focusable, optional boolean dismissOnBackPress, optional boolean dismissOnClickOutside, optional androidx.compose.ui.window.SecureFlagPolicy securePolicy, optional boolean excludeFromSystemGesture, optional boolean clippingEnabled);
+    method public boolean getClippingEnabled();
     method public boolean getDismissOnBackPress();
     method public boolean getDismissOnClickOutside();
-    method @androidx.compose.ui.ExperimentalComposeUiApi public boolean getExcludeFromSystemGesture();
+    method public boolean getExcludeFromSystemGesture();
     method public boolean getFocusable();
     method public androidx.compose.ui.window.SecureFlagPolicy getSecurePolicy();
-    property @androidx.compose.ui.ExperimentalComposeUiApi public final boolean clippingEnabled;
+    property public final boolean clippingEnabled;
     property public final boolean dismissOnBackPress;
     property public final boolean dismissOnClickOutside;
-    property @androidx.compose.ui.ExperimentalComposeUiApi public final boolean excludeFromSystemGesture;
+    property public final boolean excludeFromSystemGesture;
     property public final boolean focusable;
     property public final androidx.compose.ui.window.SecureFlagPolicy securePolicy;
   }
diff --git a/compose/ui/ui/api/restricted_1.0.0-beta08.txt b/compose/ui/ui/api/restricted_1.0.0-beta08.txt
index db057a1..7938cd4 100644
--- a/compose/ui/ui/api/restricted_1.0.0-beta08.txt
+++ b/compose/ui/ui/api/restricted_1.0.0-beta08.txt
@@ -2605,13 +2605,17 @@
   }
 
   @androidx.compose.runtime.Immutable public final class PopupProperties {
-    ctor public PopupProperties(optional boolean focusable, optional boolean dismissOnBackPress, optional boolean dismissOnClickOutside, optional androidx.compose.ui.window.SecureFlagPolicy securePolicy);
+    ctor public PopupProperties(optional boolean focusable, optional boolean dismissOnBackPress, optional boolean dismissOnClickOutside, optional androidx.compose.ui.window.SecureFlagPolicy securePolicy, optional boolean excludeFromSystemGesture, optional boolean clippingEnabled);
+    method public boolean getClippingEnabled();
     method public boolean getDismissOnBackPress();
     method public boolean getDismissOnClickOutside();
+    method public boolean getExcludeFromSystemGesture();
     method public boolean getFocusable();
     method public androidx.compose.ui.window.SecureFlagPolicy getSecurePolicy();
+    property public final boolean clippingEnabled;
     property public final boolean dismissOnBackPress;
     property public final boolean dismissOnClickOutside;
+    property public final boolean excludeFromSystemGesture;
     property public final boolean focusable;
     property public final androidx.compose.ui.window.SecureFlagPolicy securePolicy;
   }
diff --git a/compose/ui/ui/api/restricted_current.ignore b/compose/ui/ui/api/restricted_current.ignore
index 266ec45..f30e81b 100644
--- a/compose/ui/ui/api/restricted_current.ignore
+++ b/compose/ui/ui/api/restricted_current.ignore
@@ -109,3 +109,5 @@
     Removed method androidx.compose.ui.input.pointer.PointerInputChange.getType()
 RemovedMethod: androidx.compose.ui.semantics.SemanticsPropertiesKt#setImeAction(androidx.compose.ui.semantics.SemanticsPropertyReceiver, androidx.compose.ui.text.input.ImeAction):
     Removed method androidx.compose.ui.semantics.SemanticsPropertiesKt.setImeAction(androidx.compose.ui.semantics.SemanticsPropertyReceiver,androidx.compose.ui.text.input.ImeAction)
+RemovedMethod: androidx.compose.ui.window.PopupProperties#PopupProperties(boolean, boolean, boolean, androidx.compose.ui.window.SecureFlagPolicy):
+    Removed constructor androidx.compose.ui.window.PopupProperties(boolean,boolean,boolean,androidx.compose.ui.window.SecureFlagPolicy)
diff --git a/compose/ui/ui/api/restricted_current.txt b/compose/ui/ui/api/restricted_current.txt
index db057a1..7938cd4 100644
--- a/compose/ui/ui/api/restricted_current.txt
+++ b/compose/ui/ui/api/restricted_current.txt
@@ -2605,13 +2605,17 @@
   }
 
   @androidx.compose.runtime.Immutable public final class PopupProperties {
-    ctor public PopupProperties(optional boolean focusable, optional boolean dismissOnBackPress, optional boolean dismissOnClickOutside, optional androidx.compose.ui.window.SecureFlagPolicy securePolicy);
+    ctor public PopupProperties(optional boolean focusable, optional boolean dismissOnBackPress, optional boolean dismissOnClickOutside, optional androidx.compose.ui.window.SecureFlagPolicy securePolicy, optional boolean excludeFromSystemGesture, optional boolean clippingEnabled);
+    method public boolean getClippingEnabled();
     method public boolean getDismissOnBackPress();
     method public boolean getDismissOnClickOutside();
+    method public boolean getExcludeFromSystemGesture();
     method public boolean getFocusable();
     method public androidx.compose.ui.window.SecureFlagPolicy getSecurePolicy();
+    property public final boolean clippingEnabled;
     property public final boolean dismissOnBackPress;
     property public final boolean dismissOnClickOutside;
+    property public final boolean excludeFromSystemGesture;
     property public final boolean focusable;
     property public final androidx.compose.ui.window.SecureFlagPolicy securePolicy;
   }
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/draw/InvalidatingNotPlacedChildTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/draw/InvalidatingNotPlacedChildTest.kt
index aff3562..befd806 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/draw/InvalidatingNotPlacedChildTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/draw/InvalidatingNotPlacedChildTest.kt
@@ -17,20 +17,21 @@
 package androidx.compose.ui.draw
 
 import android.os.Build
+import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.Spacer
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.requiredSize
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.State
-import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.setValue
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.background
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.graphics.graphicsLayer
 import androidx.compose.ui.layout.Layout
 import androidx.compose.ui.layout.assertCenterPixelColor
+import androidx.compose.ui.layout.layout
+import androidx.compose.ui.platform.AndroidOwnerExtraAssertionsRule
 import androidx.compose.ui.platform.testTag
 import androidx.compose.ui.test.captureToImage
 import androidx.compose.ui.test.junit4.createComposeRule
@@ -50,6 +51,9 @@
     @get:Rule
     val composeTestRule = createComposeRule()
 
+    @get:Rule
+    val excessiveAssertions = AndroidOwnerExtraAssertionsRule()
+
     @Test
     @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
     fun childIsDisplayedWhenItWasNotPlacedOriginallyButPlacedLater() {
@@ -116,44 +120,6 @@
 
     @Test
     @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
-    fun grandChildIsDisplayedCorrectlyWhenTheColorWasChangedWhileNotPlaced() {
-        val shouldPlace = mutableStateOf(false)
-        var color by mutableStateOf(Color.Gray)
-        composeTestRule.setContent {
-            ConditionallyPlacedChild(
-                shouldPlace,
-                Modifier.background(Color.Blue)
-                    .testTag("node")
-            ) {
-                MeasureInLayoutBlock {
-                    Spacer(
-                        Modifier.fillMaxSize()
-                            .graphicsLayer()
-                            .background(color)
-                    )
-                }
-            }
-        }
-
-        composeTestRule.runOnIdle {
-            shouldPlace.value = false
-        }
-
-        composeTestRule.runOnIdle {
-            color = Color.Red
-        }
-
-        composeTestRule.runOnIdle {
-            shouldPlace.value = true
-        }
-
-        composeTestRule.onNodeWithTag("node")
-            .captureToImage()
-            .assertCenterPixelColor(Color.Red)
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
     fun childIsNotDisplayedWhenIsNotPlacedAnymore() {
         val shouldPlace = mutableStateOf(true)
         composeTestRule.setContent {
@@ -179,30 +145,289 @@
             .captureToImage()
             .assertCenterPixelColor(Color.Blue)
     }
+
+    @Test
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+    fun childRedrawRequestedWhileNotPlaced() {
+        assertChangeWhileNotPlacedIsApplied { shouldPlace, color ->
+            ConditionallyPlacedChild(shouldPlace) {
+                Spacer(
+                    Modifier.fillMaxSize()
+                        .drawBehind {
+                            drawRect(color.value)
+                        }
+                )
+            }
+        }
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+    fun childRedrawRequestedWhileNotPlaced_hadLayer() {
+        assertChangeWhileNotPlacedIsApplied { shouldPlace, color ->
+            ConditionallyPlacedChild(shouldPlace) {
+                Spacer(
+                    Modifier.fillMaxSize()
+                        .graphicsLayer()
+                        .drawBehind {
+                            drawRect(color.value)
+                        }
+                )
+            }
+        }
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+    fun childRedrawRequestedWhileNotPlaced_hadLayerAsLastModifierInTheChain() {
+        assertChangeWhileNotPlacedIsApplied { shouldPlace, color ->
+            ConditionallyPlacedChild(shouldPlace) {
+                Box(Modifier.graphicsLayer()) {
+                    Spacer(
+                        Modifier.fillMaxSize()
+                            .drawBehind {
+                                drawRect(color.value)
+                            }
+                    )
+                }
+            }
+        }
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+    fun childRedrawRequestedWhileNotPlaced_placedWithLayer() {
+        assertChangeWhileNotPlacedIsApplied { shouldPlace, color ->
+            ConditionallyPlacedChild(shouldPlace, placeWithLayer = true) {
+                Spacer(
+                    Modifier.fillMaxSize()
+                        .drawBehind {
+                            drawRect(color.value)
+                        }
+                )
+            }
+        }
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+    fun childRedrawAndRemeasureRequestedWhileNotPlaced() {
+        assertChangeWhileNotPlacedIsApplied { shouldPlace, color ->
+            ConditionallyPlacedChild(shouldPlace) {
+                Spacer(
+                    Modifier.fillMaxSize()
+                        .layout(useDuringMeasure = color)
+                        .drawBehind {
+                            drawRect(color.value)
+                        }
+                )
+            }
+        }
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+    fun childRedrawAndRelayoutRequestedWhileNotPlaced() {
+        assertChangeWhileNotPlacedIsApplied { shouldPlace, color ->
+            ConditionallyPlacedChild(shouldPlace) {
+                Spacer(
+                    Modifier.fillMaxSize()
+                        .layout(useDuringLayout = color)
+                        .drawBehind {
+                            drawRect(color.value)
+                        }
+                )
+            }
+        }
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+    fun grandChildRedrawRequestedWhileNotPlaced() {
+        assertChangeWhileNotPlacedIsApplied { shouldPlace, color ->
+            ConditionallyPlacedChild(shouldPlace) {
+                MeasureInLayoutBlock {
+                    Spacer(
+                        Modifier.fillMaxSize()
+                            .drawBehind {
+                                drawRect(color.value)
+                            }
+                    )
+                }
+            }
+        }
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+    fun grandChildRedrawRequestedWhileNotPlaced_hadLayer() {
+        assertChangeWhileNotPlacedIsApplied { shouldPlace, color ->
+            ConditionallyPlacedChild(shouldPlace) {
+                MeasureInLayoutBlock {
+                    Spacer(
+                        Modifier.fillMaxSize()
+                            .graphicsLayer()
+                            .drawBehind {
+                                drawRect(color.value)
+                            }
+                    )
+                }
+            }
+        }
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+    fun grandChildRedrawRequestedWhileNotPlaced_hadLayerAsLastModifierInTheChain() {
+        assertChangeWhileNotPlacedIsApplied { shouldPlace, color ->
+            ConditionallyPlacedChild(shouldPlace) {
+                MeasureInLayoutBlock {
+                    Box(Modifier.graphicsLayer()) {
+                        Spacer(
+                            Modifier.fillMaxSize()
+                                .drawBehind {
+                                    drawRect(color.value)
+                                }
+                        )
+                    }
+                }
+            }
+        }
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+    fun grandChildRedrawRequestedWhileNotPlaced_placedWithLayer() {
+        assertChangeWhileNotPlacedIsApplied { shouldPlace, color ->
+            ConditionallyPlacedChild(shouldPlace) {
+                MeasureInLayoutBlock(placeWithLayer = true) {
+                    Spacer(
+                        Modifier.fillMaxSize()
+                            .drawBehind {
+                                drawRect(color.value)
+                            }
+                    )
+                }
+            }
+        }
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+    fun grandChildRedrawAndRemeasureRequestedWhileNotPlaced() {
+        assertChangeWhileNotPlacedIsApplied { shouldPlace, color ->
+            ConditionallyPlacedChild(shouldPlace) {
+                MeasureInLayoutBlock {
+                    Spacer(
+                        Modifier.fillMaxSize()
+                            .layout(useDuringMeasure = color)
+                            .drawBehind {
+                                drawRect(color.value)
+                            }
+                    )
+                }
+            }
+        }
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+    fun grandChildRedrawAndRelayoutRequestedWhileNotPlaced() {
+        assertChangeWhileNotPlacedIsApplied { shouldPlace, color ->
+            ConditionallyPlacedChild(shouldPlace) {
+                MeasureInLayoutBlock {
+                    Spacer(
+                        Modifier.fillMaxSize()
+                            .layout(useDuringLayout = color)
+                            .drawBehind {
+                                drawRect(color.value)
+                            }
+                    )
+                }
+            }
+        }
+    }
+
+    fun assertChangeWhileNotPlacedIsApplied(
+        content: @Composable (State<Boolean>, State<Color>) -> Unit
+    ) {
+        val shouldPlace = mutableStateOf(true)
+        var color = mutableStateOf(Color.Gray)
+        composeTestRule.setContent {
+            Box(
+                Modifier.background(Color.Blue)
+                    .testTag("node")
+            ) {
+                content(shouldPlace, color)
+            }
+        }
+
+        composeTestRule.runOnIdle {
+            shouldPlace.value = false
+        }
+
+        composeTestRule.runOnIdle {
+            color.value = Color.Red
+        }
+
+        composeTestRule.runOnIdle {
+            shouldPlace.value = true
+        }
+
+        composeTestRule.onNodeWithTag("node")
+            .captureToImage()
+            .assertCenterPixelColor(Color.Red)
+    }
 }
 
 @Composable
 private fun ConditionallyPlacedChild(
     shouldPlace: State<Boolean>,
-    modifier: Modifier,
+    modifier: Modifier = Modifier,
+    placeWithLayer: Boolean = false,
     content: @Composable () -> Unit
 ) {
     Layout(content = content, modifier = modifier) { measurables, constraints ->
         val placeable = measurables.first().measure(constraints)
         layout(placeable.width, placeable.height) {
             if (shouldPlace.value) {
-                placeable.place(0, 0)
+                if (placeWithLayer) {
+                    placeable.placeWithLayer(0, 0)
+                } else {
+                    placeable.place(0, 0)
+                }
             }
         }
     }
 }
 
 @Composable
-private fun MeasureInLayoutBlock(content: @Composable () -> Unit) {
-    Layout(content = content) { measurables, constraints ->
+private fun MeasureInLayoutBlock(
+    modifier: Modifier = Modifier,
+    placeWithLayer: Boolean = false,
+    content: @Composable () -> Unit
+) {
+    Layout(content = content, modifier = modifier) { measurables, constraints ->
         val size = 5.dp.roundToPx()
         layout(size, size) {
-            measurables.first().measure(constraints).place(0, 0)
+            val placeable = measurables.first().measure(constraints)
+            if (placeWithLayer) {
+                placeable.placeWithLayer(0, 0)
+            } else {
+                placeable.place(0, 0)
+            }
         }
     }
 }
+
+private fun Modifier.layout(
+    useDuringMeasure: State<*>? = null,
+    useDuringLayout: State<*>? = null,
+): Modifier = layout { measurable, constraints ->
+    useDuringMeasure?.value
+    val placeable = measurable.measure(constraints)
+    layout(placeable.width, placeable.height) {
+        useDuringLayout?.value
+        placeable.place(0, 0)
+    }
+}
\ No newline at end of file
diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/ViewLayerContainer.android.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/ViewLayerContainer.android.kt
index 4a3f22c..a4282ee 100644
--- a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/ViewLayerContainer.android.kt
+++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/ViewLayerContainer.android.kt
@@ -19,6 +19,7 @@
 import android.content.Context
 import android.view.View
 import android.view.ViewGroup
+import androidx.compose.ui.R
 import androidx.compose.ui.graphics.Canvas
 import androidx.compose.ui.graphics.nativeCanvas
 
@@ -45,6 +46,9 @@
 internal open class DrawChildContainer(context: Context) : ViewGroup(context) {
     init {
         clipChildren = false
+
+        // Hide this view and its children in tools:
+        setTag(R.id.hide_in_inspector_tag, true)
     }
 
     override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/window/AndroidPopup.android.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/window/AndroidPopup.android.kt
index 807327f..45c4c1a 100644
--- a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/window/AndroidPopup.android.kt
+++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/window/AndroidPopup.android.kt
@@ -95,32 +95,14 @@
  * The default value is true.
  */
 @Immutable
-class PopupProperties @ExperimentalComposeUiApi constructor(
+class PopupProperties(
     val focusable: Boolean = false,
     val dismissOnBackPress: Boolean = true,
     val dismissOnClickOutside: Boolean = true,
     val securePolicy: SecureFlagPolicy = SecureFlagPolicy.Inherit,
-    @get:ExperimentalComposeUiApi
     val excludeFromSystemGesture: Boolean = true,
-    @get:ExperimentalComposeUiApi
     val clippingEnabled: Boolean = true
 ) {
-    @OptIn(ExperimentalComposeUiApi::class)
-    constructor(
-        focusable: Boolean = false,
-        dismissOnBackPress: Boolean = true,
-        dismissOnClickOutside: Boolean = true,
-        securePolicy: SecureFlagPolicy = SecureFlagPolicy.Inherit,
-    ) : this (
-        focusable,
-        dismissOnBackPress,
-        dismissOnClickOutside,
-        securePolicy,
-        excludeFromSystemGesture = true,
-        clippingEnabled = true
-    )
-
-    @OptIn(ExperimentalComposeUiApi::class)
     override fun equals(other: Any?): Boolean {
         if (this === other) return true
         if (other !is PopupProperties) return false
@@ -135,7 +117,6 @@
         return true
     }
 
-    @OptIn(ExperimentalComposeUiApi::class)
     override fun hashCode(): Int {
         var result = dismissOnBackPress.hashCode()
         result = 31 * result + focusable.hashCode()
@@ -502,7 +483,6 @@
     /**
      * Updates the position of the popup based on current position properties.
      */
-    @OptIn(ExperimentalComposeUiApi::class)
     fun updatePosition() {
         val parentBounds = parentBounds ?: return
         val popupContentSize = popupContentSize ?: return
diff --git a/compose/ui/ui/src/androidMain/res/values/ids.xml b/compose/ui/ui/src/androidMain/res/values/ids.xml
index caa764c..3c04edc 100644
--- a/compose/ui/ui/src/androidMain/res/values/ids.xml
+++ b/compose/ui/ui/src/androidMain/res/values/ids.xml
@@ -52,4 +52,5 @@
     <item name="inspection_slot_table_set" type="id" />
     <item name="androidx_compose_ui_view_composition_context" type="id" />
     <item name="compose_view_saveable_id_tag" type="id" />
+    <item name="hide_in_inspector_tag" type="id" />
 </resources>
\ No newline at end of file
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutNode.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutNode.kt
index 07483f5..b4362fe 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutNode.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutNode.kt
@@ -535,7 +535,8 @@
      * every placed node assigns this variable to [parent]s [nextChildPlaceOrder] and increments
      * this counter. Not placed items will still have [NotPlacedPlaceOrder] set.
      */
-    private var placeOrder: Int = NotPlacedPlaceOrder
+    internal var placeOrder: Int = NotPlacedPlaceOrder
+        private set
 
     /**
      * The counter on a parent node which is used by its children to understand the order in which
@@ -840,17 +841,10 @@
         }
 
         if (!isPlaced) {
-            isPlaced = true
             // when the visibility of a child has been changed we need to invalidate
             // parents inner layer - the layer in which this child will be drawn
             parent?.invalidateLayer()
-            // plus all the inner layers that were invalidated while the node was not placed
-            forEachDelegate {
-                if (it.lastLayerDrawingWasSkipped) {
-                    it.invalidateLayer()
-                }
-            }
-            markSubtreeAsPlaced()
+            markNodeAndSubtreeAsPlaced()
         }
 
         if (parent != null) {
@@ -917,14 +911,44 @@
         if (alignmentLines.dirty && alignmentLines.required) alignmentLines.recalculate()
     }
 
-    private fun markSubtreeAsPlaced() {
-        _children.forEach {
-            // if the layout state is not Ready then isPlaced will be set during the layout
-            if (it.layoutState == Ready && it.placeOrder != NotPlacedPlaceOrder) {
-                it.isPlaced = true
-                it.markSubtreeAsPlaced()
+    private fun markNodeAndSubtreeAsPlaced() {
+        isPlaced = true
+        // invalidate all the nodes layers that were invalidated while the node was not placed
+        forEachDelegateIncludingInner {
+            if (it.lastLayerDrawingWasSkipped) {
+                it.invalidateLayer()
             }
         }
+        _children.forEach {
+            // this child was placed during the previous parent's layoutChildren(). this means that
+            // before the parent became not placed this child was placed. we need to restore that
+            if (it.placeOrder != NotPlacedPlaceOrder) {
+                it.markNodeAndSubtreeAsPlaced()
+                rescheduleRemeasureOrRelayout(it)
+            }
+        }
+    }
+
+    private fun rescheduleRemeasureOrRelayout(it: LayoutNode) {
+        when (val state = it.layoutState) {
+            NeedsRemeasure, NeedsRelayout -> {
+                // we need to reset the state before requesting as otherwise the request
+                // would be ignored.
+                it.layoutState = Ready
+                // this node was scheduled for remeasure or relayout while it was not
+                // placed. such requests are ignored for non-placed nodes so we have to
+                // re-schedule remeasure or relayout.
+                if (state == NeedsRemeasure) {
+                    it.requestRemeasure()
+                } else {
+                    it.requestRelayout()
+                }
+            }
+            Ready -> {
+                // no extra work required and node is ready to be displayed
+            }
+            else -> throw IllegalStateException("Unexpected state ${it.layoutState}")
+        }
     }
 
     private fun markSubtreeAsNotPlaced() {
@@ -1252,7 +1276,7 @@
         /**
          * Constant used by [placeOrder].
          */
-        private const val NotPlacedPlaceOrder = Int.MAX_VALUE
+        internal const val NotPlacedPlaceOrder = Int.MAX_VALUE
 
         /**
          * Pre-allocated constructor to be used with ComposeNode
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutNodeWrapper.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutNodeWrapper.kt
index 2fa6f6c..6073d13 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutNodeWrapper.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutNodeWrapper.kt
@@ -299,6 +299,7 @@
                 }
             }
             layer = null
+            lastLayerDrawingWasSkipped = false
         }
     }
 
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutTreeConsistencyChecker.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutTreeConsistencyChecker.kt
index 337e2b0..be1fb03 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutTreeConsistencyChecker.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutTreeConsistencyChecker.kt
@@ -49,7 +49,10 @@
     }
 
     private fun LayoutNode.consistentLayoutState(): Boolean {
-        if (isPlaced) {
+        val parent = this.parent
+        if (isPlaced ||
+            placeOrder != LayoutNode.NotPlacedPlaceOrder && parent?.isPlaced == true
+        ) {
             if (layoutState == LayoutNode.LayoutState.NeedsRemeasure &&
                 postponedMeasureRequests.contains(this)
             ) {
@@ -58,7 +61,7 @@
                 return true
             }
             // remeasure or relayout is scheduled
-            val parentLayoutState = this.parent?.layoutState
+            val parentLayoutState = parent?.layoutState
             if (layoutState == LayoutNode.LayoutState.NeedsRemeasure) {
                 return relayoutNodes.contains(this) ||
                     parentLayoutState == LayoutNode.LayoutState.NeedsRemeasure ||
diff --git a/core/core-google-shortcuts/build.gradle b/core/core-google-shortcuts/build.gradle
index ea77000..7f7bf68 100644
--- a/core/core-google-shortcuts/build.gradle
+++ b/core/core-google-shortcuts/build.gradle
@@ -32,7 +32,7 @@
 }
 
 dependencies {
-    api(project(":core:core"))
+    api("androidx.core:core:1.6.0-beta01")
 
     implementation("com.google.firebase:firebase-appindexing:19.2.0")
     implementation("com.google.crypto.tink:tink-android:1.5.0")
diff --git a/core/core/api/current.txt b/core/core/api/current.txt
index 2cf275c..83d6a2d 100644
--- a/core/core/api/current.txt
+++ b/core/core/api/current.txt
@@ -1542,6 +1542,7 @@
     method @Deprecated @ChecksSdkIntAtLeast(api=android.os.Build.VERSION_CODES.Q) public static boolean isAtLeastQ();
     method @Deprecated @ChecksSdkIntAtLeast(api=android.os.Build.VERSION_CODES.R) public static boolean isAtLeastR();
     method @ChecksSdkIntAtLeast(codename="S") public static boolean isAtLeastS();
+    method @ChecksSdkIntAtLeast(codename="T") public static boolean isAtLeastT();
   }
 
   public final class CancellationSignal {
diff --git a/core/core/api/public_plus_experimental_current.txt b/core/core/api/public_plus_experimental_current.txt
index 3934ab1..ebb5dfb 100644
--- a/core/core/api/public_plus_experimental_current.txt
+++ b/core/core/api/public_plus_experimental_current.txt
@@ -1540,6 +1540,7 @@
     method @Deprecated @ChecksSdkIntAtLeast(api=android.os.Build.VERSION_CODES.Q) public static boolean isAtLeastQ();
     method @Deprecated @ChecksSdkIntAtLeast(api=android.os.Build.VERSION_CODES.R) public static boolean isAtLeastR();
     method @ChecksSdkIntAtLeast(codename="S") public static boolean isAtLeastS();
+    method @ChecksSdkIntAtLeast(codename="T") public static boolean isAtLeastT();
   }
 
   public final class CancellationSignal {
diff --git a/core/core/api/restricted_current.txt b/core/core/api/restricted_current.txt
index 46113a6..34557d9 100644
--- a/core/core/api/restricted_current.txt
+++ b/core/core/api/restricted_current.txt
@@ -1861,6 +1861,7 @@
     method @Deprecated @ChecksSdkIntAtLeast(api=android.os.Build.VERSION_CODES.Q) public static boolean isAtLeastQ();
     method @Deprecated @ChecksSdkIntAtLeast(api=android.os.Build.VERSION_CODES.R) public static boolean isAtLeastR();
     method @ChecksSdkIntAtLeast(codename="S") public static boolean isAtLeastS();
+    method @ChecksSdkIntAtLeast(codename="T") public static boolean isAtLeastT();
   }
 
   public final class CancellationSignal {
diff --git a/core/core/src/main/java/androidx/core/os/BuildCompat.java b/core/core/src/main/java/androidx/core/os/BuildCompat.java
index ba91322..389622f 100644
--- a/core/core/src/main/java/androidx/core/os/BuildCompat.java
+++ b/core/core/src/main/java/androidx/core/os/BuildCompat.java
@@ -139,6 +139,21 @@
      */
     @ChecksSdkIntAtLeast(codename = "S")
     public static boolean isAtLeastS() {
-        return VERSION.CODENAME.equals("S");
+        return VERSION.CODENAME.equals("S") || VERSION.CODENAME.equals("T");
+    }
+
+    /**
+     * Checks if the device is running on a pre-release version of Android T or a release version of
+     * Android T or newer.
+     * <p>
+     * <strong>Note:</strong> When Android T is finalized for release, this method will be
+     * deprecated and all calls should be replaced with {@code Build.VERSION.SDK_INT >=
+     * Build.VERSION_CODES.T}.
+     *
+     * @return {@code true} if T APIs are available for use, {@code false} otherwise
+     */
+    @ChecksSdkIntAtLeast(codename = "T")
+    public static boolean isAtLeastT() {
+        return VERSION.CODENAME.equals("T");
     }
 }
diff --git a/core/core/src/main/java/androidx/core/view/OnReceiveContentListener.java b/core/core/src/main/java/androidx/core/view/OnReceiveContentListener.java
index 9031f8c..4682dce 100644
--- a/core/core/src/main/java/androidx/core/view/OnReceiveContentListener.java
+++ b/core/core/src/main/java/androidx/core/view/OnReceiveContentListener.java
@@ -80,16 +80,40 @@
      * implementation and see {@link ContentInfoCompat#partition} for a convenient way to split the
      * passed-in content.
      *
-     * <p>If implementing handling for text: if the view has a selection, the selection should
-     * be overwritten by the passed-in content; if there's no selection, the passed-in content
-     * should be inserted at the current cursor position.
+     * <h3>Handling different content</h3>
+     * <ul>
+     *     <li>Text. The passed-in text should overwrite the current selection or be inserted at
+     *     the current cursor position if there is no selection.
+     *     <li>Non-text content (e.g. images). The content may be inserted inline if the widget
+     *     supports this, or it may be added as an attachment (could potentially be shown in a
+     *     completely separate view).
+     * </ul>
      *
-     * <p>If implementing handling for non-text content (e.g. images): the content may be
-     * inserted inline, or it may be added as an attachment (could potentially be shown in a
-     * completely separate view).
+     * <h3>URI permissions</h3>
+     * <p>{@link android.content.Intent#FLAG_GRANT_READ_URI_PERMISSION Read permissions} are
+     * granted automatically by the platform for any
+     * {@link android.content.ContentResolver#SCHEME_CONTENT content URIs} in the payload passed
+     * to this listener. Permissions are transient and will be released automatically by the
+     * platform.
+     * <p>Processing of content should normally be done in a service or activity.
+     * For long-running processing, using {@code androidx.work.WorkManager} is recommended.
+     * When implementing this, permissions should be extended to the target service or activity
+     * by passing the content using {@link android.content.Intent#setClipData Intent.setClipData}
+     * and {@link android.content.Intent#addFlags(int) setting} the flag
+     * {@link android.content.Intent#FLAG_GRANT_READ_URI_PERMISSION FLAG_GRANT_READ_URI_PERMISSION}.
+     * <p>Alternatively, if using a background thread within the current context to process the
+     * content, a reference to the {@code payload} object should be maintained to ensure that
+     * permissions are not revoked prematurely.
      *
      * @param view The view where the content insertion was requested.
-     * @param payload The content to insert and related metadata.
+     * @param payload The content to insert and related metadata. The payload may contain multiple
+     *                items and their MIME types may be different (e.g. an image item and a text
+     *                item). The payload may also contain items whose MIME type is not in the list
+     *                of MIME types specified when
+     *                {@link ViewCompat#setOnReceiveContentListener setting} the listener. For
+     *                those items, the listener may reject the content (defer to the default
+     *                platform behavior) or execute some other fallback logic (e.g. show an
+     *                appropriate message to the user).
      *
      * @return The portion of the passed-in content whose processing should be delegated to
      * the platform. Return null if all content was handled in some way. Actual insertion of
diff --git a/core/core/src/main/java/androidx/core/view/ViewCompat.java b/core/core/src/main/java/androidx/core/view/ViewCompat.java
index 88180d1..52df3c3 100644
--- a/core/core/src/main/java/androidx/core/view/ViewCompat.java
+++ b/core/core/src/main/java/androidx/core/view/ViewCompat.java
@@ -2701,19 +2701,25 @@
      * Sets the listener to be used to handle insertion of content into the given view.
      *
      * <p>Depending on the type of view, this listener may be invoked for different scenarios. For
-     * example, for an AppCompatEditText, this listener will be invoked for the following scenarios:
+     * example, for an {@code AppCompatEditText}, this listener will be invoked for the following
+     * scenarios:
      * <ol>
      *     <li>Paste from the clipboard (e.g. "Paste" or "Paste as plain text" action in the
      *     insertion/selection menu)
      *     <li>Content insertion from the keyboard (from {@link InputConnection#commitContent})
+     *     <li>Drag and drop (drop events from {@link View#onDragEvent})
      * </ol>
      *
-     * <p>When setting a listener, clients should also declare the MIME types accepted by it.
-     * When invoked with other types of content, the listener may reject the content (defer to
-     * the default platform behavior) or execute some other fallback logic. The MIME types
-     * declared here allow different features to optionally alter their behavior. For example,
-     * the soft keyboard may choose to hide its UI for inserting GIFs for a particular input
-     * field if the MIME types set here for that field don't include "image/gif" or "image/*".
+     * <p>When setting a listener, clients must also declare the accepted MIME types.
+     * The listener will still be invoked even if the MIME type of the content is not one of the
+     * declared MIME types (e.g. if the user pastes content whose type is not one of the declared
+     * MIME types).
+     * In that case, the listener may reject the content (defer to the default platform behavior)
+     * or execute some other fallback logic (e.g. show an appropriate message to the user).
+     * The declared MIME types serve as a hint to allow different features to optionally alter
+     * their behavior. For example, a soft keyboard may optionally choose to hide its UI for
+     * inserting GIFs for a particular input field if the MIME types set here for that field
+     * don't include "image/gif" or "image/*".
      *
      * <p>Note: MIME type matching in the Android framework is case-sensitive, unlike formal RFC
      * MIME types. As a result, you should always write your MIME types with lowercase letters,
diff --git a/mediarouter/mediarouter/lint-baseline.xml b/mediarouter/mediarouter/lint-baseline.xml
index e41e0ea..7aa92ae 100644
--- a/mediarouter/mediarouter/lint-baseline.xml
+++ b/mediarouter/mediarouter/lint-baseline.xml
@@ -958,15 +958,4 @@
             column="65"/>
     </issue>
 
-    <issue
-        id="ObsoleteSdkInt"
-        message="Unnecessary; SDK_INT is always >= 14"
-        errorLine1="            } else if (Build.VERSION.SDK_INT >= 14) {"
-        errorLine2="                       ~~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/mediarouter/media/MediaRouter.java"
-            line="3403"
-            column="24"/>
-    </issue>
-
 </issues>
diff --git a/mediarouter/mediarouter/src/main/java/androidx/mediarouter/media/MediaRouter.java b/mediarouter/mediarouter/src/main/java/androidx/mediarouter/media/MediaRouter.java
index 9e98d05..2e7ff569 100644
--- a/mediarouter/mediarouter/src/main/java/androidx/mediarouter/media/MediaRouter.java
+++ b/mediarouter/mediarouter/src/main/java/androidx/mediarouter/media/MediaRouter.java
@@ -3400,7 +3400,7 @@
             mCompatSession = session;
             if (Build.VERSION.SDK_INT >= 21) {
                 setMediaSessionRecord(session != null ? new MediaSessionRecord(session) : null);
-            } else if (Build.VERSION.SDK_INT >= 14) {
+            } else {
                 if (mRccMediaSession != null) {
                     removeRemoteControlClient(mRccMediaSession.getRemoteControlClient());
                     mRccMediaSession.removeOnActiveChangeListener(mSessionActiveListener);
diff --git a/wear/tiles/tiles-renderer/build.gradle b/wear/tiles/tiles-renderer/build.gradle
index ae9634b..8b6d6ee 100644
--- a/wear/tiles/tiles-renderer/build.gradle
+++ b/wear/tiles/tiles-renderer/build.gradle
@@ -33,6 +33,8 @@
 
     implementation "androidx.concurrent:concurrent-futures:1.1.0"
     implementation "androidx.core:core:1.3.2"
+    implementation "androidx.wear:wear:1.2.0-alpha09"
+
     implementation(project(":wear:tiles:tiles"))
     implementation(project(path: ":wear:tiles:tiles-proto", configuration: "shadow"))
     implementation(libs.kotlinCoroutinesCore)
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 4bc4b41..433bec7 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
@@ -120,8 +120,8 @@
     public AndroidXScreenshotTestRule screenshotRule =
             new AndroidXScreenshotTestRule("wear/wear-tiles-renderer");
 
-    // This isn't totally ideal right now.
-    // The screenshot tests run on a phone, so emulate some watch dimensions here.
+    // This isn't totally ideal right now. The screenshot tests run on a phone, so emulate some
+    // watch dimensions here.
     private static final int SCREEN_WIDTH = 390;
     private static final int SCREEN_HEIGHT = 390;
 
@@ -149,9 +149,9 @@
         byte[] inlineImagePayload =
                 new byte[INLINE_IMAGE_WIDTH * INLINE_IMAGE_HEIGHT * INLINE_IMAGE_PIXEL_STRIDE];
 
-        // Generate a square image, with a white square in the center.
-        // This replaces an inline payload as a byte array. We could hardcode it, but the
-        // autoformatter will ruin the formatting.
+        // Generate a square image, with a white square in the center. This replaces an inline
+        // payload as a byte array. We could hardcode it, but the autoformatter will ruin the
+        // formatting.
         for (int y = 0; y < 8; y++) {
             for (int x = 0; x < 8; x++) {
                 int index = ((y * INLINE_IMAGE_WIDTH) + x) * INLINE_IMAGE_PIXEL_STRIDE;
diff --git a/wear/tiles/tiles-renderer/src/main/java/androidx/wear/tiles/manager/UpdateScheduler.java b/wear/tiles/tiles-renderer/src/main/java/androidx/wear/tiles/manager/UpdateScheduler.java
index 861ecfc..efcc923 100644
--- a/wear/tiles/tiles-renderer/src/main/java/androidx/wear/tiles/manager/UpdateScheduler.java
+++ b/wear/tiles/tiles-renderer/src/main/java/androidx/wear/tiles/manager/UpdateScheduler.java
@@ -41,9 +41,8 @@
     private long mScheduledUpdateTimeMillis = NO_SCHEDULED_UPDATE;
 
     // Last time at which we updated the tile, measured by the device uptime. This needs to be
-    // device
-    // uptime to prevent issues when time changes (e.g. time jumps caused by syncs with NTP or
-    // similar).
+    // device uptime to prevent issues when time changes (e.g. time jumps caused by syncs with NTP
+    // or similar).
     private long mLastUpdateRealtimeMillis = 0;
 
     UpdateScheduler(AlarmManager alarmManager, Clock clock) {
@@ -99,8 +98,7 @@
                 fireUpdate();
             } else {
                 // "Schedule" an update. This is just so enableUpdates will definitely trigger the
-                // update
-                // when called.
+                // update when called.
                 mScheduledUpdateTimeMillis = nowMillis;
             }
         }
@@ -125,8 +123,7 @@
 
         if (mScheduledUpdateTimeMillis != Long.MAX_VALUE) {
             // If the schedule update is in the past, then fire now, otherwise schedule for the
-            // given
-            // time.
+            // given time.
             long now = mClock.getElapsedTimeMillis();
 
             if (now >= mScheduledUpdateTimeMillis) {
diff --git a/wear/tiles/tiles-renderer/src/main/java/androidx/wear/tiles/renderer/internal/DefaultInlineImageResourceResolver.java b/wear/tiles/tiles-renderer/src/main/java/androidx/wear/tiles/renderer/internal/DefaultInlineImageResourceResolver.java
index b31a850..3f28ec0 100644
--- a/wear/tiles/tiles-renderer/src/main/java/androidx/wear/tiles/renderer/internal/DefaultInlineImageResourceResolver.java
+++ b/wear/tiles/tiles-renderer/src/main/java/androidx/wear/tiles/renderer/internal/DefaultInlineImageResourceResolver.java
@@ -62,8 +62,7 @@
         }
 
         // The app Context is correct here, as it's just used for display density, so it doesn't
-        // depend
-        // on anything from the provider app.
+        // depend on anything from the provider app.
         return new BitmapDrawable(mAppContext.getResources(), bitmap);
     }
 
diff --git a/wear/tiles/tiles-renderer/src/main/java/androidx/wear/tiles/renderer/internal/RatioViewWrapper.java b/wear/tiles/tiles-renderer/src/main/java/androidx/wear/tiles/renderer/internal/RatioViewWrapper.java
index 6cb97bd..512318d 100644
--- a/wear/tiles/tiles-renderer/src/main/java/androidx/wear/tiles/renderer/internal/RatioViewWrapper.java
+++ b/wear/tiles/tiles-renderer/src/main/java/androidx/wear/tiles/renderer/internal/RatioViewWrapper.java
@@ -16,7 +16,6 @@
 
 package androidx.wear.tiles.renderer.internal;
 
-
 import android.content.Context;
 import android.util.AttributeSet;
 import android.view.View;
@@ -113,8 +112,7 @@
         }
 
         // If both are MeasureSpec.EXACTLY, we can't do anything else. Set our dimensions to be the
-        // same
-        // and exit.
+        // same and exit.
         if (widthMeasureMode == MeasureSpec.EXACTLY && heightMeasureMode == MeasureSpec.EXACTLY) {
             setMeasuredDimension(childView.getMeasuredWidth(), childView.getMeasuredHeight());
             return;
@@ -133,16 +131,13 @@
                 && (heightMeasureMode == MeasureSpec.AT_MOST
                         || heightMeasureMode == MeasureSpec.UNSPECIFIED)) {
             // Generally, this happens if this view has both width/height=WRAP_CONTENT. This can
-            // also
-            // happen though if this view has both dimensions as MATCH_CONTENT, but the parent view
-            // is
-            // WRAP_CONTENT. In that case, the parent will run a first view pass to get the size of
-            // the
-            // children, then calculate its size and re-size this widget with EXACTLY MeasureSpecs.
+            // also happen though if this view has both dimensions as MATCH_CONTENT, but the parent
+            // view is WRAP_CONTENT. In that case, the parent will run a first view pass to get the
+            // size of the children, then calculate its size and re-size this widget with EXACTLY
+            // MeasureSpecs.
             //
             // In this case, let's just assume that the child has reached the maximum size that it
-            // wants,
-            // so rescale the dimension that will make it _smaller_.
+            // wants, so rescale the dimension that will make it _smaller_.
             float targetWidth = childView.getMeasuredHeight() * mAspectRatio;
             float targetHeight = childView.getMeasuredWidth() / mAspectRatio;
 
@@ -170,7 +165,7 @@
                 // This should have been picked up by the aspect ratio check above...
                 throw new IllegalStateException(
                         "Neither target width nor target height was smaller than measured"
-                            + " width/height");
+                                + " width/height");
             }
         } else if (widthMeasureMode == MeasureSpec.EXACTLY) {
             // Can't change the width, but can change height.
@@ -182,10 +177,8 @@
 
             childView.measure(childWidth, childHeight);
 
-            // We're pulling some hacks here.
-            // We get an AT_MOST constraint, but if we oversize ourselves, the parent container
-            // should
-            // do appropriate clipping.
+            // We're pulling some hacks here. We get an AT_MOST constraint, but if we oversize
+            // ourselves, the parent container should do appropriate clipping.
             setMeasuredDimension(childView.getMeasuredWidth(), childView.getMeasuredHeight());
         } else if (heightMeasureMode == MeasureSpec.EXACTLY) {
             // Can't change height, change width.
@@ -200,12 +193,9 @@
             setMeasuredDimension(childView.getMeasuredWidth(), childView.getMeasuredHeight());
         } else {
             // This should never happen; the first if checks that both MeasureSpecs are either
-            // AT_MOST
-            // or UNSPECIFIED. If that branch isn't taken, one of the MeasureSpecs must be EXACTLY.
-            // It's
-            // technically possible to smash the flag bits though (mode == 3 is invalid), so if we
-            // get
-            // here, that must have happened.
+            // AT_MOST or UNSPECIFIED. If that branch isn't taken, one of the MeasureSpecs must be
+            // EXACTLY. It's technically possible to smash the flag bits though (mode == 3 is
+            // invalid), so if we get here, that must have happened.
             throw new IllegalArgumentException("Unknown measure mode bits in given MeasureSpecs");
         }
     }
@@ -215,16 +205,13 @@
         View childView = getChildAt(0);
 
         // Place the child view within the bounds. If the child is greater than the bounds (i.e. one
-        // of
-        // the constraints was MATCH_PARENT, and the other was free), then just align the top-left
-        // for
-        // now.
+        // of the constraints was MATCH_PARENT, and the other was free), then just align the
+        // top-left for now.
         childView.layout(0, 0, childView.getMeasuredWidth(), childView.getMeasuredHeight());
     }
 
     // setPadding(Relative) should just pass straight through to the child; this View should just be
-    // a
-    // wrapper, so should not itself introduce any extra spacing.
+    // a wrapper, so should not itself introduce any extra spacing.
     //
     // We don't override the getters, since nothing in the layout tree should actually use them.
     @Override
diff --git a/wear/tiles/tiles-renderer/src/main/java/androidx/wear/tiles/renderer/internal/ResourceResolvers.java b/wear/tiles/tiles-renderer/src/main/java/androidx/wear/tiles/renderer/internal/ResourceResolvers.java
index 5325714..5c7bd19 100644
--- a/wear/tiles/tiles-renderer/src/main/java/androidx/wear/tiles/renderer/internal/ResourceResolvers.java
+++ b/wear/tiles/tiles-renderer/src/main/java/androidx/wear/tiles/renderer/internal/ResourceResolvers.java
@@ -179,11 +179,9 @@
                         "Can't find resolver for image resource " + protoResourceId));
     }
 
-    /** Returns whether an image can be tinted or not. */
     public boolean canImageBeTinted(@NonNull String protoResourceId) {
         // Only Android image resources can be tinted for now. This is because we don't really know
-        // what
-        // is in an inline image.
+        // what is in an inline image.
         ResourceProto.ImageResource imageResource =
                 mProtoResources.getIdToImageMap().get(protoResourceId);
 
diff --git a/wear/tiles/tiles-renderer/src/main/java/androidx/wear/tiles/renderer/internal/TileRendererInternal.java b/wear/tiles/tiles-renderer/src/main/java/androidx/wear/tiles/renderer/internal/TileRendererInternal.java
index 7154cd0..55cd63b 100644
--- a/wear/tiles/tiles-renderer/src/main/java/androidx/wear/tiles/renderer/internal/TileRendererInternal.java
+++ b/wear/tiles/tiles-renderer/src/main/java/androidx/wear/tiles/renderer/internal/TileRendererInternal.java
@@ -117,7 +117,8 @@
 import androidx.wear.tiles.proto.StateProto.State;
 import androidx.wear.tiles.renderer.R;
 import androidx.wear.tiles.renderer.internal.ResourceResolvers.ResourceAccessException;
-import androidx.wear.tiles.renderer.internal.WearArcLayout.ArcLayoutWidget;
+import androidx.wear.widget.ArcLayout;
+import androidx.wear.widget.CurvedTextView;
 
 import com.google.common.util.concurrent.ListenableFuture;
 
@@ -142,9 +143,9 @@
     private static final int TEXT_ALIGN_DEFAULT = Gravity.CENTER_HORIZONTAL;
     private static final ScaleType IMAGE_DEFAULT_SCALE_TYPE = ScaleType.FIT_CENTER;
 
-    @WearArcLayout.LayoutParams.VerticalAlignment
+    @ArcLayout.LayoutParams.VerticalAlignment
     private static final int ARC_VERTICAL_ALIGN_DEFAULT =
-            WearArcLayout.LayoutParams.VERTICAL_ALIGN_CENTER;
+            ArcLayout.LayoutParams.VERTICAL_ALIGN_CENTER;
 
     private static final int SPAN_VERTICAL_ALIGN_DEFAULT = ImageSpan.ALIGN_BOTTOM;
 
@@ -162,8 +163,7 @@
                     .setWrappedDimension(WrappedDimensionProp.getDefaultInstance())
                     .build();
 
-    @WearArcLayout.AnchorType
-    private static final int ARC_ANCHOR_DEFAULT = WearArcLayout.ANCHOR_CENTER;
+    @ArcLayout.AnchorType private static final int ARC_ANCHOR_DEFAULT = ArcLayout.ANCHOR_CENTER;
 
     // White
     private static final int LINE_COLOR_DEFAULT = 0xFFFFFFFF;
@@ -302,13 +302,13 @@
         //
         // A Row (LinearLayout) supports this with width=0 and weight>0. After doing a layout pass,
         // it will assign all remaining space to elements with width=0 and weight>0, biased by the
-        // weight. This causes problems if there are two (or more) "expand" elements in a row,
-        // which is itself set to WRAP_CONTENTS, and one of those elements has a measured width
-        // (e.g. Text). In that case, the LinearLayout will measure the text, then ensure that
-        // all elements with a weight set have their widths set according to the weight. For us,
-        // that means that _all_ elements with expand=true will size themselves to the same width
-        // as the Text, pushing out the bounds of the parent row. This happens on columns too,
-        // but of course regarding height.
+        // weight. This causes problems if there are two (or more) "expand" elements in a row, which
+        // is itself set to WRAP_CONTENTS, and one of those elements has a measured width (e.g.
+        // Text). In that case, the LinearLayout will measure the text, then ensure that all
+        // elements with a weight set have their widths set according to the weight. For us, that
+        // means that _all_ elements with expand=true will size themselves to the same width as the
+        // Text, pushing out the bounds of the parent row. This happens on columns too, but of
+        // course regarding height.
         //
         // To get around this, if an element with expand=true is added to a row that is WRAP_CONTENT
         // (e.g. a row with no explicit width, that is not expanded), we ignore the expand=true, and
@@ -403,15 +403,15 @@
         return VERTICAL_ALIGN_DEFAULT_GRAVITY;
     }
 
-    @WearArcLayout.LayoutParams.VerticalAlignment
+    @ArcLayout.LayoutParams.VerticalAlignment
     private static int verticalAlignmentToArcVAlign(VerticalAlignmentProp alignment) {
         switch (alignment.getValue()) {
             case VERTICAL_ALIGN_TOP:
-                return WearArcLayout.LayoutParams.VERTICAL_ALIGN_OUTER;
+                return ArcLayout.LayoutParams.VERTICAL_ALIGN_OUTER;
             case VERTICAL_ALIGN_CENTER:
-                return WearArcLayout.LayoutParams.VERTICAL_ALIGN_CENTER;
+                return ArcLayout.LayoutParams.VERTICAL_ALIGN_CENTER;
             case VERTICAL_ALIGN_BOTTOM:
-                return WearArcLayout.LayoutParams.VERTICAL_ALIGN_INNER;
+                return ArcLayout.LayoutParams.VERTICAL_ALIGN_INNER;
             case UNRECOGNIZED:
             case VERTICAL_ALIGN_UNDEFINED:
                 return ARC_VERTICAL_ALIGN_DEFAULT;
@@ -459,10 +459,10 @@
     private static boolean isBold(FontStyle fontStyle) {
         // Although this method could be a simple equality check against FONT_WEIGHT_BOLD, we list
         // all current cases here so that this will become a compile time error as soon as a new
-        // FontWeight value is added to the schema. If this fails to build, then this means that
-        // an int typeface style is no longer enough to represent all FontWeight values and a
-        // customizable, per-weight text style must be introduced to TileRendererInternal to
-        // handle this. See b/176980535
+        // FontWeight value is added to the schema. If this fails to build, then this means that an
+        // int typeface style is no longer enough to represent all FontWeight values and a
+        // customizable, per-weight text style must be introduced to TileRendererInternal to handle
+        // this. See b/176980535
         switch (fontStyle.getWeight().getValue()) {
             case FONT_WEIGHT_BOLD:
                 return true;
@@ -538,8 +538,8 @@
 
     private void applyFontStyle(FontStyle style, TextView textView) {
         // Need to supply typefaceStyle when creating the typeface (will select specialist
-        // bold/italic typefaces), *and* when setting the typeface (will set synthetic
-        // bold/italic flags in Paint if they're not supported by the given typeface).
+        // bold/italic typefaces), *and* when setting the typeface (will set synthetic bold/italic
+        // flags in Paint if they're not supported by the given typeface).
         textView.setTypeface(createTypeface(style), fontStyleToTypefaceStyle(style));
 
         int currentPaintFlags = textView.getPaintFlags();
@@ -564,22 +564,15 @@
         textView.setTextColor(extractTextColorArgb(style));
     }
 
-    private void applyFontStyle(FontStyle style, WearCurvedTextView textView) {
+    private void applyFontStyle(FontStyle style, CurvedTextView textView) {
         // Need to supply typefaceStyle when creating the typeface (will select specialist
-        // bold/italic typefaces), *and* when setting the typeface (will set synthetic
-        // bold/italic flags in Paint if they're not supported by the given typeface).
+        // bold/italic typefaces), *and* when setting the typeface (will set synthetic bold/italic
+        // flags in Paint if they're not supported by the given typeface).
         textView.setTypeface(createTypeface(style), fontStyleToTypefaceStyle(style));
 
-        int currentPaintFlags = textView.getPaintFlags();
-
-        // Remove the bits we're setting
-        currentPaintFlags &= ~Paint.UNDERLINE_TEXT_FLAG;
-
-        if (style.hasUnderline() && style.getUnderline().getValue()) {
-            currentPaintFlags |= Paint.UNDERLINE_TEXT_FLAG;
-        }
-
-        textView.setPaintFlags(currentPaintFlags);
+        // TODO(b/188801917): Implement underline. CurvedTextView (well, drawTextOnArc) doesn't
+        // support underline. We can implement this later by drawing a line under the text ourselves
+        // though.
 
         if (style.hasSize()) {
             textView.setTextSize(toPx(style.getSize()));
@@ -716,14 +709,14 @@
         return view;
     }
 
-    // This is a little nasty; ArcLayoutWidget is just an interface, so we have no guarantee that
-    // the instance also extends View (as it should). Instead, just take a View in and rename
-    // this, and check that it's an ArcLayoutWidget internally.
+    // This is a little nasty; ArcLayout.Widget is just an interface, so we have no guarantee that
+    // the instance also extends View (as it should). Instead, just take a View in and rename this,
+    // and check that it's an ArcLayout.Widget internally.
     private View applyModifiersToArcLayoutView(View view, ArcModifiers modifiers) {
-        if (!(view instanceof ArcLayoutWidget)) {
+        if (!(view instanceof ArcLayout.Widget)) {
             Log.e(
                     TAG,
-                    "applyModifiersToArcLayoutView should only be called with an ArcLayoutWidget");
+                    "applyModifiersToArcLayoutView should only be called with an ArcLayout.Widget");
             return view;
         }
 
@@ -770,15 +763,15 @@
         return TEXT_OVERFLOW_DEFAULT;
     }
 
-    @WearArcLayout.AnchorType
+    @ArcLayout.AnchorType
     private static int anchorTypeToAnchorPos(ArcAnchorTypeProp type) {
         switch (type.getValue()) {
             case ARC_ANCHOR_START:
-                return WearArcLayout.ANCHOR_START;
+                return ArcLayout.ANCHOR_START;
             case ARC_ANCHOR_CENTER:
-                return WearArcLayout.ANCHOR_CENTER;
+                return ArcLayout.ANCHOR_CENTER;
             case ARC_ANCHOR_END:
-                return WearArcLayout.ANCHOR_END;
+                return ArcLayout.ANCHOR_END;
             case ARC_ANCHOR_UNDEFINED:
             case UNRECOGNIZED:
                 return ARC_ANCHOR_DEFAULT;
@@ -939,17 +932,17 @@
 
         // HACK: FrameLayout has a bug in it. If we add one WRAP_CONTENT child, and one MATCH_PARENT
         // child, the expected behaviour is that the FrameLayout sizes itself to fit the
-        // WRAP_CONTENT child (e.g. a TextView), then the MATCH_PARENT child is forced to the
-        // same size as the outer FrameLayout (and hence, the size of the TextView, after
-        // accounting for padding etc). Because of a bug though, this doesn't happen; instead,
-        // the MATCH_PARENT child will just keep its intrinsic size. This is because FrameLayout
-        // only forces MATCH_PARENT children to a given size if there are _more than one_ of them
-        // (see the bottom of FrameLayout#onMeasure).
+        // WRAP_CONTENT child (e.g. a TextView), then the MATCH_PARENT child is forced to the same
+        // size as the outer FrameLayout (and hence, the size of the TextView, after accounting for
+        // padding etc). Because of a bug though, this doesn't happen; instead, the MATCH_PARENT
+        // child will just keep its intrinsic size. This is because FrameLayout only forces
+        // MATCH_PARENT children to a given size if there are _more than one_ of them (see the
+        // 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();
@@ -1015,7 +1008,7 @@
         LayoutParams layoutParams = generateDefaultLayoutParams();
 
         space.setSweepAngleDegrees(lengthDegrees);
-        space.setThicknessPx(thicknessPx);
+        space.setThickness(thicknessPx);
 
         View wrappedView = applyModifiersToArcLayoutView(space, spacer.getModifiers());
         parent.addView(wrappedView, layoutParams);
@@ -1067,8 +1060,8 @@
     }
 
     private View inflateArcText(ViewGroup parent, ArcText text) {
-        WearCurvedTextView textView =
-                new WearCurvedTextView(
+        CurvedTextView textView =
+                new CurvedTextView(
                         mAppContext, /* attrs= */ null, R.attr.tilesFallbackTextAppearance);
 
         LayoutParams layoutParams = generateDefaultLayoutParams();
@@ -1217,17 +1210,20 @@
         parent.addView(wrappedView, ratioWrapperLayoutParams);
 
         ListenableFuture<Drawable> drawableFuture = mResourceResolvers.getDrawable(protoResId);
-        if (drawableFuture.isDone()) {
+        boolean isImageSet = false;
+        if (drawableFuture.isDone() && !drawableFuture.isCancelled()) {
             // If the future is done, immediately draw.
-            setImageDrawable(imageView, drawableFuture, protoResId);
-        } else {
+            isImageSet = setImageDrawable(imageView, drawableFuture, protoResId);
+        }
+
+        if (!isImageSet) {
             // Is there a placeholder to use in the meantime?
             try {
                 if (mResourceResolvers.hasPlaceholderDrawable(protoResId)) {
                     imageView.setImageDrawable(
                             mResourceResolvers.getPlaceholderDrawableOrThrow(protoResId));
                 }
-            } catch (ResourceAccessException ex) {
+            } catch (ResourceAccessException | IllegalArgumentException ex) {
                 Log.e(TAG, "Exception loading placeholder for resource " + protoResId, ex);
             }
 
@@ -1262,13 +1258,15 @@
         return wrappedView;
     }
 
-    private static void setImageDrawable(
+    private static boolean setImageDrawable(
             ImageView imageView, Future<Drawable> drawableFuture, String protoResId) {
         try {
             imageView.setImageDrawable(drawableFuture.get());
+            return true;
         } catch (ExecutionException | InterruptedException e) {
             Log.w(TAG, "Could not get drawable for image " + protoResId);
         }
+        return false;
     }
 
     @Nullable
@@ -1293,7 +1291,7 @@
             lineColor = line.getColor().getArgb();
         }
 
-        lineView.setThicknessPx(thicknessPx);
+        lineView.setThickness(thicknessPx);
         lineView.setSweepAngleDegrees(lengthDegrees);
         lineView.setColor(lineColor);
 
@@ -1305,7 +1303,7 @@
 
     @Nullable
     private View inflateArc(ViewGroup parent, Arc arc) {
-        WearArcLayout arcLayout = new WearArcLayout(mAppContext);
+        ArcLayout arcLayout = new ArcLayout(mAppContext);
 
         LayoutParams layoutParams = generateDefaultLayoutParams();
         layoutParams.width = LayoutParams.MATCH_PARENT;
@@ -1318,15 +1316,15 @@
         for (ArcLayoutElement child : arc.getContentsList()) {
             @Nullable View childView = inflateArcLayoutElement(arcLayout, child);
             if (childView != null) {
-                WearArcLayout.LayoutParams childLayoutParams =
-                        (WearArcLayout.LayoutParams) childView.getLayoutParams();
+                ArcLayout.LayoutParams childLayoutParams =
+                        (ArcLayout.LayoutParams) childView.getLayoutParams();
                 boolean rotate = false;
                 if (child.hasAdapter()) {
                     rotate = child.getAdapter().getRotateContents().getValue();
                 }
 
                 // Apply rotation and gravity.
-                childLayoutParams.setRotate(rotate);
+                childLayoutParams.setRotated(rotate);
                 childLayoutParams.setVerticalAlignment(
                         verticalAlignmentToArcVAlign(arc.getVerticalAlign()));
             }
@@ -1891,9 +1889,9 @@
         private static Typeface loadTypeface(TypedArray array, int styleableResId) {
             // Resources are a little nasty; we can't just check if resType =
             // TypedValue.TYPE_REFERENCE, because it never is (if you use @font/foo inside of
-            // styles.xml, the value will be a string of the form res/font/foo.ttf). Instead, see
-            // if there's a resource ID at all, and use that, otherwise assume it's a well known
-            // font family.
+            // styles.xml, the value will be a string of the form res/font/foo.ttf). Instead, see if
+            // there's a resource ID at all, and use that, otherwise assume it's a well known font
+            // family.
             int resType = array.getType(styleableResId);
 
             if (array.getResourceId(styleableResId, -1) != -1
diff --git a/wear/tiles/tiles-renderer/src/main/java/androidx/wear/tiles/renderer/internal/WearArcLayout.java b/wear/tiles/tiles-renderer/src/main/java/androidx/wear/tiles/renderer/internal/WearArcLayout.java
deleted file mode 100644
index 589a78f..0000000
--- a/wear/tiles/tiles-renderer/src/main/java/androidx/wear/tiles/renderer/internal/WearArcLayout.java
+++ /dev/null
@@ -1,747 +0,0 @@
-/*
- * Copyright 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.wear.tiles.renderer.internal;
-
-import static java.lang.Math.asin;
-import static java.lang.Math.max;
-import static java.lang.Math.round;
-
-import android.annotation.SuppressLint;
-import android.content.Context;
-import android.content.res.TypedArray;
-import android.graphics.Canvas;
-import android.graphics.Matrix;
-import android.util.AttributeSet;
-import android.util.DisplayMetrics;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.ViewGroup;
-
-import androidx.annotation.AttrRes;
-import androidx.annotation.IntDef;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.annotation.RestrictTo;
-import androidx.annotation.StyleRes;
-import androidx.annotation.UiThread;
-import androidx.wear.tiles.renderer.R;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-
-/**
- * Container which will lay its elements out on an arc. Elements will be relative to a given anchor
- * angle (where 0 degrees = 12 o clock), where the layout relative to the anchor angle is controlled
- * using {@code anchorAngleDegrees} and {@code anchorType}. The thickness of the arc is calculated
- * based on the child element with the greatest height (in the case of Android widgets), or greatest
- * thickness (for curved widgets). By default, the container lays its children one by one in
- * clockwise direction. The attribute 'clockwise' can be set to false to make the layout direction
- * as anti-clockwise. These two types of widgets will be drawn as follows.
- *
- * <p>Standard Android Widgets:
- *
- * <p>These widgets will be drawn as usual, but placed at the correct position on the arc, with the
- * correct amount of rotation applied. As an example, for an Android Text widget, the text baseline
- * would be drawn at a tangent to the arc. The arc length of a widget is obtained by measuring the
- * width of the widget, and transforming that to the length of an arc on a circle.
- *
- * <p>A standard Android widget will be measured as usual, but the maximum height constraint will be
- * capped at the minimum radius of the arc (i.e. width / 2).
- *
- * <p>"Curved" widgets:
- *
- * <p>Widgets which implement {@link ArcLayoutWidget} are expected to draw themselves within an arc
- * automatically. These widgets will be measured with the full dimensions of the arc container. They
- * are also expected to provide their thickness (used when calculating the thickness of the arc) and
- * the current sweep angle (used for laying out when drawing). Note that the WearArcLayout will
- * apply a rotation transform to the canvas before drawing this child; the inner child need not
- * perform any rotations itself.
- *
- * <p>An example of a widget which implements this interface is {@link WearCurvedTextView}, which
- * will lay itself out along the arc.
- */
-// TODO(b/174649543): Replace this with the actual androidx.wear.widget.WearArcLayout when
-// the next stable release of it is ready.
-// TODO(b/177464637): Resolve RestrictToUsage suppression.
-@SuppressWarnings("RestrictToUsage")
-@UiThread
-public class WearArcLayout extends ViewGroup {
-
-    /**
-     * Interface for a widget which knows it is being rendered inside an arc, and will draw itself
-     * accordingly. Any widget implementing this interface will receive the full-sized canvas,
-     * pre-rotated, in its draw call.
-     */
-    public interface ArcLayoutWidget {
-
-        /** Returns the sweep angle that this widget is drawn with. */
-        float getSweepAngleDegrees();
-
-        /** Returns the thickness of this widget inside the arc. */
-        int getThicknessPx();
-
-        /** Check whether the widget contains invalid attributes as a child of WearArcLayout */
-        void checkInvalidAttributeAsChild();
-
-        /**
-         * Return true when the given point is in the clickable area of the child widget. In
-         * particular, the coordinates should be considered as if the child was drawn centered at
-         * the default angle (12 o clock).
-         */
-        boolean insideClickArea(float x, float y);
-    }
-
-    /**
-     * Layout parameters for a widget added to an arc. This allows each element to specify whether
-     * or not it should be rotated(around the center of the child) when drawn inside the arc. For
-     * example, when the child is put at the center-bottom of the arc, whether the parent layout is
-     * responsible to rotate it 180 degree to draw it upside down.
-     *
-     * <p>Note that the {@code rotate} parameter is ignored when drawing "Fullscreen" elements.
-     */
-    public static class LayoutParams extends ViewGroup.MarginLayoutParams {
-
-        /**
-         * Vertical alignment of elements within the arc.
-         *
-         * @hide
-         */
-        @Retention(RetentionPolicy.SOURCE)
-        @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-        @IntDef({VERTICAL_ALIGN_OUTER, VERTICAL_ALIGN_CENTER, VERTICAL_ALIGN_INNER})
-        public @interface VerticalAlignment {}
-
-        /** Align to the outer edge of the parent WearArcLayout. */
-        public static final int VERTICAL_ALIGN_OUTER = 0;
-
-        /** Align to the center of the parent WearArcLayout. */
-        public static final int VERTICAL_ALIGN_CENTER = 1;
-
-        /** Align to the inner edge of the parent WearArcLayout. */
-        public static final int VERTICAL_ALIGN_INNER = 2;
-
-        private boolean mRotate = true;
-        @VerticalAlignment private int mVerticalAlignment = VERTICAL_ALIGN_CENTER;
-
-        // Internally used during layout/draw
-        // Stores the angle of the child, used to handle touch events.
-        float mMiddleAngle;
-
-        /**
-         * Creates a new set of layout parameters. The values are extracted from the supplied
-         * attributes set and context.
-         *
-         * @param context the application environment
-         * @param attrs the set of attributes from which to extract the layout parameters' values
-         */
-        public LayoutParams(@NonNull Context context, @NonNull AttributeSet attrs) {
-            super(context, attrs);
-
-            TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.WearArcLayout_Layout);
-
-            mRotate = a.getBoolean(R.styleable.WearArcLayout_Layout_layout_rotate, true);
-            mVerticalAlignment =
-                    a.getInt(R.styleable.WearArcLayout_Layout_layout_valign, VERTICAL_ALIGN_CENTER);
-
-            a.recycle();
-        }
-
-        /**
-         * Creates a new set of layout parameters with specified width and height
-         *
-         * @param width the width, either WRAP_CONTENT, MATCH_PARENT or a fixed size in pixels
-         * @param height the height, either WRAP_CONTENT, MATCH_PARENT or a fixed size in pixels
-         */
-        public LayoutParams(int width, int height) {
-            super(width, height);
-        }
-
-        /** Copy constructor */
-        public LayoutParams(@NonNull ViewGroup.LayoutParams source) {
-            super(source);
-        }
-
-        /**
-         * Gets whether the widget shall be rotated by the WearArcLayout container corresponding to
-         * its layout position angle
-         */
-        public boolean getRotate() {
-            return mRotate;
-        }
-
-        /**
-         * Sets whether the widget shall be rotated by the WearArcLayout container corresponding to
-         * its layout position angle
-         */
-        public void setRotate(boolean rotate) {
-            mRotate = rotate;
-        }
-
-        /** Gets how the widget is positioned vertically in the WearArcLayout. */
-        @VerticalAlignment
-        public int getVerticalAlignment() {
-            return mVerticalAlignment;
-        }
-
-        /**
-         * Sets how the widget is positioned vertically in the WearArcLayout.
-         *
-         * @param verticalAlignment align the widget to outer, inner edges or center.
-         */
-        public void setVerticalAlignment(@VerticalAlignment int verticalAlignment) {
-            mVerticalAlignment = verticalAlignment;
-        }
-    }
-
-    /**
-     * Annotation for anchor types.
-     *
-     * @hide
-     */
-    @Retention(RetentionPolicy.SOURCE)
-    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-    @IntDef({ANCHOR_START, ANCHOR_CENTER, ANCHOR_END})
-    public @interface AnchorType {}
-
-    /**
-     * Anchor at the start of the set of elements drawn within this container. This causes the first
-     * child to be drawn from {@code anchorAngle} degrees, to the right.
-     *
-     * <p>As an example, if this container contains two arcs, one having 10 degrees of sweep and the
-     * other having 20 degrees of sweep, the first will be drawn between 0-10 degrees, and the
-     * second between 10-30 degrees.
-     */
-    public static final int ANCHOR_START = 0;
-
-    /**
-     * Anchor at the center of the set of elements drawn within this container.
-     *
-     * <p>As an example, if this container contains two arcs, one having 10 degrees of sweep and the
-     * other having 20 degrees of sweep, the first will be drawn between -15 and -5 degrees, and the
-     * second between -5 and 15 degrees.
-     */
-    public static final int ANCHOR_CENTER = 1;
-
-    /**
-     * Anchor at the end of the set of elements drawn within this container. This causes the last
-     * element to end at {@code anchorAngle} degrees, with the other elements swept to the left.
-     *
-     * <p>As an example, if this container contains two arcs, one having 10 degrees of sweep and the
-     * other having 20 degrees of sweep, the first will be drawn between -30 and -20 degrees, and
-     * the second between -20 and 0 degrees.
-     */
-    public static final int ANCHOR_END = 2;
-
-    private static final float DEFAULT_START_ANGLE_DEGREES = 0f;
-    private static final boolean DEFAULT_LAYOUT_DIRECTION_IS_CLOCKWISE = true; // clockwise
-    @AnchorType private static final int DEFAULT_ANCHOR_TYPE = ANCHOR_START;
-
-    private int mThicknessPx = 0;
-
-    @AnchorType private int mAnchorType;
-    private float mAnchorAngleDegrees;
-    private boolean mClockwise;
-
-    @SuppressWarnings("SyntheticAccessor")
-    private final ChildArcAngles mChildArcAngles = new ChildArcAngles();
-
-    public WearArcLayout(@NonNull Context context) {
-        this(context, null);
-    }
-
-    public WearArcLayout(@NonNull Context context, @Nullable AttributeSet attrs) {
-        this(context, attrs, 0);
-    }
-
-    public WearArcLayout(
-            @NonNull Context context, @Nullable AttributeSet attrs, @AttrRes int defStyleAttr) {
-        this(context, attrs, defStyleAttr, 0);
-    }
-
-    public WearArcLayout(
-            @NonNull Context context,
-            @Nullable AttributeSet attrs,
-            @AttrRes int defStyleAttr,
-            @StyleRes int defStyleRes) {
-        super(context, attrs, defStyleAttr, defStyleRes);
-
-        TypedArray a =
-                context.obtainStyledAttributes(
-                        attrs, R.styleable.WearArcLayout, defStyleAttr, defStyleRes);
-
-        mAnchorType = a.getInt(R.styleable.WearArcLayout_anchorPosition, DEFAULT_ANCHOR_TYPE);
-        mAnchorAngleDegrees =
-                a.getFloat(
-                        R.styleable.WearArcLayout_anchorAngleDegrees, DEFAULT_START_ANGLE_DEGREES);
-        mClockwise =
-                a.getBoolean(
-                        R.styleable.WearArcLayout_clockwise, DEFAULT_LAYOUT_DIRECTION_IS_CLOCKWISE);
-
-        a.recycle();
-    }
-
-    @Override
-    public void requestLayout() {
-        super.requestLayout();
-
-        for (int i = 0; i < getChildCount(); i++) {
-            getChildAt(i).forceLayout();
-        }
-    }
-
-    @Override
-    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
-        // Need to derive the thickness of the curve from the children. We're a curve, so the
-        // children can only be sized up to (width or height)/2 units. This currently only
-        // supports fitting to a circle.
-        //
-        // No matter what, fit to the given size, be it a maximum or a fixed size. It doesn't make
-        // sense for this container to wrap its children.
-        int actualWidthPx = MeasureSpec.getSize(widthMeasureSpec);
-        int actualHeightPx = MeasureSpec.getSize(heightMeasureSpec);
-
-        if (MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.UNSPECIFIED
-                && MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.UNSPECIFIED) {
-            // We can't actually resolve this.
-            // Let's fit to the screen dimensions, for need of anything better...
-            DisplayMetrics displayMetrics = getContext().getResources().getDisplayMetrics();
-            actualWidthPx = displayMetrics.widthPixels;
-            actualHeightPx = displayMetrics.heightPixels;
-        }
-
-        // Fit to a square.
-        if (actualWidthPx < actualHeightPx) {
-            actualHeightPx = actualWidthPx;
-        } else if (actualHeightPx < actualWidthPx) {
-            actualWidthPx = actualHeightPx;
-        }
-
-        int maxChildDimension = actualHeightPx / 2;
-
-        // Measure all children in the new measurespec, and cache the largest.
-        int childMeasureSpec = MeasureSpec.makeMeasureSpec(maxChildDimension, MeasureSpec.AT_MOST);
-
-        // We need to do two measure passes. First, we need to measure all "normal" children, and
-        // get the thickness of all "CurvedContainer" children. Once we have that, we know the
-        // maximum thickness, and we can lay out the "CurvedContainer" children, taking into
-        // account their vertical alignment.
-        int maxChildHeightPx = 0;
-        int childState = 0;
-        for (int i = 0; i < getChildCount(); i++) {
-            View child = getChildAt(i);
-
-            if (child.getVisibility() == GONE) {
-                continue;
-            }
-
-            // ArcLayoutWidget is a special case. Because of how it draws, fit it to the size
-            // of the whole widget.
-            int childMeasuredHeight;
-            if (child instanceof ArcLayoutWidget) {
-                childMeasuredHeight = ((ArcLayoutWidget) child).getThicknessPx();
-            } else {
-                measureChild(
-                        child,
-                        getChildMeasureSpec(childMeasureSpec, 0, child.getLayoutParams().width),
-                        getChildMeasureSpec(childMeasureSpec, 0, child.getLayoutParams().height));
-                childMeasuredHeight = child.getMeasuredHeight();
-                childState = combineMeasuredStates(childState, child.getMeasuredState());
-            }
-            LayoutParams childLayoutParams = (LayoutParams) child.getLayoutParams();
-            maxChildHeightPx =
-                    max(
-                            maxChildHeightPx,
-                            childMeasuredHeight
-                                    + childLayoutParams.topMargin
-                                    + childLayoutParams.bottomMargin);
-        }
-
-        mThicknessPx = maxChildHeightPx;
-
-        // And now do the pass for the ArcLayoutWidgets
-        for (int i = 0; i < getChildCount(); i++) {
-            View child = getChildAt(i);
-
-            if (child.getVisibility() == GONE) {
-                continue;
-            }
-
-            if (child instanceof ArcLayoutWidget) {
-                LayoutParams childLayoutParams = (LayoutParams) child.getLayoutParams();
-
-                float insetPx = getChildTopInset(child);
-
-                int innerChildMeasureSpec =
-                        MeasureSpec.makeMeasureSpec(
-                                maxChildDimension * 2 - round(insetPx * 2), MeasureSpec.EXACTLY);
-
-                measureChild(
-                        child,
-                        getChildMeasureSpec(innerChildMeasureSpec, 0, childLayoutParams.width),
-                        getChildMeasureSpec(innerChildMeasureSpec, 0, childLayoutParams.height));
-
-                childState = combineMeasuredStates(childState, child.getMeasuredState());
-            }
-        }
-
-        setMeasuredDimension(
-                resolveSizeAndState(actualWidthPx, widthMeasureSpec, childState),
-                resolveSizeAndState(actualHeightPx, heightMeasureSpec, childState));
-    }
-
-    @Override
-    protected void onLayout(boolean changed, int l, int t, int r, int b) {
-        for (int i = 0; i < getChildCount(); i++) {
-            View child = getChildAt(i);
-
-            if (child.getVisibility() == GONE) {
-                continue;
-            }
-
-            // Curved container widgets have been measured so that the "arc" inside their widget
-            // will touch the outside of the box they have been measured in, taking into account
-            // the vertical alignment. Just grow them from the center.
-            if (child instanceof ArcLayoutWidget) {
-                int leftPx = round((getMeasuredWidth() / 2f) - (child.getMeasuredWidth() / 2f));
-                int topPx = round((getMeasuredHeight() / 2f) - (child.getMeasuredHeight() / 2f));
-
-                child.layout(
-                        leftPx,
-                        topPx,
-                        leftPx + child.getMeasuredWidth(),
-                        topPx + child.getMeasuredHeight());
-            } else {
-                // Normal widgets need to be placed on their canvas, taking into account their
-                // vertical position.
-                int leftPx = round((getMeasuredWidth() / 2f) - (child.getMeasuredWidth() / 2f));
-                int topPx = round(getChildTopInset(child));
-
-                child.layout(
-                        leftPx,
-                        topPx,
-                        leftPx + child.getMeasuredWidth(),
-                        topPx + child.getMeasuredHeight());
-            }
-        }
-
-        // Once dimensions are set, also layout the children in the arc, computing the
-        // center angle where they should be drawn.
-        float currentCumulativeAngle = calculateInitialRotation();
-        for (int i = 0; i < getChildCount(); i++) {
-            View child = getChildAt(i);
-
-            if (child.getVisibility() == GONE) {
-                continue;
-            }
-
-            calculateArcAngle(child, mChildArcAngles);
-            float preRotation =
-                    mChildArcAngles.leftMarginAsAngle + mChildArcAngles.actualChildAngle / 2f;
-            float multiplier = mClockwise ? 1f : -1f;
-
-            float middleAngle = multiplier * (currentCumulativeAngle + preRotation);
-            LayoutParams childLayoutParams = (LayoutParams) child.getLayoutParams();
-            childLayoutParams.mMiddleAngle = middleAngle;
-
-            currentCumulativeAngle += mChildArcAngles.getTotalAngle();
-        }
-    }
-
-    // When a view (that can handle it) receives a TOUCH_DOWN event, it will get all subsequent
-    // events until the touch is released, even if the pointer goes outside of it's bounds.
-    @Nullable private View mTouchedView = null;
-
-    @Override
-    public boolean onInterceptTouchEvent(@NonNull MotionEvent event) {
-        if (mTouchedView == null && event.getActionMasked() == MotionEvent.ACTION_DOWN) {
-            for (int i = 0; i < getChildCount(); i++) {
-                View child = getChildAt(i);
-                // Ensure that the view is visible
-                if (child.getVisibility() != VISIBLE) {
-                    continue;
-                }
-
-                // Map the event to the child's coordinate system
-                LayoutParams childLayoutParams = (LayoutParams) child.getLayoutParams();
-                float angle = childLayoutParams.mMiddleAngle;
-
-                float[] point = new float[] {event.getX(), event.getY()};
-                mapPoint(child, angle, point);
-
-                // Check if the click is actually in the child area
-                float x = point[0];
-                float y = point[1];
-
-                if (insideChildClickArea(child, x, y)) {
-                    mTouchedView = child;
-                    break;
-                }
-            }
-        }
-        // We can't do normal dispatching because it will capture touch in the original position
-        // of children.
-        return true;
-    }
-
-    private static boolean insideChildClickArea(View child, float x, float y) {
-        if (child instanceof ArcLayoutWidget) {
-            return ((ArcLayoutWidget) child).insideClickArea(x, y);
-        }
-        return x >= 0 && x < child.getMeasuredWidth() && y >= 0 && y < child.getMeasuredHeight();
-    }
-
-    // Map a point to local child coordinates.
-    private void mapPoint(View child, float angle, float[] point) {
-        float cx = getMeasuredWidth() / 2;
-        float cy = getMeasuredHeight() / 2;
-
-        Matrix m = new Matrix();
-        m.postRotate(-angle, cx, cy);
-        m.postTranslate(-child.getX(), -child.getY());
-        if (!(child instanceof ArcLayoutWidget)) {
-            LayoutParams childLayoutParams = (LayoutParams) child.getLayoutParams();
-            if (!childLayoutParams.getRotate()) {
-                m.postRotate(angle, child.getWidth() / 2, child.getHeight() / 2);
-            }
-        }
-        m.mapPoints(point);
-    }
-
-    @Override
-    @SuppressLint("ClickableViewAccessibility")
-    public boolean onTouchEvent(@NonNull MotionEvent event) {
-        @Nullable View touchedView = mTouchedView;
-        if (touchedView != null) {
-            // Map the event's coordinates to the child's coordinate space
-            float[] point = new float[] {event.getX(), event.getY()};
-            LayoutParams touchedViewLayoutParams = (LayoutParams) touchedView.getLayoutParams();
-            mapPoint(touchedView, touchedViewLayoutParams.mMiddleAngle, point);
-
-            float dx = point[0] - event.getX();
-            float dy = point[1] - event.getY();
-            event.offsetLocation(dx, dy);
-
-            touchedView.dispatchTouchEvent(event);
-
-            if (event.getActionMasked() == MotionEvent.ACTION_UP
-                    || event.getActionMasked() == MotionEvent.ACTION_CANCEL) {
-                // We have finished handling these series of events.
-                mTouchedView = null;
-            }
-            return true;
-        }
-        return false;
-    }
-
-    @Override
-    protected boolean drawChild(@NonNull Canvas canvas, @NonNull View child, long drawingTime) {
-        // Rotate the canvas to make the children render in the right place.
-        canvas.save();
-
-        LayoutParams childLayoutParams = (LayoutParams) child.getLayoutParams();
-        float middleAngle = childLayoutParams.mMiddleAngle;
-
-        // Rotate the child widget.
-        canvas.rotate(middleAngle, getMeasuredWidth() / 2f, getMeasuredHeight() / 2f);
-
-        if (child instanceof ArcLayoutWidget) {
-            ((ArcLayoutWidget) child).checkInvalidAttributeAsChild();
-        } else {
-            // Do we need to do some counter rotation?
-            LayoutParams layoutParams = (LayoutParams) child.getLayoutParams();
-
-            float angleToRotate = 0f;
-
-            if (layoutParams.getRotate()) {
-                // For counterclockwise layout, especially when mixing standard Android widget with
-                // ArcLayoutWidget as children, we might need to rotate the standard widget to make
-                // them have the same upwards direction.
-                if (!mClockwise) {
-                    angleToRotate = 180f;
-                }
-            } else {
-                // Un-rotate about the top of the canvas, around the center of the actual child.
-                // This compounds with the initial rotation into a translation.
-                angleToRotate = -middleAngle;
-            }
-
-            // Do the actual rotation. Note that the strange rotation center is because the child
-            // view is x-centered but at the top of this container.
-            float childInset = getChildTopInset(child);
-            canvas.rotate(
-                    angleToRotate,
-                    getMeasuredWidth() / 2f,
-                    child.getMeasuredHeight() / 2f + childInset);
-        }
-
-        boolean wasInvalidateIssued = super.drawChild(canvas, child, drawingTime);
-
-        canvas.restore();
-
-        return wasInvalidateIssued;
-    }
-
-    private float calculateInitialRotation() {
-        float multiplier = mClockwise ? 1f : -1f;
-        if (mAnchorType == ANCHOR_START) {
-            return multiplier * mAnchorAngleDegrees;
-        }
-
-        float totalArcAngle = 0;
-
-        for (int i = 0; i < getChildCount(); i++) {
-            calculateArcAngle(getChildAt(i), mChildArcAngles);
-            totalArcAngle += mChildArcAngles.getTotalAngle();
-        }
-
-        if (mAnchorType == ANCHOR_CENTER) {
-            return multiplier * mAnchorAngleDegrees - (totalArcAngle / 2f);
-        } else if (mAnchorType == ANCHOR_END) {
-            return multiplier * mAnchorAngleDegrees - totalArcAngle;
-        }
-
-        return 0;
-    }
-
-    private static float widthToAngleDegrees(float widthPx, float radiusPx) {
-        return (float) Math.toDegrees(2 * asin(widthPx / radiusPx / 2f));
-    }
-
-    private void calculateArcAngle(@NonNull View view, @NonNull ChildArcAngles childAngles) {
-        if (view.getVisibility() == GONE) {
-            childAngles.leftMarginAsAngle = 0;
-            childAngles.rightMarginAsAngle = 0;
-            childAngles.actualChildAngle = 0;
-            return;
-        }
-
-        float radiusPx = (getMeasuredWidth() / 2f) - mThicknessPx;
-
-        LayoutParams childLayoutParams = (LayoutParams) view.getLayoutParams();
-
-        childAngles.leftMarginAsAngle = widthToAngleDegrees(childLayoutParams.leftMargin, radiusPx);
-        childAngles.rightMarginAsAngle =
-                widthToAngleDegrees(childLayoutParams.rightMargin, radiusPx);
-
-        if (view instanceof ArcLayoutWidget) {
-            childAngles.actualChildAngle = ((ArcLayoutWidget) view).getSweepAngleDegrees();
-        } else {
-            childAngles.actualChildAngle = widthToAngleDegrees(view.getMeasuredWidth(), radiusPx);
-        }
-    }
-
-    private float getChildTopInset(@NonNull View child) {
-        LayoutParams childLayoutParams = (LayoutParams) child.getLayoutParams();
-
-        int childHeight =
-                child instanceof ArcLayoutWidget
-                        ? ((ArcLayoutWidget) child).getThicknessPx()
-                        : child.getMeasuredHeight();
-
-        int thicknessDiffPx =
-                mThicknessPx
-                        - childLayoutParams.topMargin
-                        - childLayoutParams.bottomMargin
-                        - childHeight;
-
-        int margin = mClockwise ? childLayoutParams.topMargin : childLayoutParams.bottomMargin;
-
-        switch (childLayoutParams.getVerticalAlignment()) {
-            case LayoutParams.VERTICAL_ALIGN_OUTER:
-                return margin;
-            case LayoutParams.VERTICAL_ALIGN_CENTER:
-                return margin + thicknessDiffPx / 2f;
-            case LayoutParams.VERTICAL_ALIGN_INNER:
-                return margin + thicknessDiffPx;
-            default:
-                // Nortmally unreachable...
-                return 0;
-        }
-    }
-
-    @Override
-    protected boolean checkLayoutParams(@Nullable ViewGroup.LayoutParams p) {
-        return p instanceof LayoutParams;
-    }
-
-    @Override
-    @NonNull
-    protected ViewGroup.LayoutParams generateLayoutParams(@NonNull ViewGroup.LayoutParams p) {
-        return new LayoutParams(p);
-    }
-
-    @Override
-    @NonNull
-    public ViewGroup.LayoutParams generateLayoutParams(@NonNull AttributeSet attrs) {
-        return new LayoutParams(getContext(), attrs);
-    }
-
-    @Override
-    @NonNull
-    protected ViewGroup.LayoutParams generateDefaultLayoutParams() {
-        return new LayoutParams(
-                ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
-    }
-
-    /** Returns the anchor type used for this container. */
-    @AnchorType
-    public int getAnchorType() {
-        return mAnchorType;
-    }
-
-    /** Sets the anchor type used for this container. */
-    public void setAnchorType(@AnchorType int anchorType) {
-        if (anchorType < ANCHOR_START || anchorType > ANCHOR_END) {
-            throw new IllegalArgumentException("Unknown anchor type");
-        }
-
-        mAnchorType = anchorType;
-        invalidate();
-    }
-
-    /** Returns the anchor angle used for this container, in degrees. */
-    public float getAnchorAngleDegrees() {
-        return mAnchorAngleDegrees;
-    }
-
-    /** Sets the anchor angle used for this container, in degrees. */
-    public void setAnchorAngleDegrees(float anchorAngleDegrees) {
-        mAnchorAngleDegrees = anchorAngleDegrees;
-        invalidate();
-    }
-
-    /** returns the layout direction */
-    public boolean getClockwise() {
-        return mClockwise;
-    }
-
-    /** Sets the layout direction */
-    public void setClockwise(boolean clockwise) {
-        mClockwise = clockwise;
-        invalidate();
-    }
-
-    private static class ChildArcAngles {
-        public float leftMarginAsAngle;
-        public float rightMarginAsAngle;
-        public float actualChildAngle;
-
-        public float getTotalAngle() {
-            return leftMarginAsAngle + rightMarginAsAngle + actualChildAngle;
-        }
-    }
-}
diff --git a/wear/tiles/tiles-renderer/src/main/java/androidx/wear/tiles/renderer/internal/WearCurvedLineView.java b/wear/tiles/tiles-renderer/src/main/java/androidx/wear/tiles/renderer/internal/WearCurvedLineView.java
index 61459b2..208edd2 100644
--- a/wear/tiles/tiles-renderer/src/main/java/androidx/wear/tiles/renderer/internal/WearCurvedLineView.java
+++ b/wear/tiles/tiles-renderer/src/main/java/androidx/wear/tiles/renderer/internal/WearCurvedLineView.java
@@ -33,6 +33,7 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.wear.tiles.renderer.R;
+import androidx.wear.widget.ArcLayout;
 
 /**
  * A line, drawn inside an arc.
@@ -41,7 +42,7 @@
  * the color to draw with. This widget will then draw an arc, with the specified thickness, around
  * its parent arc. The sweep angle is specified in degrees, clockwise.
  */
-public class WearCurvedLineView extends View implements WearArcLayout.ArcLayoutWidget {
+public class WearCurvedLineView extends View implements ArcLayout.Widget {
     private static final int DEFAULT_THICKNESS_PX = 0;
     private static final float DEFAULT_SWEEP_ANGLE_DEGREES = 0;
     @ColorInt private static final int DEFAULT_COLOR = 0xFFFFFFFF;
@@ -133,18 +134,13 @@
         updatePathAndPaint();
     }
 
-    @Override
-    public int getThicknessPx() {
-        return mThicknessPx;
-    }
-
     /** Sets the thickness of this arc in pixels. */
-    public void setThicknessPx(int thicknessPx) {
-        if (thicknessPx < 0) {
-            thicknessPx = 0;
+    public void setThickness(int thickness) {
+        if (thickness < 0) {
+            thickness = 0;
         }
 
-        this.mThicknessPx = thicknessPx;
+        this.mThicknessPx = thickness;
         updatePathAndPaint();
         invalidate();
     }
@@ -154,6 +150,11 @@
         return mSweepAngleDegrees;
     }
 
+    @Override
+    public int getThickness() {
+        return mThicknessPx;
+    }
+
     /** Sets the sweep angle of this arc in degrees. */
     public void setSweepAngleDegrees(float sweepAngleDegrees) {
         this.mSweepAngleDegrees = sweepAngleDegrees;
@@ -185,7 +186,7 @@
     }
 
     @Override
-    public boolean insideClickArea(float x, float y) {
+    public boolean isPointInsideClickArea(float x, float y) {
         // Stolen from WearCurvedTextView...
         float radius2 = min(getWidth(), getHeight()) / 2f - getPaddingTop();
         float radius1 = radius2 - mThicknessPx;
diff --git a/wear/tiles/tiles-renderer/src/main/java/androidx/wear/tiles/renderer/internal/WearCurvedSpacer.java b/wear/tiles/tiles-renderer/src/main/java/androidx/wear/tiles/renderer/internal/WearCurvedSpacer.java
index dd29df2..b6e6a55 100644
--- a/wear/tiles/tiles-renderer/src/main/java/androidx/wear/tiles/renderer/internal/WearCurvedSpacer.java
+++ b/wear/tiles/tiles-renderer/src/main/java/androidx/wear/tiles/renderer/internal/WearCurvedSpacer.java
@@ -24,13 +24,13 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.wear.tiles.renderer.R;
+import androidx.wear.widget.ArcLayout;
 
 /**
  * A lightweight curved widget that represents space between elements inside an Arc. This does no
- * rendering; it simply causes the parent {@link WearArcLayout} to advance by {@code
- * sweepAngleDegrees}.
+ * rendering; it simply causes the parent {@link ArcLayout} to advance by {@code sweepAngleDegrees}.
  */
-public class WearCurvedSpacer extends View implements WearArcLayout.ArcLayoutWidget {
+public class WearCurvedSpacer extends View implements ArcLayout.Widget {
 
     private static final float DEFAULT_SWEEP_ANGLE_DEGREES = 0f;
     private static final int DEFAULT_THICKNESS_PX = 0;
@@ -75,26 +75,26 @@
         return mSweepAngleDegrees;
     }
 
+    @Override
+    public int getThickness() {
+        return mThicknessPx;
+    }
+
     /** Sets the sweep angle of this spacer, in degrees. */
     public void setSweepAngleDegrees(float sweepAngleDegrees) {
         this.mSweepAngleDegrees = sweepAngleDegrees;
     }
 
-    @Override
-    public int getThicknessPx() {
-        return mThicknessPx;
-    }
-
     /** Sets the thickness of this spacer, in DP. */
-    public void setThicknessPx(int thicknessPx) {
-        this.mThicknessPx = thicknessPx;
+    public void setThickness(int thickness) {
+        this.mThicknessPx = thickness;
     }
 
     @Override
     public void checkInvalidAttributeAsChild() {}
 
     @Override
-    public boolean insideClickArea(float x, float y) {
+    public boolean isPointInsideClickArea(float x, float y) {
         return false;
     }
 }
diff --git a/wear/tiles/tiles-renderer/src/main/java/androidx/wear/tiles/renderer/internal/WearCurvedTextView.java b/wear/tiles/tiles-renderer/src/main/java/androidx/wear/tiles/renderer/internal/WearCurvedTextView.java
deleted file mode 100644
index 242d853..0000000
--- a/wear/tiles/tiles-renderer/src/main/java/androidx/wear/tiles/renderer/internal/WearCurvedTextView.java
+++ /dev/null
@@ -1,946 +0,0 @@
-/*
- * Copyright 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.wear.tiles.renderer.internal;
-
-import static java.lang.Math.cos;
-import static java.lang.Math.max;
-import static java.lang.Math.min;
-import static java.lang.Math.round;
-import static java.lang.Math.sin;
-
-import android.annotation.SuppressLint;
-import android.content.Context;
-import android.content.res.ColorStateList;
-import android.content.res.Resources;
-import android.content.res.TypedArray;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.Paint;
-import android.graphics.Path;
-import android.graphics.Rect;
-import android.graphics.Typeface;
-import android.os.Build;
-import android.text.StaticLayout;
-import android.text.TextPaint;
-import android.text.TextUtils;
-import android.util.AttributeSet;
-import android.view.MotionEvent;
-import android.view.View;
-
-import androidx.annotation.ColorInt;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.annotation.RequiresApi;
-import androidx.wear.tiles.renderer.R;
-
-/**
- * A WearCurvedTextView is a component allowing developers to easily write curved text following the
- * curvature of the largest circle that can be inscribed in the view. WearArcLayout could be used to
- * concatenate multiple curved texts, also layout together with other widgets such as icons.
- */
-// TODO(b/174649543): Replace this with the actual androidx.wear.widget.WearCurvedTextView when
-// the next stable release of it is ready.
-public final class WearCurvedTextView extends View implements WearArcLayout.ArcLayoutWidget {
-    private static final float UNSET_ANCHOR_DEGREE = -1f;
-    private static final int UNSET_ANCHOR_TYPE = -1;
-    private static final float MIN_SWEEP_DEGREE = 0f;
-    private static final float MAX_SWEEP_DEGREE = 359.9f;
-    private static final float DEFAULT_TEXT_SIZE = 24f;
-    @ColorInt private static final int DEFAULT_TEXT_COLOR = Color.WHITE;
-    private static final int DEFAULT_TEXT_STYLE = Typeface.NORMAL;
-    private static final boolean DEFAULT_CLOCKWISE = true;
-    private static final int FONT_WEIGHT_MAX = 1000;
-    private static final float ITALIC_SKEW_X = -0.25f;
-    // make 0 degree at 12 o'clock, since canvas assumes 0 degree is 3 o'clock
-    private static final float ANCHOR_DEGREE_OFFSET = -90f;
-
-    private final Path mPath = new Path();
-    private final Path mBgPath = new Path();
-    private final TextPaint mPaint = new TextPaint();
-    private final Rect mBounds = new Rect();
-    private final Rect mBgBounds = new Rect();
-    private boolean mDirty = true;
-    private String mTextToDraw = "";
-    private float mPathRadius = 0f;
-    private float mTextSweepDegrees = 0f;
-    private float mBackgroundSweepDegrees = MAX_SWEEP_DEGREE;
-    private int mLastUsedTextAlignment = -1;
-    private float mLocalRotateAngle = 0f;
-
-    private int mAnchorType = UNSET_ANCHOR_TYPE;
-    private float mAnchorAngleDegrees = UNSET_ANCHOR_DEGREE;
-    private float mMinSweepDegrees = MIN_SWEEP_DEGREE;
-    private float mMaxSweepDegrees = MAX_SWEEP_DEGREE;
-    private String mText = "";
-    private float mTextSize = DEFAULT_TEXT_SIZE;
-    @Nullable private Typeface mTypeface = null;
-    private boolean mClockwise = DEFAULT_CLOCKWISE;
-    @ColorInt private int mTextColor = DEFAULT_TEXT_COLOR;
-    @Nullable private TextUtils.TruncateAt mEllipsize = null;
-    private float mLetterSpacing = 0f;
-    @Nullable private String mFontFeatureSettings = null;
-    @Nullable private String mFontVariationSettings = null;
-
-    // If true, it means we got the touch_down event and are receiving the touch events that follow.
-    private boolean mHandlingTouch = false;
-
-    public WearCurvedTextView(@NonNull Context context) {
-        this(context, null);
-    }
-
-    public WearCurvedTextView(@NonNull Context context, @Nullable AttributeSet attrs) {
-        this(context, attrs, android.R.attr.textViewStyle);
-    }
-
-    public WearCurvedTextView(
-            @NonNull Context context, @Nullable AttributeSet attrs, int defStyle) {
-        this(context, attrs, defStyle, 0);
-    }
-
-    public WearCurvedTextView(
-            @NonNull Context context, @Nullable AttributeSet attrs, int defStyle, int defStyleRes) {
-        super(context, attrs, defStyle, defStyleRes);
-
-        mPaint.setAntiAlias(true);
-
-        TextAppearanceAttributes attributes = new TextAppearanceAttributes();
-        attributes.mTextColor = ColorStateList.valueOf(DEFAULT_TEXT_COLOR);
-
-        final Resources.Theme theme = context.getTheme();
-        TypedArray a =
-                theme.obtainStyledAttributes(
-                        attrs, R.styleable.TextViewAppearance, defStyle, defStyleRes);
-
-        TypedArray appearance = null;
-        int ap = a.getResourceId(R.styleable.TextViewAppearance_android_textAppearance, -1);
-        a.recycle();
-
-        if (ap != -1) {
-            appearance = theme.obtainStyledAttributes(ap, R.styleable.TextAppearance);
-        }
-        if (appearance != null) {
-            readTextAppearance(appearance, attributes, true);
-            appearance.recycle();
-        }
-
-        a =
-                context.obtainStyledAttributes(
-                        attrs, R.styleable.WearCurvedTextView, defStyle, defStyleRes);
-        // overrride the value in the appearance with explicitly specified attribute values
-        readTextAppearance(a, attributes, false);
-
-        // read the other supported TextView attributes
-        if (a.hasValue(R.styleable.WearCurvedTextView_android_text)) {
-            mText = nullToEmpty(a.getString(R.styleable.WearCurvedTextView_android_text));
-        }
-
-        int textEllipsize = a.getInt(R.styleable.WearCurvedTextView_android_ellipsize, 0);
-        switch (textEllipsize) {
-            case 1:
-                mEllipsize = TextUtils.TruncateAt.START;
-                break;
-            case 2:
-                mEllipsize = TextUtils.TruncateAt.MIDDLE;
-                break;
-            case 3:
-                mEllipsize = TextUtils.TruncateAt.END;
-                break;
-            default:
-                mEllipsize = null;
-        }
-
-        // read the custom WearCurvedTextView attributes
-        mMaxSweepDegrees =
-                a.getFloat(R.styleable.WearCurvedTextView_maxSweepDegrees, MAX_SWEEP_DEGREE);
-        mMaxSweepDegrees = min(mMaxSweepDegrees, MAX_SWEEP_DEGREE);
-        mMinSweepDegrees =
-                a.getFloat(R.styleable.WearCurvedTextView_minSweepDegrees, MIN_SWEEP_DEGREE);
-        if (mMinSweepDegrees > mMaxSweepDegrees) {
-            throw new IllegalArgumentException(
-                    "MinSweepDegrees cannot be bigger than MaxSweepDegrees");
-        }
-        mAnchorType = a.getInt(R.styleable.WearCurvedTextView_anchorPosition, UNSET_ANCHOR_TYPE);
-        mAnchorAngleDegrees =
-                a.getFloat(R.styleable.WearCurvedTextView_anchorAngleDegrees, UNSET_ANCHOR_DEGREE);
-        mAnchorAngleDegrees = mAnchorAngleDegrees % 360f;
-        mClockwise = a.getBoolean(R.styleable.WearCurvedTextView_clockwise, DEFAULT_CLOCKWISE);
-
-        a.recycle();
-
-        applyTextAppearance(attributes);
-
-        mPaint.setTextSize(mTextSize);
-    }
-
-    @Override
-    public float getSweepAngleDegrees() {
-        return mBackgroundSweepDegrees;
-    }
-
-    @Override
-    public int getThicknessPx() {
-        return round(mPaint.getFontMetrics().descent - mPaint.getFontMetrics().ascent);
-    }
-
-    /**
-     * @throws IllegalArgumentException if the anchorType and/or anchorAngleDegrees attributes were
-     *     set for a widget in WearArcLayout
-     */
-    @Override
-    public void checkInvalidAttributeAsChild() {
-        if (mAnchorType != UNSET_ANCHOR_TYPE) {
-            throw new IllegalArgumentException(
-                    "WearCurvedTextView shall not set anchorType value when added into"
-                            + "WearArcLayout");
-        }
-
-        if (mAnchorAngleDegrees != UNSET_ANCHOR_DEGREE) {
-            throw new IllegalArgumentException(
-                    "WearCurvedTextView shall not set anchorAngleDegrees value when added into "
-                            + "WearArcLayout");
-        }
-    }
-
-    @Override
-    public boolean insideClickArea(float x, float y) {
-        float radius2 =
-                min(getWidth(), getHeight()) / 2f
-                        - (mClockwise ? getPaddingTop() : getPaddingBottom());
-        float radius1 = radius2 - mPaint.getFontMetrics().descent + mPaint.getFontMetrics().ascent;
-
-        float dx = x - getWidth() / 2;
-        float dy = y - getHeight() / 2;
-
-        float r2 = dx * dx + dy * dy;
-        if (r2 < radius1 * radius1 || r2 > radius2 * radius2) {
-            return false;
-        }
-
-        // Since we are symmetrical on the Y-axis, we can constrain the angle to the x>=0 quadrants.
-        float angle = (float) Math.toDegrees(Math.atan2(Math.abs(dx), -dy));
-        return angle < mBackgroundSweepDegrees / 2;
-    }
-
-    @Override
-    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
-        super.onSizeChanged(w, h, oldw, oldh);
-        doUpdate();
-    }
-
-    @Override
-    protected void onLayout(boolean changed, int l, int t, int r, int b) {
-        super.onLayout(changed, l, t, r, b);
-
-        mPaint.getTextBounds(mText, 0, mText.length(), mBounds);
-
-        // Note that ascent is negative.
-        mPathRadius =
-                min(getWidth(), getHeight()) / 2f
-                        + (mClockwise
-                                ? mPaint.getFontMetrics().ascent - getPaddingTop()
-                                : -mPaint.getFontMetrics().descent - getPaddingBottom());
-        mTextSweepDegrees =
-                min(getWidthSelf() / mPathRadius / (float) Math.PI * 180f, MAX_SWEEP_DEGREE);
-        mBackgroundSweepDegrees = max(min(mMaxSweepDegrees, mTextSweepDegrees), mMinSweepDegrees);
-    }
-
-    private float getWidthSelf() {
-        return (float) mBounds.width() + getPaddingLeft() + getPaddingRight();
-    }
-
-    private String ellipsize(int ellipsizedWidth) {
-        StaticLayout.Builder layoutBuilder =
-                StaticLayout.Builder.obtain(mText, 0, mText.length(), mPaint, ellipsizedWidth);
-        layoutBuilder.setEllipsize(mEllipsize);
-        layoutBuilder.setMaxLines(1);
-        StaticLayout layout = layoutBuilder.build();
-
-        // Cut text that it's too big even if no ellipsize mode is provided.
-        if (mEllipsize == null) {
-            return mText.substring(0, layout.getLineEnd(0));
-        }
-
-        int ellipsisCount = layout.getEllipsisCount(0);
-        if (ellipsisCount == 0) {
-            return mText;
-        }
-
-        int ellipsisStart = layout.getEllipsisStart(0);
-        char[] textToDrawArray = mText.toCharArray();
-        textToDrawArray[ellipsisStart] = '\u2026'; // ellipsis "..."
-        for (int i = ellipsisStart + 1; i < ellipsisStart + ellipsisCount; i++) {
-            if (i >= 0 && i < mText.length()) {
-                textToDrawArray[i] = '\uFEFF'; // 0-width space
-            }
-        }
-        return new String(textToDrawArray);
-    }
-
-    private void updatePathsIfNeeded(boolean withBackground) {
-        // The dirty flag is not set when properties we inherit from View are modified
-        if (!mDirty && ((int) getTextAlignment() == mLastUsedTextAlignment)) {
-            return;
-        }
-
-        mDirty = false;
-        mLastUsedTextAlignment = (int) getTextAlignment();
-
-        if (mTextSweepDegrees <= mMaxSweepDegrees) {
-            mTextToDraw = mText;
-        } else {
-            mTextToDraw =
-                    ellipsize(
-                            (int) (Math.toRadians(mMaxSweepDegrees) * mPathRadius)
-                                    - getPaddingLeft()
-                                    - getPaddingRight());
-            mTextSweepDegrees = mMaxSweepDegrees;
-        }
-
-        float clockwiseFactor = mClockwise ? 1f : -1f;
-
-        float alignmentFactor = 0.5f;
-        switch (getTextAlignment()) {
-            case TEXT_ALIGNMENT_TEXT_START:
-            case TEXT_ALIGNMENT_VIEW_START:
-                alignmentFactor = 0f;
-                break;
-            case TEXT_ALIGNMENT_TEXT_END:
-            case TEXT_ALIGNMENT_VIEW_END:
-                alignmentFactor = 1f;
-                break;
-            default:
-                alignmentFactor = 0.5f; // TEXT_ALIGNMENT_CENTER
-        }
-
-        float anchorTypeFactor;
-        switch (mAnchorType) {
-            case WearArcLayout.ANCHOR_START:
-                anchorTypeFactor = 0.5f;
-                break;
-            case WearArcLayout.ANCHOR_END:
-                anchorTypeFactor = -0.5f;
-                break;
-            case WearArcLayout.ANCHOR_CENTER: // Center is the default.
-            default:
-                anchorTypeFactor = 0f;
-        }
-
-        mLocalRotateAngle =
-                (mAnchorAngleDegrees == UNSET_ANCHOR_DEGREE ? 0f : mAnchorAngleDegrees)
-                        + clockwiseFactor * anchorTypeFactor * mBackgroundSweepDegrees;
-
-        // Always draw the curved text on top center, then rotate the canvas to the right position
-        float backgroundStartAngle =
-                -clockwiseFactor * 0.5f * mBackgroundSweepDegrees + ANCHOR_DEGREE_OFFSET;
-
-        float textStartAngle =
-                backgroundStartAngle
-                        + clockwiseFactor
-                                * (float)
-                                        (alignmentFactor
-                                                        * (mBackgroundSweepDegrees
-                                                                - mTextSweepDegrees)
-                                                + Math.toDegrees(getPaddingLeft() / mPathRadius));
-
-        float centerX = getWidth() / 2f;
-        float centerY = getHeight() / 2f;
-        mPath.reset();
-        mPath.addArc(
-                centerX - mPathRadius,
-                centerY - mPathRadius,
-                centerX + mPathRadius,
-                centerY + mPathRadius,
-                textStartAngle,
-                clockwiseFactor * mTextSweepDegrees);
-
-        if (withBackground) {
-            mBgPath.reset();
-            // NOTE: Ensure that if the code to compute these radius* change, containsPoint() is
-            // also updated.
-            float radius1 = mPathRadius - clockwiseFactor * mPaint.getFontMetrics().descent;
-            float radius2 = mPathRadius - clockwiseFactor * mPaint.getFontMetrics().ascent;
-            mBgPath.arcTo(
-                    centerX - radius2,
-                    centerY - radius2,
-                    centerX + radius2,
-                    centerY + radius2,
-                    backgroundStartAngle,
-                    clockwiseFactor * mBackgroundSweepDegrees,
-                    false);
-            mBgPath.arcTo(
-                    centerX - radius1,
-                    centerY - radius1,
-                    centerX + radius1,
-                    centerY + radius1,
-                    backgroundStartAngle + clockwiseFactor * mBackgroundSweepDegrees,
-                    -clockwiseFactor * mBackgroundSweepDegrees,
-                    false);
-            mBgPath.close();
-
-            float angle = backgroundStartAngle;
-            float x0 = (float) (centerX + radius2 * cos(Math.toRadians(angle)));
-            float x1 = (float) (centerX + radius1 * cos(Math.toRadians(angle)));
-            float y0 = (float) (centerX + radius2 * sin(Math.toRadians(angle)));
-            float y1 = (float) (centerX + radius1 * sin(Math.toRadians(angle)));
-            angle = backgroundStartAngle + clockwiseFactor * mBackgroundSweepDegrees;
-            float x2 = (float) (centerX + radius2 * cos(Math.toRadians(angle)));
-            float x3 = (float) (centerX + radius1 * cos(Math.toRadians(angle)));
-            // Background axis-aligned bounding box calculation. Note that, we always center the
-            // text on the top-center of the view.
-            // top: always will be centerY - outerRadius
-            // bottom: the max y of end points of the outer and inner arc contains the text
-            // left: if over -90 degrees, centerX - outerRadius, otherwise the min x of start,
-            // end points of the outer and inner arc contains the text
-            // right: if over 90 degrees, centerX + outerRadius, otherwise the max x of start,
-            // end points of the outer and inner arc contains the text
-            float outerRadius = max(radius1, radius2);
-            mBgBounds.top = (int) (centerY - outerRadius);
-            mBgBounds.bottom = (int) max(y0, y1);
-            mBgBounds.left =
-                    mBackgroundSweepDegrees >= 180.0f
-                            ? (int) (centerX - outerRadius)
-                            : (int) min(x0, min(x1, min(x2, x3)));
-            mBgBounds.right =
-                    mBackgroundSweepDegrees >= 180.0f
-                            ? (int) (centerX + outerRadius)
-                            : (int) max(x0, max(x1, max(x2, x3)));
-        }
-    }
-
-    @Override
-    // We only filter events and defer to super.onTouchEvent()
-    @SuppressLint("ClickableViewAccessibility")
-    public boolean onTouchEvent(@NonNull MotionEvent event) {
-        if (!mHandlingTouch && event.getAction() != MotionEvent.ACTION_DOWN) {
-            return false;
-        }
-
-        float x0 = event.getX() - getWidth() / 2;
-        float y0 = event.getY() - getHeight() / 2;
-
-        double rotAngle = -Math.toRadians(mLocalRotateAngle);
-
-        float tempX = (float) ((x0 * cos(rotAngle) - y0 * sin(rotAngle)) + getWidth() / 2);
-        y0 = (float) ((x0 * sin(rotAngle) + y0 * cos(rotAngle)) + getHeight() / 2);
-        x0 = tempX;
-
-        // Should we start handling the touch events?
-        if (!mHandlingTouch && insideClickArea(x0, y0)) {
-            mHandlingTouch = true;
-        }
-
-        // We just started or are in the middle of handling events, forward to View to handle.
-        if (mHandlingTouch) {
-            if (event.getAction() == MotionEvent.ACTION_UP
-                    || event.getAction() == MotionEvent.ACTION_CANCEL) {
-                // We should end handling events now
-                mHandlingTouch = false;
-            }
-            event.offsetLocation(x0 - event.getX(), y0 - event.getY());
-            return super.onTouchEvent(event);
-        }
-
-        return false;
-    }
-
-    @Override
-    public void draw(@NonNull Canvas canvas) {
-        canvas.save();
-
-        boolean withBackground = getBackground() != null;
-        updatePathsIfNeeded(withBackground);
-        canvas.rotate(mLocalRotateAngle, getWidth() / 2f, getHeight() / 2f);
-
-        if (withBackground) {
-            canvas.clipPath(mBgPath);
-            getBackground().setBounds(mBgBounds);
-        }
-        super.draw(canvas);
-
-        canvas.restore();
-    }
-
-    @Override
-    protected void onDraw(@NonNull Canvas canvas) {
-        mPaint.setColor(mTextColor);
-        mPaint.setStyle(Paint.Style.FILL);
-        canvas.drawTextOnPath(mTextToDraw, mPath, 0f, 0f, mPaint);
-    }
-
-    /**
-     * Sets the Typeface taking into account the given attributes.
-     *
-     * @param familyName family name string, e.g. "serif"
-     * @param typefaceIndex an index of the typeface enum, e.g. SANS, SERIF.
-     * @param style a typeface style
-     * @param weight a weight value for the Typeface or -1 if not specified.
-     */
-    private void setTypefaceFromAttrs(
-            @Nullable String familyName, int typefaceIndex, int style, int weight) {
-        // typeface is ignored when font family is set
-        if (mTypeface == null && familyName != null) {
-            // Lookup normal Typeface from system font map.
-            Typeface normalTypeface = Typeface.create(familyName, Typeface.NORMAL);
-            resolveStyleAndSetTypeface(normalTypeface, style, weight);
-        } else if (mTypeface != null) {
-            resolveStyleAndSetTypeface(mTypeface, style, weight);
-        } else { // both typeface and familyName is null.
-            switch (typefaceIndex) {
-                case 1:
-                    resolveStyleAndSetTypeface(Typeface.SANS_SERIF, style, weight);
-                    break;
-                case 2:
-                    resolveStyleAndSetTypeface(Typeface.SERIF, style, weight);
-                    break;
-                case 3:
-                    resolveStyleAndSetTypeface(Typeface.MONOSPACE, style, weight);
-                    break;
-                default:
-                    resolveStyleAndSetTypeface(null, style, weight);
-            }
-        }
-    }
-
-    private void resolveStyleAndSetTypeface(@Nullable Typeface tf, int style, int weight) {
-        if (weight >= 0 && Build.VERSION.SDK_INT >= 28) {
-            int clampedWeight = min(FONT_WEIGHT_MAX, weight);
-            boolean italic = (style & Typeface.ITALIC) != 0;
-            mTypeface = Api28Impl.createTypeface(tf, clampedWeight, italic);
-            mPaint.setTypeface(mTypeface);
-        } else {
-            setTypeface(tf, style);
-        }
-    }
-
-    /** Set of attribute that can be defined in a Text Appearance. */
-    private static class TextAppearanceAttributes {
-        @Nullable ColorStateList mTextColor = null;
-        float mTextSize = DEFAULT_TEXT_SIZE;
-        @Nullable String mFontFamily = null;
-        boolean mFontFamilyExplicit = false;
-        int mTypefaceIndex = -1;
-        int mTextStyle = DEFAULT_TEXT_STYLE;
-        int mFontWeight = -1;
-        float mLetterSpacing = 0f;
-        @Nullable String mFontFeatureSettings = null;
-        @Nullable String mFontVariationSettings = null;
-
-        TextAppearanceAttributes() {}
-    }
-
-    /** Sets the textColor, size, style, font etc from the specified TextAppearanceAttributes */
-    private void applyTextAppearance(WearCurvedTextView this, TextAppearanceAttributes attributes) {
-        if (attributes.mTextColor != null) {
-            mTextColor = attributes.mTextColor.getDefaultColor();
-        }
-
-        if (attributes.mTextSize != -1f) {
-            mTextSize = attributes.mTextSize;
-        }
-
-        setTypefaceFromAttrs(
-                attributes.mFontFamily,
-                attributes.mTypefaceIndex,
-                attributes.mTextStyle,
-                attributes.mFontWeight);
-
-        mPaint.setLetterSpacing(attributes.mLetterSpacing);
-        mLetterSpacing = attributes.mLetterSpacing;
-
-        // setFontFeatureSettings does accept null, but is not annotated as such. Empty string does
-        // the
-        // same thing though.
-        mPaint.setFontFeatureSettings(nullToEmpty(attributes.mFontFeatureSettings));
-        mFontFeatureSettings = attributes.mFontFeatureSettings;
-        if (Build.VERSION.SDK_INT >= 26 && attributes.mFontVariationSettings != null) {
-            Api26Impl.paintSetFontVariationSettings(mPaint, attributes.mFontVariationSettings);
-        }
-        mFontVariationSettings = attributes.mFontVariationSettings;
-    }
-
-    /**
-     * Read the Text Appearance attributes from a given TypedArray and set its values to the given
-     * set. If the TypedArray contains a value that already set in the given attributes, that will
-     * be overridden.
-     */
-    private static void readTextAppearance(
-            TypedArray appearance, TextAppearanceAttributes attributes, boolean isTextAppearance) {
-        int attrIndex =
-                isTextAppearance
-                        ? R.styleable.TextAppearance_android_textColor
-                        : R.styleable.WearCurvedTextView_android_textColor;
-        if (appearance.hasValue(attrIndex)) {
-            attributes.mTextColor = appearance.getColorStateList(attrIndex);
-        }
-
-        attributes.mTextSize =
-                appearance.getDimensionPixelSize(
-                        isTextAppearance
-                                ? R.styleable.TextAppearance_android_textSize
-                                : R.styleable.WearCurvedTextView_android_textSize,
-                        (int) attributes.mTextSize);
-
-        attributes.mTextStyle =
-                appearance.getInt(
-                        isTextAppearance
-                                ? R.styleable.TextAppearance_android_textStyle
-                                : R.styleable.WearCurvedTextView_android_textStyle,
-                        attributes.mTextStyle);
-
-        // make sure that the typeface attribute is read before fontFamily attribute
-        attributes.mTypefaceIndex =
-                appearance.getInt(
-                        isTextAppearance
-                                ? R.styleable.TextAppearance_android_typeface
-                                : R.styleable.WearCurvedTextView_android_typeface,
-                        attributes.mTypefaceIndex);
-        if (attributes.mTypefaceIndex != -1 && !attributes.mFontFamilyExplicit) {
-            attributes.mFontFamily = null;
-        }
-
-        attrIndex =
-                isTextAppearance
-                        ? R.styleable.TextAppearance_android_fontFamily
-                        : R.styleable.WearCurvedTextView_android_fontFamily;
-        if (appearance.hasValue(attrIndex)) {
-            attributes.mFontFamily = appearance.getString(attrIndex);
-            attributes.mFontFamilyExplicit = !isTextAppearance;
-        }
-
-        attributes.mFontWeight =
-                appearance.getInt(
-                        isTextAppearance
-                                ? R.styleable.TextAppearance_android_textFontWeight
-                                : R.styleable.WearCurvedTextView_android_textFontWeight,
-                        attributes.mFontWeight);
-
-        attributes.mLetterSpacing =
-                appearance.getFloat(
-                        isTextAppearance
-                                ? R.styleable.TextAppearance_android_letterSpacing
-                                : R.styleable.WearCurvedTextView_android_letterSpacing,
-                        attributes.mLetterSpacing);
-
-        attrIndex =
-                isTextAppearance
-                        ? R.styleable.TextAppearance_android_fontFeatureSettings
-                        : R.styleable.WearCurvedTextView_android_fontFeatureSettings;
-        if (appearance.hasValue(attrIndex)) {
-            attributes.mFontFeatureSettings = appearance.getString(attrIndex);
-        }
-
-        attrIndex =
-                isTextAppearance
-                        ? R.styleable.TextAppearance_android_fontVariationSettings
-                        : R.styleable.WearCurvedTextView_android_fontVariationSettings;
-        if (appearance.hasValue(attrIndex)) {
-            attributes.mFontVariationSettings = appearance.getString(attrIndex);
-        }
-    }
-
-    private void doUpdate() {
-        mDirty = true;
-        requestLayout();
-        postInvalidate();
-    }
-
-    private void doRedraw() {
-        mDirty = true;
-        postInvalidate();
-    }
-
-    /** returns the anchor type for positioning the curved text */
-    public int getAnchorType() {
-        return mAnchorType;
-    }
-
-    /**
-     * Sets the anchor type for positioning the curved text.
-     *
-     * @param value the anchor type, one of {ANCHOR_START, ANCHOR_CENTER, ANCHOR_END}
-     */
-    public void setAnchorType(@WearArcLayout.AnchorType int value) {
-        mAnchorType = value;
-        doUpdate();
-    }
-
-    /** Returns the anchor angle used for positioning the text, in degrees. */
-    public float getAnchorAngleDegrees() {
-        return mAnchorAngleDegrees;
-    }
-
-    /** Sets the anchor angle used for positioning the text, in degrees. */
-    public void setAnchorAngleDegrees(float value) {
-        mAnchorAngleDegrees = value;
-        doRedraw();
-    }
-
-    /** returns the maximum sweep angle in degrees for rendering the text */
-    public float getMaxSweepDegrees() {
-        return mMaxSweepDegrees;
-    }
-
-    /** sets the maximum sweep angle in degrees for rendering the text */
-    public void setMaxSweepDegrees(float value) {
-        if (value < mMinSweepDegrees) {
-            throw new IllegalArgumentException(
-                    "MaxSweepDegrees cannot be smaller than MinSweepDegrees");
-        }
-        mMaxSweepDegrees = min(value, MAX_SWEEP_DEGREE);
-        doUpdate();
-    }
-    /** returns the sweep angle in degrees for rendering the text */
-    public float getMinSweepDegrees() {
-        return mMinSweepDegrees;
-    }
-
-    /** sets the sweep angle in degrees for rendering the text */
-    public void setMinSweepDegrees(float value) {
-        if (value > mMaxSweepDegrees) {
-            throw new IllegalArgumentException(
-                    "MinSweepDegrees cannot be bigger than MaxSweepDegrees");
-        }
-        mMinSweepDegrees = value;
-        doUpdate();
-    }
-
-    /** returns the text to be rendered */
-    @Nullable
-    public String getText() {
-        return mText;
-    }
-
-    /** sets the text to be rendered */
-    public void setText(@Nullable String value) {
-        mText = nullToEmpty(value);
-        doUpdate();
-    }
-
-    /** returns the text size for rendering the text */
-    public float getTextSize() {
-        return mTextSize;
-    }
-
-    /** sets the text size for rendering the text */
-    public void setTextSize(float value) {
-        mTextSize = value;
-        mPaint.setTextSize(mTextSize);
-        doUpdate();
-    }
-
-    /** Gets the current Typeface that is used to style the text. */
-    @Nullable
-    public Typeface getTypeface() {
-        return mTypeface;
-    }
-
-    /**
-     * Sets the typeface and style in which the text should be displayed. Note that not all Typeface
-     * families actually have bold and italic variants
-     */
-    public void setTypeface(@Nullable Typeface value) {
-        mTypeface = value;
-        doUpdate();
-    }
-
-    /**
-     * Sets the typeface and style in which the text should be displayed, and turns on the fake bold
-     * and italic bits in the Paint if the Typeface that you provided does not have all the bits in
-     * the style that you specified.
-     */
-    public void setTypeface(@Nullable Typeface tf, int style) {
-        if (style > 0) {
-            if (tf == null) {
-                tf = Typeface.defaultFromStyle(style);
-            } else {
-                tf = Typeface.create(tf, style);
-            }
-            if (!tf.equals(mPaint.getTypeface())) {
-                mPaint.setTypeface(tf);
-                mTypeface = tf;
-            }
-            // now compute what (if any) algorithmic styling is needed
-            int typefaceStyle = tf != null ? tf.getStyle() : 0;
-            int need = style & ~typefaceStyle;
-            mPaint.setFakeBoldText((need & Typeface.BOLD) != 0);
-            mPaint.setTextSkewX(((need & Typeface.ITALIC) != 0) ? ITALIC_SKEW_X : 0f);
-        } else {
-            mPaint.setFakeBoldText(false);
-            mPaint.setTextSkewX(0f);
-            if ((tf != null && !tf.equals(mPaint.getTypeface()))
-                    || (tf == null && mPaint.getTypeface() != null)) {
-                mPaint.setTypeface(tf);
-                mTypeface = tf;
-            }
-        }
-
-        doUpdate();
-    }
-
-    /** returns the curved text layout direction */
-    public boolean getClockwise() {
-        return mClockwise;
-    }
-
-    /** sets the curved text layout direction */
-    public void setClockwise(boolean value) {
-        mClockwise = value;
-        doUpdate();
-    }
-
-    /** returns the color for rendering the text */
-    @ColorInt
-    public int getTextColor() {
-        return mTextColor;
-    }
-
-    /** sets the color for rendering the text */
-    public void setTextColor(@ColorInt int value) {
-        mTextColor = value;
-        doRedraw();
-    }
-
-    /**
-     * Returns where, if anywhere, words that are longer than the view is wide should be ellipsized.
-     */
-    @Nullable
-    public TextUtils.TruncateAt getEllipsize() {
-        return mEllipsize;
-    }
-
-    /**
-     * Causes words in the text that are longer than the view's width to be ellipsized. Use null to
-     * turn off ellipsizing.
-     */
-    public void setEllipsize(@Nullable TextUtils.TruncateAt value) {
-        mEllipsize = value;
-        doRedraw();
-    }
-
-    /**
-     * Gets the text letter-space value, which determines the spacing between characters. The value
-     * returned is in ems. Normally, this value is 0.0.
-     *
-     * @return The text letter-space value in ems.
-     */
-    public float getLetterSpacing() {
-        return mLetterSpacing;
-    }
-
-    /**
-     * Sets text letter-spacing in ems. Typical values for slight expansion will be around 0.05.
-     * Negative values tighten text.
-     *
-     * @param value A text letter-space value in ems.
-     */
-    public void setLetterSpacing(float value) {
-        mLetterSpacing = value;
-        doUpdate();
-    }
-
-    /**
-     * Returns the font feature settings. The format is the same as the CSS font-feature-settings
-     * attribute: https://www.w3.org/TR/css-fonts-3/#font-feature-settings-prop
-     *
-     * @return the currently set font feature settings. Default is null.
-     */
-    @Nullable
-    public String getFontFeatureSettings() {
-        return mFontFeatureSettings;
-    }
-
-    /**
-     * Sets font feature settings. The format is the same as the CSS font-feature-settings
-     * attribute: https://www.w3.org/TR/css-fonts-3/#font-feature-settings-prop
-     *
-     * @param value font feature settings represented as CSS compatible string. This value may be
-     *     null.
-     */
-    public void setFontFeatureSettings(@Nullable String value) {
-        mFontFeatureSettings = value;
-        doUpdate();
-    }
-
-    /** Returns TrueType or OpenType font variation settings. */
-    @Nullable
-    public String getFontVariationSettings() {
-        return mFontVariationSettings;
-    }
-
-    /**
-     * Sets TrueType or OpenType font variation settings.
-     *
-     * @param value font variation settings. You can pass null or empty string as no variation
-     *     settings. This value may be null
-     */
-    public void setFontVariationSettings(@Nullable String value) {
-        mFontVariationSettings = value;
-        doUpdate();
-    }
-
-    /**
-     * Gets the flags on the Paint being used to display the text.
-     *
-     * @return The flags on the Paint being used to display the text.
-     * @see Paint#getFlags
-     */
-    public int getPaintFlags() {
-        return mPaint.getFlags();
-    }
-
-    /**
-     * Sets flags on the Paint being used to display the text and reflows the text if they are
-     * different from the old flags.
-     *
-     * @see Paint#setFlags
-     */
-    public void setPaintFlags(int flags) {
-        if (mPaint.getFlags() != flags) {
-            mPaint.setFlags(flags);
-            doUpdate();
-        }
-    }
-
-    @NonNull
-    private String nullToEmpty(@Nullable String str) {
-        if (str == null) {
-            return "";
-        } else {
-            return str;
-        }
-    }
-
-    /** Nested class to avoid verification errors for methods induces in API level 26 */
-    @RequiresApi(26)
-    private static class Api26Impl {
-        private Api26Impl() {}
-
-        static void paintSetFontVariationSettings(Paint paint, String fontVariationSettings) {
-            paint.setFontVariationSettings(fontVariationSettings);
-        }
-    }
-
-    /** Nested class to avoid verification errors for methods induces in API level 28 */
-    @RequiresApi(28)
-    private static class Api28Impl {
-        private Api28Impl() {}
-
-        static Typeface createTypeface(@Nullable Typeface family, int weight, boolean italic) {
-            return Typeface.create(family, weight, italic);
-        }
-    }
-}
diff --git a/wear/tiles/tiles-renderer/src/main/java/androidx/wear/tiles/timeline/internal/TilesTimelineCacheInternal.java b/wear/tiles/tiles-renderer/src/main/java/androidx/wear/tiles/timeline/internal/TilesTimelineCacheInternal.java
index a454af9..91dc111 100644
--- a/wear/tiles/tiles-renderer/src/main/java/androidx/wear/tiles/timeline/internal/TilesTimelineCacheInternal.java
+++ b/wear/tiles/tiles-renderer/src/main/java/androidx/wear/tiles/timeline/internal/TilesTimelineCacheInternal.java
@@ -195,8 +195,7 @@
             }
 
             // Discard if it's less than "fromTime". This prevents accidentally returning valid
-            // times in
-            // the past.
+            // times in the past.
             if (nextEntryValidity.getStartMillis() < fromTimeMillis) {
                 continue;
             }
diff --git a/wear/tiles/tiles-renderer/src/main/res/values/attrs.xml b/wear/tiles/tiles-renderer/src/main/res/values/attrs.xml
index 81f80a1..9ec117e 100644
--- a/wear/tiles/tiles-renderer/src/main/res/values/attrs.xml
+++ b/wear/tiles/tiles-renderer/src/main/res/values/attrs.xml
@@ -12,28 +12,6 @@
   <attr name="sweepAngleDegrees" format="float" />
   <attr name="thickness" format="dimension" />
 
-  <declare-styleable name="WearCurvedTextView">
-    <!-- supported TextView attributes -->
-    <attr name="android:text" />
-    <attr name="android:textSize" />
-    <attr name="android:textColor" />
-    <attr name="android:typeface" />
-    <attr name="android:fontFamily" />
-    <attr name="android:textStyle" />
-    <attr name="android:textFontWeight" />
-    <attr name="android:letterSpacing" />
-    <attr name="android:fontFeatureSettings" />
-    <attr name="android:fontVariationSettings" />
-    <attr name="android:textAppearance" />
-    <attr name="android:ellipsize" />
-    <!-- custom WearCurvedTextView Attributes -->
-    <attr name="anchorAngleDegrees" />
-    <attr name="anchorPosition" />
-    <attr name="minSweepDegrees" format="float" />
-    <attr name="maxSweepDegrees" format="float" />
-    <attr name="clockwise" />
-  </declare-styleable>
-
   <declare-styleable name="TextAppearance">
     <!-- Text color. -->
     <attr name="android:textColor" />
@@ -60,21 +38,6 @@
     <attr name="android:textAppearance" />
   </declare-styleable>
 
-  <declare-styleable name="WearArcLayout_Layout">
-    <attr name="layout_rotate" format="boolean" />
-    <attr name="layout_valign" format="enum">
-      <enum name="outer" value="0" />
-      <enum name="center" value="1" />
-      <enum name="inner" value="2" />
-    </attr>
-  </declare-styleable>
-
-  <declare-styleable name="WearArcLayout">
-    <attr name="anchorAngleDegrees"/>
-    <attr name="anchorPosition"/>
-    <attr name="clockwise" />
-  </declare-styleable>
-
   <declare-styleable name="WearCurvedLineView">
     <attr name="sweepAngleDegrees" />
     <attr name="thickness" />
diff --git a/wear/tiles/tiles-renderer/src/test/java/androidx/wear/tiles/manager/UpdateSchedulerTest.java b/wear/tiles/tiles-renderer/src/test/java/androidx/wear/tiles/manager/UpdateSchedulerTest.java
index 95c752c..c96a0bc 100644
--- a/wear/tiles/tiles-renderer/src/test/java/androidx/wear/tiles/manager/UpdateSchedulerTest.java
+++ b/wear/tiles/tiles-renderer/src/test/java/androidx/wear/tiles/manager/UpdateSchedulerTest.java
@@ -54,9 +54,9 @@
     public void setUp() {
         mFired = new ArrayList<>();
 
-        // Set the current time to the min interval. This is because the "last update time" inits
-        // to zero, so we can get some weird results if the starting time is lower than the min
-        // update interval (i.e. alarms being postponed where they shouldn't be)
+        // Set the current time to the min interval. This is because the "last update time" inits to
+        // zero, so we can get some weird results if the starting time is lower than the min update
+        // interval (i.e. alarms being postponed where they shouldn't be)
         mCurrentTime = MIN_INTER_UPDATE_INTERVAL_MILLIS;
 
         AlarmManager alarmManager = getApplicationContext().getSystemService(AlarmManager.class);
diff --git a/wear/tiles/tiles-renderer/src/test/java/androidx/wear/tiles/timeline/TilesTimelineCacheTest.java b/wear/tiles/tiles-renderer/src/test/java/androidx/wear/tiles/timeline/TilesTimelineCacheTest.java
index 1f1d66e..7d73149 100644
--- a/wear/tiles/tiles-renderer/src/test/java/androidx/wear/tiles/timeline/TilesTimelineCacheTest.java
+++ b/wear/tiles/tiles-renderer/src/test/java/androidx/wear/tiles/timeline/TilesTimelineCacheTest.java
@@ -113,8 +113,7 @@
     @Test
     public void timelineCache_overlappingEntryWithDefault() {
         // Test that with a default, and an entry "on top", the entry is shown for its validity
-        // period,
-        // and the default for all other times. As an example
+        // period, and the default for all other times. As an example
         //              +---------------------+
         //              |         E1          |
         //  ...---------+---------------------+----------------...
@@ -484,8 +483,7 @@
         TilesTimelineCache timelineCache = new TilesTimelineCache(timeline);
 
         // This is really undefined behaviour at the moment, but, well, let's keep this as the
-        // assumed behaviour for now.
-        // Should just pick entry1 in this case.
+        // assumed behaviour for now. Should just pick entry1 in this case.
         expectTimelineEntryEqual(timelineCache.findTimelineEntryForTime(0L), null);
         expectTimelineEntryEqual(timelineCache.findClosestTimelineEntry(0L), entry1);
         expect.that(timelineCache.findCurrentTimelineEntryExpiry(entry1, 0L))
diff --git a/wear/tiles/tiles-renderer/src/test/java/androidx/wear/tiles/timeline/TilesTimelineManagerTest.java b/wear/tiles/tiles-renderer/src/test/java/androidx/wear/tiles/timeline/TilesTimelineManagerTest.java
index 7ee2185..f6c490f 100644
--- a/wear/tiles/tiles-renderer/src/test/java/androidx/wear/tiles/timeline/TilesTimelineManagerTest.java
+++ b/wear/tiles/tiles-renderer/src/test/java/androidx/wear/tiles/timeline/TilesTimelineManagerTest.java
@@ -71,8 +71,8 @@
                 (AlarmManager) getApplicationContext().getSystemService(Context.ALARM_SERVICE);
 
         // Sync the clock with the current time. This is really just to ensure that the test times
-        // we're going to use are in the future, which prevents AlarmManager (which always uses
-        // the system time :( ) from immediately firing all our alarms.
+        // we're going to use are in the future, which prevents AlarmManager (which always uses the
+        // system time :( ) from immediately firing all our alarms.
         mCurrentTime = System.currentTimeMillis();
 
         mTimelineManager = null;
@@ -280,8 +280,7 @@
     public void timelineManager_minDelayUsesCorrectEntry() {
         // This has three entries, one initial one, one that happens after MIN_DELAY/2, and one that
         // happens after MIN_DELAY. This should totally skip the middle entry, and only show the
-        // first
-        // and last entries.
+        // first and last entries.
         List<Layout> returnedLayouts = new ArrayList<>();
 
         final long cutover1Millis =
diff --git a/wear/tiles/tiles/src/main/java/androidx/wear/tiles/ModifiersBuilders.java b/wear/tiles/tiles/src/main/java/androidx/wear/tiles/ModifiersBuilders.java
index a70797a..ba77d87 100644
--- a/wear/tiles/tiles/src/main/java/androidx/wear/tiles/ModifiersBuilders.java
+++ b/wear/tiles/tiles/src/main/java/androidx/wear/tiles/ModifiersBuilders.java
@@ -370,8 +370,8 @@
              * side of the container if the device is using an RTL locale). If false, start/end will
              * always map to left/right, accordingly.
              */
-            @NonNull
             @SuppressLint("MissingGetterMatchingBuilder")
+            @NonNull
             public Builder setRtlAware(boolean rtlAware) {
                 mImpl.setRtlAware(TypesProto.BoolProp.newBuilder().setValue(rtlAware));
                 return this;
diff --git a/wear/tiles/tiles/src/main/java/androidx/wear/tiles/SysUiTileUpdateRequester.java b/wear/tiles/tiles/src/main/java/androidx/wear/tiles/SysUiTileUpdateRequester.java
index 3965e0e7..c1d54c8 100644
--- a/wear/tiles/tiles/src/main/java/androidx/wear/tiles/SysUiTileUpdateRequester.java
+++ b/wear/tiles/tiles/src/main/java/androidx/wear/tiles/SysUiTileUpdateRequester.java
@@ -140,9 +140,8 @@
                         }
 
                         // This is a little suboptimal, as if an update is requested in this lock,
-                        // we'll
-                        // unbind, then immediately rebind. That said, this class should be used
-                        // pretty rarely
+                        // we'll unbind, then immediately rebind. That said, this class should be
+                        // used pretty rarely
                         // (and it'll be rare to have two in-flight update requests at once
                         // regardless), so
                         // it's probably fine.
diff --git a/wear/tiles/tiles/src/main/java/androidx/wear/tiles/TileProviderService.java b/wear/tiles/tiles/src/main/java/androidx/wear/tiles/TileProviderService.java
index 5b682ae..6029f15 100644
--- a/wear/tiles/tiles/src/main/java/androidx/wear/tiles/TileProviderService.java
+++ b/wear/tiles/tiles/src/main/java/androidx/wear/tiles/TileProviderService.java
@@ -277,7 +277,7 @@
                                             Log.e(
                                                     TAG,
                                                     "RemoteException while returning resources"
-                                                        + " payload",
+                                                            + " payload",
                                                     ex);
                                         }
                                     },
diff --git a/wear/wear-watchface-data/proguard-rules.pro b/wear/wear-watchface-data/proguard-rules.pro
index 1de112f7f..ecdad1c 100644
--- a/wear/wear-watchface-data/proguard-rules.pro
+++ b/wear/wear-watchface-data/proguard-rules.pro
@@ -14,4 +14,7 @@
 
 # Prevent Parcelizer objects from being removed or renamed.
 -keep public class androidx.wear.watchface.**Parcelizer { *; }
--keep public class androidx.wear.watchface.style.data.ComplicationOverlayWireFormat { *; }
\ No newline at end of file
+-keep public class androidx.wear.watchface.style.data.ComplicationOverlayWireFormat { *; }
+-keep public class android.support.wearable.complications.ComplicationData { *; }
+-keep public class android.support.wearable.complications.ComplicationProviderInfo  { *; }
+-keep public class android.support.wearable.complications.ComplicationText { *; }
diff --git a/wear/wear-watchface/samples/src/main/java/androidx/wear/watchface/samples/ExampleCanvasDigitalWatchFaceService.kt b/wear/wear-watchface/samples/src/main/java/androidx/wear/watchface/samples/ExampleCanvasDigitalWatchFaceService.kt
index 2be3a38..0395226 100644
--- a/wear/wear-watchface/samples/src/main/java/androidx/wear/watchface/samples/ExampleCanvasDigitalWatchFaceService.kt
+++ b/wear/wear-watchface/samples/src/main/java/androidx/wear/watchface/samples/ExampleCanvasDigitalWatchFaceService.kt
@@ -469,36 +469,36 @@
     // Lazy because the context isn't initialized til later.
     private val watchFaceStyle by lazy { WatchFaceColorStyle.create(this, RED_STYLE) }
 
-    private val colorStyleSetting = UserStyleSetting.ListUserStyleSetting(
-        UserStyleSetting.Id(COLOR_STYLE_SETTING),
-        getString(R.string.colors_style_setting),
-        getString(R.string.colors_style_setting_description),
-        icon = null,
-        options = listOf(
-            UserStyleSetting.ListUserStyleSetting.ListOption(
-                Option.Id(RED_STYLE),
-                getString(R.string.colors_style_red),
-                Icon.createWithResource(this, R.drawable.red_style)
+    private val colorStyleSetting by lazy {
+        UserStyleSetting.ListUserStyleSetting(
+            UserStyleSetting.Id(COLOR_STYLE_SETTING),
+            getString(R.string.colors_style_setting),
+            getString(R.string.colors_style_setting_description),
+            icon = null,
+            options = listOf(
+                UserStyleSetting.ListUserStyleSetting.ListOption(
+                    Option.Id(RED_STYLE),
+                    getString(R.string.colors_style_red),
+                    Icon.createWithResource(this, R.drawable.red_style)
+                ),
+                UserStyleSetting.ListUserStyleSetting.ListOption(
+                    Option.Id(GREEN_STYLE),
+                    getString(R.string.colors_style_green),
+                    Icon.createWithResource(this, R.drawable.green_style)
+                ),
+                UserStyleSetting.ListUserStyleSetting.ListOption(
+                    Option.Id(BLUE_STYLE),
+                    getString(R.string.colors_style_blue),
+                    Icon.createWithResource(this, R.drawable.blue_style)
+                )
             ),
-            UserStyleSetting.ListUserStyleSetting.ListOption(
-                Option.Id(GREEN_STYLE),
-                getString(R.string.colors_style_green),
-                Icon.createWithResource(this, R.drawable.green_style)
-            ),
-            UserStyleSetting.ListUserStyleSetting.ListOption(
-                Option.Id(BLUE_STYLE),
-                getString(R.string.colors_style_blue),
-                Icon.createWithResource(this, R.drawable.blue_style)
+            listOf(
+                WatchFaceLayer.BASE,
+                WatchFaceLayer.COMPLICATIONS,
+                WatchFaceLayer.COMPLICATIONS_OVERLAY
             )
-        ),
-        listOf(
-            WatchFaceLayer.BASE,
-            WatchFaceLayer.COMPLICATIONS,
-            WatchFaceLayer.COMPLICATIONS_OVERLAY
         )
-    )
-
-    private val userStyleSchema = UserStyleSchema(listOf(colorStyleSetting))
+    }
 
     private val canvasComplicationFactory =
         CanvasComplicationFactory { watchState, listener ->
@@ -608,7 +608,7 @@
         DefaultComplicationProviderPolicy()
     ).build()
 
-    override fun createUserStyleSchema() = userStyleSchema
+    override fun createUserStyleSchema() = UserStyleSchema(listOf(colorStyleSetting))
 
     override fun createComplicationsManager(
         currentUserStyleRepository: CurrentUserStyleRepository