Merge changes I53e2fc68,Id24344ce into androidx-main

* changes:
  Sort color stops when reading a SweepGradient.
  Interpolate colors linearly to align with interpolation done by the shader.
diff --git a/README.md b/README.md
index 659405f..be89bdb 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,6 @@
 # Android Jetpack
 
-[![Revved up by Gradle Enterprise](https://img.shields.io/badge/Revved%20up%20by-Gradle%20Enterprise-06A0CE?logo=Gradle&labelColor=02303A)](https://ge.androidx.dev)
+[![Revved up by Develocity](https://img.shields.io/badge/Revved%20up%20by-Develocity-06A0CE?logo=Gradle&labelColor=02303A)](https://ge.androidx.dev)
 
 Jetpack is a suite of libraries, tools, and guidance to help developers write high-quality apps easier. These components help you follow best practices, free you from writing boilerplate code, and simplify complex tasks, so you can focus on the code you care about.
 
diff --git a/activity/activity/api/current.txt b/activity/activity/api/current.txt
index c1d1b6d9..08bd5c7 100644
--- a/activity/activity/api/current.txt
+++ b/activity/activity/api/current.txt
@@ -21,7 +21,7 @@
   public static final class BackEventCompat.Companion {
   }
 
-  public class ComponentActivity extends android.app.Activity implements androidx.activity.result.ActivityResultCaller androidx.activity.result.ActivityResultRegistryOwner androidx.activity.contextaware.ContextAware androidx.activity.FullyDrawnReporterOwner androidx.lifecycle.HasDefaultViewModelProviderFactory androidx.lifecycle.LifecycleOwner androidx.core.view.MenuHost androidx.activity.OnBackPressedDispatcherOwner androidx.core.content.OnConfigurationChangedProvider androidx.core.app.OnMultiWindowModeChangedProvider androidx.core.app.OnNewIntentProvider androidx.core.app.OnPictureInPictureModeChangedProvider androidx.core.content.OnTrimMemoryProvider androidx.savedstate.SavedStateRegistryOwner androidx.lifecycle.ViewModelStoreOwner {
+  public class ComponentActivity extends android.app.Activity implements androidx.activity.result.ActivityResultCaller androidx.activity.result.ActivityResultRegistryOwner androidx.activity.contextaware.ContextAware androidx.activity.FullyDrawnReporterOwner androidx.lifecycle.HasDefaultViewModelProviderFactory androidx.lifecycle.LifecycleOwner androidx.core.view.MenuHost androidx.activity.OnBackPressedDispatcherOwner androidx.core.content.OnConfigurationChangedProvider androidx.core.app.OnMultiWindowModeChangedProvider androidx.core.app.OnNewIntentProvider androidx.core.app.OnPictureInPictureModeChangedProvider androidx.core.content.OnTrimMemoryProvider androidx.core.app.OnUserLeaveHintProvider androidx.savedstate.SavedStateRegistryOwner androidx.lifecycle.ViewModelStoreOwner {
     ctor public ComponentActivity();
     ctor @ContentView public ComponentActivity(@LayoutRes int contentLayoutId);
     method public void addMenuProvider(androidx.core.view.MenuProvider provider);
@@ -33,6 +33,7 @@
     method public final void addOnNewIntentListener(androidx.core.util.Consumer<android.content.Intent> listener);
     method public final void addOnPictureInPictureModeChangedListener(androidx.core.util.Consumer<androidx.core.app.PictureInPictureModeChangedInfo> listener);
     method public final void addOnTrimMemoryListener(androidx.core.util.Consumer<java.lang.Integer> listener);
+    method public final void addOnUserLeaveHintListener(Runnable listener);
     method public final androidx.activity.result.ActivityResultRegistry getActivityResultRegistry();
     method public androidx.lifecycle.ViewModelProvider.Factory getDefaultViewModelProviderFactory();
     method public androidx.activity.FullyDrawnReporter getFullyDrawnReporter();
@@ -57,6 +58,7 @@
     method public final void removeOnNewIntentListener(androidx.core.util.Consumer<android.content.Intent> listener);
     method public final void removeOnPictureInPictureModeChangedListener(androidx.core.util.Consumer<androidx.core.app.PictureInPictureModeChangedInfo> listener);
     method public final void removeOnTrimMemoryListener(androidx.core.util.Consumer<java.lang.Integer> listener);
+    method public final void removeOnUserLeaveHintListener(Runnable listener);
     method @Deprecated public void startActivityForResult(android.content.Intent intent, int requestCode);
     method @Deprecated public void startActivityForResult(android.content.Intent intent, int requestCode, android.os.Bundle? options);
     method @Deprecated @kotlin.jvm.Throws(exceptionClasses=SendIntentException::class) public void startIntentSenderForResult(android.content.IntentSender intent, int requestCode, android.content.Intent? fillInIntent, int flagsMask, int flagsValues, int extraFlags) throws android.content.IntentSender.SendIntentException;
diff --git a/activity/activity/api/restricted_current.txt b/activity/activity/api/restricted_current.txt
index bdd7a6f..f92e7d6 100644
--- a/activity/activity/api/restricted_current.txt
+++ b/activity/activity/api/restricted_current.txt
@@ -21,7 +21,7 @@
   public static final class BackEventCompat.Companion {
   }
 
-  public class ComponentActivity extends androidx.core.app.ComponentActivity implements androidx.activity.result.ActivityResultCaller androidx.activity.result.ActivityResultRegistryOwner androidx.activity.contextaware.ContextAware androidx.activity.FullyDrawnReporterOwner androidx.lifecycle.HasDefaultViewModelProviderFactory androidx.lifecycle.LifecycleOwner androidx.core.view.MenuHost androidx.activity.OnBackPressedDispatcherOwner androidx.core.content.OnConfigurationChangedProvider androidx.core.app.OnMultiWindowModeChangedProvider androidx.core.app.OnNewIntentProvider androidx.core.app.OnPictureInPictureModeChangedProvider androidx.core.content.OnTrimMemoryProvider androidx.savedstate.SavedStateRegistryOwner androidx.lifecycle.ViewModelStoreOwner {
+  public class ComponentActivity extends androidx.core.app.ComponentActivity implements androidx.activity.result.ActivityResultCaller androidx.activity.result.ActivityResultRegistryOwner androidx.activity.contextaware.ContextAware androidx.activity.FullyDrawnReporterOwner androidx.lifecycle.HasDefaultViewModelProviderFactory androidx.lifecycle.LifecycleOwner androidx.core.view.MenuHost androidx.activity.OnBackPressedDispatcherOwner androidx.core.content.OnConfigurationChangedProvider androidx.core.app.OnMultiWindowModeChangedProvider androidx.core.app.OnNewIntentProvider androidx.core.app.OnPictureInPictureModeChangedProvider androidx.core.content.OnTrimMemoryProvider androidx.core.app.OnUserLeaveHintProvider androidx.savedstate.SavedStateRegistryOwner androidx.lifecycle.ViewModelStoreOwner {
     ctor public ComponentActivity();
     ctor @ContentView public ComponentActivity(@LayoutRes int contentLayoutId);
     method public void addMenuProvider(androidx.core.view.MenuProvider provider);
@@ -33,6 +33,7 @@
     method public final void addOnNewIntentListener(androidx.core.util.Consumer<android.content.Intent> listener);
     method public final void addOnPictureInPictureModeChangedListener(androidx.core.util.Consumer<androidx.core.app.PictureInPictureModeChangedInfo> listener);
     method public final void addOnTrimMemoryListener(androidx.core.util.Consumer<java.lang.Integer> listener);
+    method public final void addOnUserLeaveHintListener(Runnable listener);
     method public final androidx.activity.result.ActivityResultRegistry getActivityResultRegistry();
     method public androidx.lifecycle.ViewModelProvider.Factory getDefaultViewModelProviderFactory();
     method public androidx.activity.FullyDrawnReporter getFullyDrawnReporter();
@@ -56,6 +57,7 @@
     method public final void removeOnNewIntentListener(androidx.core.util.Consumer<android.content.Intent> listener);
     method public final void removeOnPictureInPictureModeChangedListener(androidx.core.util.Consumer<androidx.core.app.PictureInPictureModeChangedInfo> listener);
     method public final void removeOnTrimMemoryListener(androidx.core.util.Consumer<java.lang.Integer> listener);
+    method public final void removeOnUserLeaveHintListener(Runnable listener);
     method @Deprecated public void startActivityForResult(android.content.Intent intent, int requestCode);
     method @Deprecated public void startActivityForResult(android.content.Intent intent, int requestCode, android.os.Bundle? options);
     method @Deprecated @kotlin.jvm.Throws(exceptionClasses=SendIntentException::class) public void startIntentSenderForResult(android.content.IntentSender intent, int requestCode, android.content.Intent? fillInIntent, int flagsMask, int flagsValues, int extraFlags) throws android.content.IntentSender.SendIntentException;
diff --git a/activity/activity/src/androidTest/AndroidManifest.xml b/activity/activity/src/androidTest/AndroidManifest.xml
index ba4d329..7592eec 100644
--- a/activity/activity/src/androidTest/AndroidManifest.xml
+++ b/activity/activity/src/androidTest/AndroidManifest.xml
@@ -53,6 +53,9 @@
             android:launchMode="singleTop"
             android:exported="true" />
         <activity
+            android:name="androidx.activity.OnUserLeaveHintActivity"
+            android:exported="true" />
+        <activity
             android:name="androidx.activity.AutoRestarterActivity"
             android:exported="true" />
         <activity
diff --git a/activity/activity/src/androidTest/java/androidx/activity/ComponentActivityCallbacksTest.kt b/activity/activity/src/androidTest/java/androidx/activity/ComponentActivityCallbacksTest.kt
index d177cab..d0bdd8d 100644
--- a/activity/activity/src/androidTest/java/androidx/activity/ComponentActivityCallbacksTest.kt
+++ b/activity/activity/src/androidTest/java/androidx/activity/ComponentActivityCallbacksTest.kt
@@ -479,6 +479,46 @@
             assertThat(receivedInfo.isInPictureInPictureMode).isTrue()
         }
     }
+
+    @Test
+    fun onUserLeaveHint() {
+        withUse(ActivityScenario.launch(OnUserLeaveHintActivity::class.java)) {
+            var receivedOnUserLeaveHint = false
+
+            val listener = Runnable { receivedOnUserLeaveHint = true }
+
+            withActivity {
+                addOnUserLeaveHintListener(listener)
+                onUserLeaveHint()
+            }
+
+            assertThat(receivedOnUserLeaveHint).isEqualTo(true)
+        }
+    }
+
+    @Test
+    fun onUserLeaveHintRemove() {
+        withUse(ActivityScenario.launch(OnUserLeaveHintActivity::class.java)) {
+            var receivedOnUserLeaveHintCount = 0
+
+            val listener = Runnable { receivedOnUserLeaveHintCount++ }
+
+            withActivity {
+                addOnUserLeaveHintListener(listener)
+                onUserLeaveHint()
+            }
+
+            assertThat(receivedOnUserLeaveHintCount).isEqualTo(1)
+
+            withActivity {
+                removeOnUserLeaveHintListener(listener)
+                onUserLeaveHint()
+            }
+
+            // should still be 1
+            assertThat(receivedOnUserLeaveHintCount).isEqualTo(1)
+        }
+    }
 }
 
 class SingleTopActivity : ComponentActivity() {
@@ -486,3 +526,9 @@
         super.onNewIntent(intent)
     }
 }
+
+class OnUserLeaveHintActivity : ComponentActivity() {
+    public override fun onUserLeaveHint() {
+        super.onUserLeaveHint()
+    }
+}
diff --git a/activity/activity/src/androidTest/java/androidx/activity/OnBackPressedDispatcherInvokerTest.kt b/activity/activity/src/androidTest/java/androidx/activity/OnBackPressedDispatcherInvokerTest.kt
index 86840cc..b0fefbf 100644
--- a/activity/activity/src/androidTest/java/androidx/activity/OnBackPressedDispatcherInvokerTest.kt
+++ b/activity/activity/src/androidTest/java/androidx/activity/OnBackPressedDispatcherInvokerTest.kt
@@ -319,6 +319,95 @@
     }
 
     @Test
+    fun testSimpleAnimatedCallbackAddedContinue() {
+        var registerCount = 0
+        var unregisterCount = 0
+        val invoker = object : OnBackInvokedDispatcher {
+            override fun registerOnBackInvokedCallback(p0: Int, p1: OnBackInvokedCallback) {
+                registerCount++
+            }
+
+            override fun unregisterOnBackInvokedCallback(p0: OnBackInvokedCallback) {
+                unregisterCount++
+            }
+        }
+
+        val dispatcher = OnBackPressedDispatcher()
+
+        dispatcher.setOnBackInvokedDispatcher(invoker)
+
+        var completedCount = 0
+        val callback = object : OnBackPressedCallback(true) {
+            override fun handleOnBackStarted(backEvent: BackEventCompat) { }
+            override fun handleOnBackProgressed(backEvent: BackEventCompat) {}
+            override fun handleOnBackPressed() {
+                completedCount++
+            }
+            override fun handleOnBackCancelled() { }
+        }
+
+        dispatcher.addCallback(callback)
+
+        assertThat(registerCount).isEqualTo(1)
+
+        dispatcher.dispatchOnBackStarted(BackEventCompat(0.1F, 0.1F, 0.1F, EDGE_LEFT))
+
+        dispatcher.addCallback(object : OnBackPressedCallback(true) {
+            override fun handleOnBackPressed() { }
+        })
+
+        dispatcher.onBackPressed()
+
+        assertThat(completedCount).isEqualTo(1)
+    }
+
+    @Test
+    fun testLifecycleAnimatedCallbackAddedContinue() {
+        var registerCount = 0
+        var unregisterCount = 0
+        val invoker = object : OnBackInvokedDispatcher {
+            override fun registerOnBackInvokedCallback(p0: Int, p1: OnBackInvokedCallback) {
+                registerCount++
+            }
+
+            override fun unregisterOnBackInvokedCallback(p0: OnBackInvokedCallback) {
+                unregisterCount++
+            }
+        }
+
+        val dispatcher = OnBackPressedDispatcher()
+
+        dispatcher.setOnBackInvokedDispatcher(invoker)
+
+        var completedCount = 0
+        val callback = object : OnBackPressedCallback(true) {
+            override fun handleOnBackStarted(backEvent: BackEventCompat) { }
+            override fun handleOnBackProgressed(backEvent: BackEventCompat) {}
+            override fun handleOnBackPressed() {
+                completedCount++
+            }
+            override fun handleOnBackCancelled() { }
+        }
+
+        val lifecycleOwner = TestLifecycleOwner(Lifecycle.State.RESUMED)
+
+        dispatcher.addCallback(lifecycleOwner, callback)
+
+        assertThat(registerCount).isEqualTo(1)
+
+        dispatcher.dispatchOnBackStarted(BackEventCompat(0.1F, 0.1F, 0.1F, EDGE_LEFT))
+
+        lifecycleOwner.currentState = Lifecycle.State.STARTED
+        dispatcher.addCallback(object : OnBackPressedCallback(true) {
+            override fun handleOnBackPressed() { }
+        })
+
+        dispatcher.onBackPressed()
+
+        assertThat(completedCount).isEqualTo(1)
+    }
+
+    @Test
     fun testSimpleAnimatedLifecycleCallbackRemovedCancel() {
         var registerCount = 0
         var unregisterCount = 0
diff --git a/activity/activity/src/main/java/androidx/activity/ComponentActivity.kt b/activity/activity/src/main/java/androidx/activity/ComponentActivity.kt
index 65dda0f..a962b3b 100644
--- a/activity/activity/src/main/java/androidx/activity/ComponentActivity.kt
+++ b/activity/activity/src/main/java/androidx/activity/ComponentActivity.kt
@@ -65,6 +65,7 @@
 import androidx.core.app.OnMultiWindowModeChangedProvider
 import androidx.core.app.OnNewIntentProvider
 import androidx.core.app.OnPictureInPictureModeChangedProvider
+import androidx.core.app.OnUserLeaveHintProvider
 import androidx.core.app.PictureInPictureModeChangedInfo
 import androidx.core.content.ContextCompat
 import androidx.core.content.OnConfigurationChangedProvider
@@ -122,6 +123,7 @@
     OnNewIntentProvider,
     OnMultiWindowModeChangedProvider,
     OnPictureInPictureModeChangedProvider,
+    OnUserLeaveHintProvider,
     MenuHost,
     FullyDrawnReporterOwner {
     internal class NonConfigurationInstances {
@@ -236,6 +238,7 @@
         CopyOnWriteArrayList<Consumer<MultiWindowModeChangedInfo>>()
     private val onPictureInPictureModeChangedListeners =
         CopyOnWriteArrayList<Consumer<PictureInPictureModeChangedInfo>>()
+    private val onUserLeaveHintListeners = CopyOnWriteArrayList<Runnable>()
     private var dispatchingOnMultiWindowModeChanged = false
     private var dispatchingOnPictureInPictureModeChanged = false
 
@@ -1013,6 +1016,27 @@
         onPictureInPictureModeChangedListeners.remove(listener)
     }
 
+    /**
+     * {@inheritDoc}
+     *
+     * Dispatches this call to all listeners added via [addOnUserLeaveHintListener].
+     */
+    @CallSuper
+    override fun onUserLeaveHint() {
+        super.onUserLeaveHint()
+        for (listener in onUserLeaveHintListeners) {
+            listener.run()
+        }
+    }
+
+    final override fun addOnUserLeaveHintListener(listener: Runnable) {
+        onUserLeaveHintListeners.add(listener)
+    }
+
+    final override fun removeOnUserLeaveHintListener(listener: Runnable) {
+        onUserLeaveHintListeners.remove(listener)
+    }
+
     override fun reportFullyDrawn() {
         try {
             if (Trace.isEnabled()) {
diff --git a/activity/activity/src/main/java/androidx/activity/OnBackPressedDispatcher.kt b/activity/activity/src/main/java/androidx/activity/OnBackPressedDispatcher.kt
index cdeb8c6..04d0388 100644
--- a/activity/activity/src/main/java/androidx/activity/OnBackPressedDispatcher.kt
+++ b/activity/activity/src/main/java/androidx/activity/OnBackPressedDispatcher.kt
@@ -248,7 +248,7 @@
 
     @MainThread
     private fun onBackProgressed(backEvent: BackEventCompat) {
-        val callback = onBackPressedCallbacks.lastOrNull {
+        val callback = inProgressCallback ?: onBackPressedCallbacks.lastOrNull {
             it.isEnabled
         }
         if (callback != null) {
@@ -268,7 +268,7 @@
      */
     @MainThread
     fun onBackPressed() {
-        val callback = onBackPressedCallbacks.lastOrNull {
+        val callback = inProgressCallback ?: onBackPressedCallbacks.lastOrNull {
             it.isEnabled
         }
         inProgressCallback = null
@@ -287,7 +287,7 @@
 
     @MainThread
     private fun onBackCancelled() {
-        val callback = onBackPressedCallbacks.lastOrNull {
+        val callback = inProgressCallback ?: onBackPressedCallbacks.lastOrNull {
             it.isEnabled
         }
         inProgressCallback = null
diff --git a/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/BaseBasicsTestCase.java b/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/BaseBasicsTestCase.java
index e126d37..96fdc01 100644
--- a/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/BaseBasicsTestCase.java
+++ b/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/BaseBasicsTestCase.java
@@ -136,7 +136,6 @@
 
     @UiThreadTest
     @Test
-    @SdkSuppress(minSdkVersion = 19)
     public void testSetActionBarTitleByActionBar() {
         final String newTitle = "hello";
         mActivityTestRule.getActivity().getSupportActionBar().setTitle(newTitle);
@@ -149,7 +148,7 @@
     }
 
     @Test
-    @SdkSuppress(minSdkVersion = 16, maxSdkVersion = 20)
+    @SdkSuppress(maxSdkVersion = 20)
     @RequiresApi(16)
     public void testFitSystemWindowsReachesContent() throws Throwable {
         final A activity = mActivityTestRule.getActivity();
diff --git a/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/LocalesConfigChangesWithoutLayoutDirectionTestCase.kt b/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/LocalesConfigChangesWithoutLayoutDirectionTestCase.kt
index 83239cd..20f8505 100644
--- a/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/LocalesConfigChangesWithoutLayoutDirectionTestCase.kt
+++ b/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/LocalesConfigChangesWithoutLayoutDirectionTestCase.kt
@@ -28,7 +28,7 @@
 import org.junit.Before
 import org.junit.Test
 
-@SdkSuppress(minSdkVersion = 17, maxSdkVersion = 32)
+@SdkSuppress(maxSdkVersion = 32)
 class LocalesConfigChangesWithoutLayoutDirectionTestCase {
     private lateinit var scenario: ActivityScenario<
         LocalesConfigChangesActivityWithoutLayoutDirection>
diff --git a/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/LocalesCustomApplyOverrideConfigurationTestCase.kt b/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/LocalesCustomApplyOverrideConfigurationTestCase.kt
index e6c0248..ca579ff 100644
--- a/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/LocalesCustomApplyOverrideConfigurationTestCase.kt
+++ b/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/LocalesCustomApplyOverrideConfigurationTestCase.kt
@@ -32,7 +32,7 @@
 /**
  * This is one approach to customize Activity's configuration that's used in google3.
  */
-@SdkSuppress(minSdkVersion = 17, maxSdkVersion = 32)
+@SdkSuppress(maxSdkVersion = 32)
 class LocalesCustomApplyOverrideConfigurationTestCase() {
 
     @get:Rule
diff --git a/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/LocalesCustomAttachBaseContextTestCase.kt b/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/LocalesCustomAttachBaseContextTestCase.kt
index 395b839..27093a8 100644
--- a/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/LocalesCustomAttachBaseContextTestCase.kt
+++ b/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/LocalesCustomAttachBaseContextTestCase.kt
@@ -34,7 +34,7 @@
 import org.junit.Test
 
 @LargeTest
-@SdkSuppress(minSdkVersion = 17, maxSdkVersion = 32)
+@SdkSuppress(maxSdkVersion = 32)
 class LocalesCustomAttachBaseContextTestCase() {
 
     @get:Rule
diff --git a/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/LocalesRotateDoesNotRecreateActivityTestCase.kt b/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/LocalesRotateDoesNotRecreateActivityTestCase.kt
index 5e76e61..a4b1e70 100644
--- a/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/LocalesRotateDoesNotRecreateActivityTestCase.kt
+++ b/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/LocalesRotateDoesNotRecreateActivityTestCase.kt
@@ -36,7 +36,7 @@
 import org.junit.Test
 
 @LargeTest
-@SdkSuppress(minSdkVersion = 18, maxSdkVersion = 32)
+@SdkSuppress(maxSdkVersion = 32)
 class LocalesRotateDoesNotRecreateActivityTestCase() {
 
     private val instrumentation = InstrumentationRegistry.getInstrumentation()
diff --git a/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/LocalesRotateRecreatesActivityWithConfigTestCase.kt b/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/LocalesRotateRecreatesActivityWithConfigTestCase.kt
index 336eb4d..061de9a 100644
--- a/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/LocalesRotateRecreatesActivityWithConfigTestCase.kt
+++ b/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/LocalesRotateRecreatesActivityWithConfigTestCase.kt
@@ -39,7 +39,7 @@
 import org.junit.Test
 
 @LargeTest
-@SdkSuppress(minSdkVersion = 18, maxSdkVersion = 32)
+@SdkSuppress(maxSdkVersion = 32)
 class LocalesRotateRecreatesActivityWithConfigTestCase() {
 
     private val instrumentation = InstrumentationRegistry.getInstrumentation()
diff --git a/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/LocalesUpdateTestCase.kt b/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/LocalesUpdateTestCase.kt
index d07329e..131e963 100644
--- a/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/LocalesUpdateTestCase.kt
+++ b/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/LocalesUpdateTestCase.kt
@@ -120,7 +120,7 @@
     }
 
     @Ignore("b/262902574")
-    @SdkSuppress(minSdkVersion = 17, maxSdkVersion = 33)
+    @SdkSuppress(maxSdkVersion = 33)
     @Test
     @FlakyTest(bugId = 255765202)
     fun testLayoutDirectionAfterRecreating() {
diff --git a/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/NightModeCustomApplicationConfigurationTestCase.kt b/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/NightModeCustomApplicationConfigurationTestCase.kt
index 813c05b..bab064a 100644
--- a/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/NightModeCustomApplicationConfigurationTestCase.kt
+++ b/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/NightModeCustomApplicationConfigurationTestCase.kt
@@ -26,7 +26,6 @@
 import androidx.appcompat.testutils.NightModeUtils.NightSetMode
 import androidx.appcompat.testutils.NightModeUtils.setNightModeAndWaitForRecreate
 import androidx.test.filters.LargeTest
-import androidx.test.filters.SdkSuppress
 import androidx.test.platform.app.InstrumentationRegistry
 import org.junit.After
 import org.junit.Assert.assertEquals
@@ -41,7 +40,6 @@
  *
  * The ContextThemeWrapper.applyOverrideConfiguration method only exists on API level 17 and up.
  */
-@SdkSuppress(minSdkVersion = 17)
 @LargeTest
 @RunWith(Parameterized::class)
 class NightModeCustomApplicationConfigurationTestCase(private val setMode: NightSetMode) {
diff --git a/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/NightModeCustomApplyOverrideConfigurationTestCase.kt b/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/NightModeCustomApplyOverrideConfigurationTestCase.kt
index a96dcb7..fea48d4 100644
--- a/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/NightModeCustomApplyOverrideConfigurationTestCase.kt
+++ b/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/NightModeCustomApplyOverrideConfigurationTestCase.kt
@@ -24,7 +24,6 @@
 import androidx.appcompat.testutils.NightModeUtils.NightSetMode
 import androidx.appcompat.testutils.NightModeUtils.setNightModeAndWaitForRecreate
 import androidx.test.filters.LargeTest
-import androidx.test.filters.SdkSuppress
 import org.junit.Assert.assertEquals
 import org.junit.Rule
 import org.junit.Test
@@ -36,7 +35,6 @@
  *
  * The ContextThemeWrapper.applyOverrideConfiguration method only exists on API level 17 and up.
  */
-@SdkSuppress(minSdkVersion = 17)
 @LargeTest
 @RunWith(Parameterized::class)
 class NightModeCustomApplyOverrideConfigurationTestCase(private val setMode: NightSetMode) {
diff --git a/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/NightModeLateOnCreateTestCase.kt b/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/NightModeLateOnCreateTestCase.kt
index 4720885..736520b 100644
--- a/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/NightModeLateOnCreateTestCase.kt
+++ b/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/NightModeLateOnCreateTestCase.kt
@@ -24,7 +24,6 @@
 import androidx.lifecycle.Lifecycle
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.LargeTest
-import androidx.test.filters.SdkSuppress
 import androidx.testutils.LifecycleOwnerUtils.waitUntilState
 import org.junit.Rule
 import org.junit.Test
@@ -37,7 +36,6 @@
     val activityRule = NightModeActivityTestRule(NightModeLateOnCreateActivity::class.java)
 
     @Test
-    @SdkSuppress(minSdkVersion = 17)
     fun testActivityRecreateLoop() {
         // Activity should be able to reach fully resumed state in default NIGHT_NO.
         waitUntilState(activityRule.activity, Lifecycle.State.RESUMED)
diff --git a/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/NightModeLocalOnlyTestCase.kt b/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/NightModeLocalOnlyTestCase.kt
index ac0a3cf..d5b3dc4 100644
--- a/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/NightModeLocalOnlyTestCase.kt
+++ b/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/NightModeLocalOnlyTestCase.kt
@@ -27,7 +27,6 @@
 import androidx.test.espresso.matcher.ViewMatchers.withText
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.LargeTest
-import androidx.test.filters.SdkSuppress
 import org.junit.Assert.assertEquals
 import org.junit.Rule
 import org.junit.Test
@@ -40,7 +39,6 @@
     val rule = NightModeActivityTestRule(NightModeActivity::class.java)
 
     @Test
-    @SdkSuppress(minSdkVersion = 17)
     fun testLocalDayNightModeRecreatesActivity() {
         // Verify first that we're in day mode
         onView(withId(R.id.text_night_mode)).check(matches(withText(STRING_DAY)))
diff --git a/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/NightModeRtlTestUtilsRegressionTestCase.kt b/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/NightModeRtlTestUtilsRegressionTestCase.kt
index 3144e25..0e178fd 100644
--- a/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/NightModeRtlTestUtilsRegressionTestCase.kt
+++ b/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/NightModeRtlTestUtilsRegressionTestCase.kt
@@ -23,7 +23,6 @@
 import androidx.appcompat.testutils.NightModeActivityTestRule
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.MediumTest
-import androidx.test.filters.SdkSuppress
 import androidx.test.platform.app.InstrumentationRegistry
 import java.util.Locale
 import org.junit.After
@@ -38,7 +37,6 @@
  * <p>
  *
  */
-@SdkSuppress(minSdkVersion = 16)
 @MediumTest
 @RunWith(AndroidJUnit4::class)
 class NightModeRtlTestUtilsRegressionTestCase {
diff --git a/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/NightModeStackedHandlingTestCase.kt b/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/NightModeStackedHandlingTestCase.kt
index d078551..6b487ac 100644
--- a/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/NightModeStackedHandlingTestCase.kt
+++ b/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/NightModeStackedHandlingTestCase.kt
@@ -31,7 +31,6 @@
 import androidx.lifecycle.Lifecycle
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.LargeTest
-import androidx.test.filters.SdkSuppress
 import androidx.test.platform.app.InstrumentationRegistry
 import androidx.testutils.LifecycleOwnerUtils.waitUntilState
 import junit.framework.Assert.assertNotNull
@@ -62,7 +61,6 @@
      * and/or B were not recreated.
      */
     @Test
-    @SdkSuppress(minSdkVersion = 17)
     public fun testDefaultNightModeWithStackedActivities() {
         val instr = InstrumentationRegistry.getInstrumentation()
         val result = Instrumentation.ActivityResult(0, Intent())
@@ -163,7 +161,6 @@
      * 3. repeat (YES/NO/YES/NO...)
      */
     @Test
-    @SdkSuppress(minSdkVersion = 17)
     public fun testDefaultNightModeWithStackedActivitiesAndNavigation() {
         val instr = InstrumentationRegistry.getInstrumentation()
         val result = Instrumentation.ActivityResult(0, Intent())
diff --git a/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/NightModeTestCase.kt b/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/NightModeTestCase.kt
index 501bcfc..1e3451f 100644
--- a/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/NightModeTestCase.kt
+++ b/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/NightModeTestCase.kt
@@ -38,7 +38,6 @@
 import androidx.test.espresso.matcher.ViewMatchers.withId
 import androidx.test.espresso.matcher.ViewMatchers.withText
 import androidx.test.filters.LargeTest
-import androidx.test.filters.SdkSuppress
 import androidx.test.platform.app.InstrumentationRegistry
 import androidx.testutils.LifecycleOwnerUtils.waitForRecreation
 import androidx.testutils.waitForExecution
@@ -236,7 +235,6 @@
     }
 
     @Test
-    @SdkSuppress(minSdkVersion = 17)
     fun testDialogCleansUpAutoMode() = rule.runOnUiThread {
         val dialog = AppCompatDialog(rule.activity)
         val delegate = dialog.delegate as AppCompatDelegateImpl
diff --git a/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/g3/FilternatorTest.kt b/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/g3/FilternatorTest.kt
index 5d3fbee..0db7761 100644
--- a/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/g3/FilternatorTest.kt
+++ b/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/g3/FilternatorTest.kt
@@ -22,7 +22,6 @@
 import androidx.test.ext.junit.rules.ActivityScenarioRule
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.LargeTest
-import androidx.test.filters.SdkSuppress
 import androidx.test.platform.app.InstrumentationRegistry
 import androidx.test.uiautomator.UiDevice
 import androidx.testutils.LifecycleOwnerUtils.waitUntilState
@@ -40,7 +39,6 @@
  */
 @Suppress("SameParameterValue")
 @LargeTest
-@SdkSuppress(minSdkVersion = 18) // UiDevice
 @RunWith(AndroidJUnit4::class)
 class FilternatorTest {
     @get:Rule
diff --git a/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/g3/FilternatorTestWithCustomDefault.kt b/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/g3/FilternatorTestWithCustomDefault.kt
index eba4c96..52949dc 100644
--- a/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/g3/FilternatorTestWithCustomDefault.kt
+++ b/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/g3/FilternatorTestWithCustomDefault.kt
@@ -23,7 +23,6 @@
 import androidx.test.ext.junit.rules.ActivityScenarioRule
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.LargeTest
-import androidx.test.filters.SdkSuppress
 import androidx.test.platform.app.InstrumentationRegistry
 import androidx.test.uiautomator.UiDevice
 import androidx.testutils.LifecycleOwnerUtils.waitUntilState
@@ -46,7 +45,6 @@
  */
 @Suppress("SameParameterValue")
 @LargeTest
-@SdkSuppress(minSdkVersion = 18) // UiDevice
 @RunWith(AndroidJUnit4::class)
 class FilternatorTestWithCustomDefault {
     @get:Rule
diff --git a/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/g3/NavDrawerActivityTest.kt b/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/g3/NavDrawerActivityTest.kt
index ce19bae..21369bb 100644
--- a/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/g3/NavDrawerActivityTest.kt
+++ b/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/g3/NavDrawerActivityTest.kt
@@ -28,7 +28,6 @@
 import androidx.test.espresso.matcher.ViewMatchers.withId
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.LargeTest
-import androidx.test.filters.SdkSuppress
 import androidx.test.platform.app.InstrumentationRegistry
 import androidx.testutils.LifecycleOwnerUtils.waitUntilState
 import com.google.common.truth.Truth.assertThat
@@ -41,7 +40,6 @@
 /**
  * Regression test for b/235567649, adapted from Translate's own tests.
  */
-@SdkSuppress(minSdkVersion = 18)
 @LargeTest
 @RunWith(AndroidJUnit4::class)
 class NavDrawerActivityTest {
diff --git a/appcompat/appcompat/src/androidTest/java/androidx/appcompat/view/ContextThemeWrapperTest.java b/appcompat/appcompat/src/androidTest/java/androidx/appcompat/view/ContextThemeWrapperTest.java
index 40e43e0..6db59d5 100644
--- a/appcompat/appcompat/src/androidTest/java/androidx/appcompat/view/ContextThemeWrapperTest.java
+++ b/appcompat/appcompat/src/androidTest/java/androidx/appcompat/view/ContextThemeWrapperTest.java
@@ -30,7 +30,6 @@
 import androidx.appcompat.test.R;
 import androidx.test.core.app.ApplicationProvider;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SdkSuppress;
 import androidx.test.filters.SmallTest;
 
 import org.junit.Before;
@@ -127,7 +126,6 @@
         }).test());
     }
 
-    @SdkSuppress(minSdkVersion = 17)
     @Test
     public void testApplyOverrideDensityConfiguration() {
         // Configuration.densityDpi is only available on API 17 and above
diff --git a/appcompat/appcompat/src/androidTest/java/androidx/appcompat/widget/AppCompatAutoCompleteTextViewTest.java b/appcompat/appcompat/src/androidTest/java/androidx/appcompat/widget/AppCompatAutoCompleteTextViewTest.java
index 976f5f0..5929304 100644
--- a/appcompat/appcompat/src/androidTest/java/androidx/appcompat/widget/AppCompatAutoCompleteTextViewTest.java
+++ b/appcompat/appcompat/src/androidTest/java/androidx/appcompat/widget/AppCompatAutoCompleteTextViewTest.java
@@ -113,7 +113,6 @@
                 TextViewCompat.getCompoundDrawableTintList(textView));
     }
 
-    @SdkSuppress(minSdkVersion = 17)
     @Test
     public void testCompoundDrawableRelativeTint() {
         // Given an ACTV with a white drawableStartCompat set and a #f0f drawableTint
diff --git a/appcompat/appcompat/src/androidTest/java/androidx/appcompat/widget/AppCompatBaseAutoSizeTest.java b/appcompat/appcompat/src/androidTest/java/androidx/appcompat/widget/AppCompatBaseAutoSizeTest.java
index 483718c..525e43a 100644
--- a/appcompat/appcompat/src/androidTest/java/androidx/appcompat/widget/AppCompatBaseAutoSizeTest.java
+++ b/appcompat/appcompat/src/androidTest/java/androidx/appcompat/widget/AppCompatBaseAutoSizeTest.java
@@ -46,7 +46,6 @@
 import androidx.test.filters.FlakyTest;
 import androidx.test.filters.LargeTest;
 import androidx.test.filters.MediumTest;
-import androidx.test.filters.SdkSuppress;
 import androidx.test.filters.SmallTest;
 import androidx.test.platform.app.InstrumentationRegistry;
 import androidx.test.rule.ActivityTestRule;
@@ -84,7 +83,6 @@
 
     @Test
     @MediumTest
-    @SdkSuppress(minSdkVersion = 16)
     // public TextView#getMaxLines only introduced in API 16.
     public void testAutoSizeCallers_setMaxLines() throws Throwable {
         final T autoSizeView = prepareAndRetrieveAutoSizeTestData(R.id.view_autosize_uniform,
diff --git a/appcompat/appcompat/src/androidTest/java/androidx/appcompat/widget/AppCompatBaseTextViewEmojiTest.java b/appcompat/appcompat/src/androidTest/java/androidx/appcompat/widget/AppCompatBaseTextViewEmojiTest.java
index c38de53..53fba42 100644
--- a/appcompat/appcompat/src/androidTest/java/androidx/appcompat/widget/AppCompatBaseTextViewEmojiTest.java
+++ b/appcompat/appcompat/src/androidTest/java/androidx/appcompat/widget/AppCompatBaseTextViewEmojiTest.java
@@ -35,7 +35,6 @@
 import androidx.test.annotation.UiThreadTest;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.MediumTest;
-import androidx.test.filters.SdkSuppress;
 import androidx.test.platform.app.InstrumentationRegistry;
 import androidx.test.rule.ActivityTestRule;
 
@@ -80,7 +79,6 @@
 
     @Test
     @UiThreadTest
-    @SdkSuppress(minSdkVersion = 19)
     public void byDefault_setText_callsEmojiCompat() {
         resetEmojiCompatToNewMock();
         ViewType subject = mActivityTestRule.getActivity()
@@ -91,7 +89,6 @@
 
     @Test
     @UiThreadTest
-    @SdkSuppress(minSdkVersion = 19)
     public void whenEnabled_setText_callsProcess() {
         resetEmojiCompatToNewMock();
         ViewType subject = mActivityTestRule.getActivity()
@@ -103,7 +100,6 @@
 
     @Test
     @UiThreadTest
-    @SdkSuppress(minSdkVersion = 19)
     public void whenDisabled_noCalls() {
         resetEmojiCompatToNewMock();
         ViewType subject = mActivityTestRule.getActivity()
@@ -116,7 +112,6 @@
 
     @Test
     @UiThreadTest
-    @SdkSuppress(minSdkVersion = 19)
     public void whenReEnabled_callsProcess() throws Throwable {
         resetEmojiCompatToNewMock();
         ViewType subject = mActivityTestRule.getActivity()
@@ -131,7 +126,6 @@
     }
 
     @Test
-    @SdkSuppress(minSdkVersion = 19)
     public void whenNotConfigured_andDisabled_doesNotEnable_whenConfigured() throws Throwable {
         EmojiCompat.reset((EmojiCompat) null);
         mActivityTestRule.finishActivity();
@@ -168,7 +162,6 @@
     }
 
     @Test
-    @SdkSuppress(minSdkVersion = 19)
     public void whenNotConfigured_callingEnabled_afterConfigure_enablesEmoji() throws Throwable {
         EmojiCompat.reset((EmojiCompat) null);
         mActivityTestRule.finishActivity();
@@ -188,7 +181,6 @@
     }
 
     @Test
-    @SdkSuppress(minSdkVersion = 19)
     public void getEnabled() throws Throwable {
         ActivityType activity = mActivityTestRule.getActivity();
         ViewType disabledInAdvance =
diff --git a/appcompat/appcompat/src/androidTest/java/androidx/appcompat/widget/AppCompatCheckBoxTest.java b/appcompat/appcompat/src/androidTest/java/androidx/appcompat/widget/AppCompatCheckBoxTest.java
index bf5cd46..d618015 100644
--- a/appcompat/appcompat/src/androidTest/java/androidx/appcompat/widget/AppCompatCheckBoxTest.java
+++ b/appcompat/appcompat/src/androidTest/java/androidx/appcompat/widget/AppCompatCheckBoxTest.java
@@ -119,7 +119,6 @@
                 TextViewCompat.getCompoundDrawableTintList(textView));
     }
 
-    @SdkSuppress(minSdkVersion = 17)
     @Test
     public void testCompoundDrawableRelativeTint() {
         // Given an ACTV with a white drawableStartCompat set and a #f0f drawableTint
diff --git a/appcompat/appcompat/src/androidTest/java/androidx/appcompat/widget/AppCompatCheckedTextViewTest.java b/appcompat/appcompat/src/androidTest/java/androidx/appcompat/widget/AppCompatCheckedTextViewTest.java
index d20446c..6f85a79 100644
--- a/appcompat/appcompat/src/androidTest/java/androidx/appcompat/widget/AppCompatCheckedTextViewTest.java
+++ b/appcompat/appcompat/src/androidTest/java/androidx/appcompat/widget/AppCompatCheckedTextViewTest.java
@@ -188,7 +188,6 @@
                 TextViewCompat.getCompoundDrawableTintList(textView));
     }
 
-    @SdkSuppress(minSdkVersion = 17)
     @Test
     public void testCompoundDrawableRelativeTint() {
         // Given an ACTV with a white drawableStartCompat set and a #f0f drawableTint
diff --git a/appcompat/appcompat/src/androidTest/java/androidx/appcompat/widget/AppCompatEditTextTest.java b/appcompat/appcompat/src/androidTest/java/androidx/appcompat/widget/AppCompatEditTextTest.java
index 88b1592..444bb86 100644
--- a/appcompat/appcompat/src/androidTest/java/androidx/appcompat/widget/AppCompatEditTextTest.java
+++ b/appcompat/appcompat/src/androidTest/java/androidx/appcompat/widget/AppCompatEditTextTest.java
@@ -281,7 +281,6 @@
                 TextViewCompat.getCompoundDrawableTintList(textView));
     }
 
-    @SdkSuppress(minSdkVersion = 17)
     @Test
     public void testCompoundDrawableRelativeTint() {
         // Given an ACTV with a white drawableStartCompat set and a #f0f drawableTint
diff --git a/appcompat/appcompat/src/androidTest/java/androidx/appcompat/widget/AppCompatMultiAutoCompleteTextViewTest.java b/appcompat/appcompat/src/androidTest/java/androidx/appcompat/widget/AppCompatMultiAutoCompleteTextViewTest.java
index c634b6d..7d587bd 100644
--- a/appcompat/appcompat/src/androidTest/java/androidx/appcompat/widget/AppCompatMultiAutoCompleteTextViewTest.java
+++ b/appcompat/appcompat/src/androidTest/java/androidx/appcompat/widget/AppCompatMultiAutoCompleteTextViewTest.java
@@ -37,7 +37,6 @@
 import androidx.core.widget.TextViewCompat;
 import androidx.test.annotation.UiThreadTest;
 import androidx.test.filters.LargeTest;
-import androidx.test.filters.SdkSuppress;
 
 import org.junit.Ignore;
 import org.junit.Test;
@@ -114,7 +113,6 @@
                 TextViewCompat.getCompoundDrawableTintList(textView));
     }
 
-    @SdkSuppress(minSdkVersion = 17)
     @Test
     public void testCompoundDrawableRelativeTint() {
         // Given an ACTV with a white drawableStartCompat set and a #f0f drawableTint
diff --git a/appcompat/appcompat/src/androidTest/java/androidx/appcompat/widget/AppCompatRadioButtonTest.java b/appcompat/appcompat/src/androidTest/java/androidx/appcompat/widget/AppCompatRadioButtonTest.java
index 2778c30..2f1d886 100644
--- a/appcompat/appcompat/src/androidTest/java/androidx/appcompat/widget/AppCompatRadioButtonTest.java
+++ b/appcompat/appcompat/src/androidTest/java/androidx/appcompat/widget/AppCompatRadioButtonTest.java
@@ -120,7 +120,6 @@
                 TextViewCompat.getCompoundDrawableTintList(textView));
     }
 
-    @SdkSuppress(minSdkVersion = 17)
     @Test
     public void testCompoundDrawableRelativeTint() {
         // Given an ACTV with a white drawableStartCompat set and a #f0f drawableTint
diff --git a/appcompat/appcompat/src/androidTest/java/androidx/appcompat/widget/AppCompatSpinnerRtlTest.java b/appcompat/appcompat/src/androidTest/java/androidx/appcompat/widget/AppCompatSpinnerRtlTest.java
index 3ff4329..4d29a66 100644
--- a/appcompat/appcompat/src/androidTest/java/androidx/appcompat/widget/AppCompatSpinnerRtlTest.java
+++ b/appcompat/appcompat/src/androidTest/java/androidx/appcompat/widget/AppCompatSpinnerRtlTest.java
@@ -16,10 +16,8 @@
 package androidx.appcompat.widget;
 
 import android.app.Instrumentation;
-import android.os.Build;
 
 import androidx.test.filters.LargeTest;
-import androidx.test.filters.SdkSuppress;
 import androidx.test.platform.app.InstrumentationRegistry;
 
 import org.junit.Test;
@@ -28,7 +26,6 @@
  * This class is for testing RTL-related functionality of {@link AppCompatSpinner}
  */
 @LargeTest
-@SdkSuppress(minSdkVersion = Build.VERSION_CODES.JELLY_BEAN_MR1)
 public class AppCompatSpinnerRtlTest
         extends AppCompatBaseViewTest<AppCompatSpinnerRtlActivity, AppCompatSpinner> {
     private Instrumentation mInstrumentation;
diff --git a/appcompat/appcompat/src/androidTest/java/androidx/appcompat/widget/AppCompatTextViewAutoSizeTest.java b/appcompat/appcompat/src/androidTest/java/androidx/appcompat/widget/AppCompatTextViewAutoSizeTest.java
index caf0354..9e83bac 100644
--- a/appcompat/appcompat/src/androidTest/java/androidx/appcompat/widget/AppCompatTextViewAutoSizeTest.java
+++ b/appcompat/appcompat/src/androidTest/java/androidx/appcompat/widget/AppCompatTextViewAutoSizeTest.java
@@ -26,21 +26,17 @@
 import android.text.method.SingleLineTransformationMethod;
 import android.util.AttributeSet;
 import android.widget.LinearLayout;
-import android.widget.TextView;
 
 import androidx.annotation.Nullable;
 import androidx.appcompat.test.R;
 import androidx.core.widget.TextViewCompat;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.MediumTest;
-import androidx.test.filters.SdkSuppress;
 import androidx.test.filters.SmallTest;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-import java.lang.reflect.Field;
-
 @SmallTest
 @RunWith(AndroidJUnit4.class)
 public class AppCompatTextViewAutoSizeTest extends
@@ -98,35 +94,6 @@
     }
 
     @Test
-    @SdkSuppress(maxSdkVersion = Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1)
-    public void testSpacingIsSetPre16() throws NoSuchFieldException, IllegalAccessException {
-        final AppCompatTextView textView = (AppCompatTextView) mActivity
-                .getLayoutInflater().inflate(R.layout.textview_autosize_maxlines, null);
-        textView.setLineSpacing(5, 5);
-
-        final AppCompatTextViewAutoSizeHelper helper =
-                new AppCompatTextViewAutoSizeHelper(textView);
-        helper.initTempTextPaint(100);
-
-        final String text = mActivity.getResources().getString(R.string.sample_text1);
-        StaticLayout staticLayout = helper.createLayout(text, ALIGN_NORMAL, 100, 1);
-
-        final Field spacingMultField = TextView.class.getDeclaredField("mSpacingMult");
-        spacingMultField.setAccessible(true);
-        final float spacingMultReference = (float) spacingMultField.get(textView);
-        final float spacingMultActual = staticLayout.getSpacingMultiplier();
-        assertEquals(spacingMultReference, spacingMultActual, 0f);
-
-        final Field spacingAddField = TextView.class.getDeclaredField("mSpacingAdd");
-        spacingAddField.setAccessible(true);
-        final float spacingAddReference = (float) spacingAddField.get(textView);
-        final float spacingAddActual = staticLayout.getSpacingAdd();
-        assertEquals(spacingAddReference, spacingAddActual, 0f);
-
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.JELLY_BEAN)
     public void testSpacingIsSet() {
         final AppCompatTextView textView = (AppCompatTextView) mActivity
                 .getLayoutInflater().inflate(R.layout.textview_autosize_maxlines,  null);
diff --git a/appcompat/appcompat/src/androidTest/java/androidx/appcompat/widget/AppCompatTextViewTest.java b/appcompat/appcompat/src/androidTest/java/androidx/appcompat/widget/AppCompatTextViewTest.java
index d392322..c329690 100644
--- a/appcompat/appcompat/src/androidTest/java/androidx/appcompat/widget/AppCompatTextViewTest.java
+++ b/appcompat/appcompat/src/androidTest/java/androidx/appcompat/widget/AppCompatTextViewTest.java
@@ -236,7 +236,6 @@
 
     @Test
     @UiThreadTest
-    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.JELLY_BEAN)
     public void testTextSize_canBeZero() {
         final TextView textView = mContainer.findViewById(R.id.textview_zero_text_size);
         // text size should be 0 as set in xml, rather than the text view default (15.0)
@@ -463,7 +462,6 @@
         assertEquals(Typeface.SERIF, textView.getTypeface());
     }
 
-    @SdkSuppress(minSdkVersion = 16)
     @Test
     @UiThreadTest
     public void testfontFamilyNamespaceHierarchy() {
@@ -774,7 +772,6 @@
         assertNotNull(compoundDrawables[3]);
     }
 
-    @SdkSuppress(minSdkVersion = 17)
     @Test
     public void testCompoundDrawablesCompat_relative() {
         // Given an ACTV with both drawableStartCompat and drawableEndCompat set
@@ -786,19 +783,6 @@
         assertNotNull(compoundDrawablesRelative[2]);
     }
 
-    @SdkSuppress(maxSdkVersion = 16)
-    @Test
-    public void testCompoundDrawablesCompat_relativeIgnoredPre17() {
-        // Given an ACTV with both drawableStartCompat and drawableEndCompat set
-        final AppCompatTextView textView = mActivity.findViewById(
-                R.id.text_view_compound_drawables_compat_relative);
-        // Then both should be ignored before API17
-        final Drawable[] compoundDrawables = textView.getCompoundDrawables();
-        assertNull(compoundDrawables[0]);
-        assertNull(compoundDrawables[2]);
-    }
-
-    @SdkSuppress(minSdkVersion = 17)
     @Test
     public void testCompoundDrawablesCompat_relativeAndAbsolute() {
         // Given an ACTV with both drawableStartCompat and drawableRightCompat set
@@ -839,7 +823,6 @@
         assertNotNull(compoundDrawables[3]);
     }
 
-    @SdkSuppress(minSdkVersion = 17)
     @Test
     public void testCompoundDrawablesRelative_platformCompatCoexist() {
         // Given an ACTV with app:drawableStartCompat & android:drawableEnd set
@@ -851,7 +834,6 @@
         assertNotNull(compoundDrawables[2]);
     }
 
-    @SdkSuppress(minSdkVersion = 17)
     @Test
     public void testCompoundDrawables_relativePlatform_ignoresCompatAbsolute() {
         // Given an ACTV with app:drawableLeftCompat & android:drawableEnd set
@@ -863,7 +845,6 @@
         assertNull(textView.getCompoundDrawablesRelative()[0]);
     }
 
-    @SdkSuppress(minSdkVersion = 17)
     @Test
     public void testCompoundDrawables_relativeCompat_ignoresPlatformAbsolute() {
         // Given an ACTV with app:drawableStartCompat & android:drawableRight set
@@ -1033,7 +1014,6 @@
                 TextViewCompat.getCompoundDrawableTintList(textView));
     }
 
-    @SdkSuppress(minSdkVersion = 17)
     @Test
     public void testCompoundDrawableRelativeTint() {
         // Given an ACTV with a white drawableStartCompat set and a #f0f drawableTint
diff --git a/appcompat/appcompat/src/androidTest/java/androidx/appcompat/widget/AppCompatToggleButtonTest.java b/appcompat/appcompat/src/androidTest/java/androidx/appcompat/widget/AppCompatToggleButtonTest.java
index 57a3567..8ff6cae 100644
--- a/appcompat/appcompat/src/androidTest/java/androidx/appcompat/widget/AppCompatToggleButtonTest.java
+++ b/appcompat/appcompat/src/androidTest/java/androidx/appcompat/widget/AppCompatToggleButtonTest.java
@@ -35,7 +35,6 @@
 import androidx.core.widget.TextViewCompat;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.MediumTest;
-import androidx.test.filters.SdkSuppress;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -88,7 +87,6 @@
                 TextViewCompat.getCompoundDrawableTintList(textView));
     }
 
-    @SdkSuppress(minSdkVersion = 17)
     @Test
     public void testCompoundDrawableRelativeTint() {
         // Given an ACTV with a white drawableStartCompat set and a #f0f drawableTint
diff --git a/appcompat/appcompat/src/androidTest/java/androidx/appcompat/widget/SearchView_CursorTest.java b/appcompat/appcompat/src/androidTest/java/androidx/appcompat/widget/SearchView_CursorTest.java
index af7488a..8d055f7 100644
--- a/appcompat/appcompat/src/androidTest/java/androidx/appcompat/widget/SearchView_CursorTest.java
+++ b/appcompat/appcompat/src/androidTest/java/androidx/appcompat/widget/SearchView_CursorTest.java
@@ -42,7 +42,6 @@
 import androidx.test.annotation.UiThreadTest;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.MediumTest;
-import androidx.test.filters.SdkSuppress;
 import androidx.test.platform.app.InstrumentationRegistry;
 import androidx.test.rule.ActivityTestRule;
 import androidx.testutils.PollingCheck;
@@ -171,7 +170,6 @@
 
     // ViewTreeObserver.OnDrawListener (used for waiting for the redraw pass on
     // emulating a tap) is only available on 16+
-    @SdkSuppress(minSdkVersion = 16)
     @Test
     public void testSuggestionSelection() throws Throwable {
         final SearchView.OnSuggestionListener mockSuggestionListener =
diff --git a/appcompat/appcompat/src/androidTest/java/androidx/appcompat/widget/SwitchCompatEmojiTest.java b/appcompat/appcompat/src/androidTest/java/androidx/appcompat/widget/SwitchCompatEmojiTest.java
index 9b84be7..d598561 100644
--- a/appcompat/appcompat/src/androidTest/java/androidx/appcompat/widget/SwitchCompatEmojiTest.java
+++ b/appcompat/appcompat/src/androidTest/java/androidx/appcompat/widget/SwitchCompatEmojiTest.java
@@ -26,7 +26,6 @@
 import androidx.appcompat.test.R;
 import androidx.emoji2.text.EmojiCompat;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SdkSuppress;
 import androidx.test.filters.SmallTest;
 
 import org.junit.Test;
@@ -42,7 +41,6 @@
     }
 
     @Test
-    @SdkSuppress(minSdkVersion = 19)
     public void setupCallbacks_whenLoading() throws Throwable {
         SwitchCompat emojiEnabled = mActivityTestRule.getActivity()
                 .findViewById(R.id.emoji_enabled);
@@ -57,7 +55,6 @@
     }
 
     @Test
-    @SdkSuppress(minSdkVersion = 19)
     public void dontSetupCallbacks_whenDisabled() throws Throwable {
         SwitchCompat emojiDisabled =
                 mActivityTestRule.getActivity().findViewById(R.id.emoji_disabled);
@@ -72,7 +69,6 @@
     }
 
     @Test
-    @SdkSuppress(minSdkVersion = 19)
     public void noInteractions_whenDisnabled_andRepeatedShowText() {
         SwitchCompat emojiDisabled =
                 mActivityTestRule.getActivity().findViewById(R.id.emoji_disabled);
@@ -90,7 +86,6 @@
     }
 
     @Test
-    @SdkSuppress(minSdkVersion = 19)
     public void setupCallbacks_whenLoading_andEnabledLate() throws Throwable {
         SwitchCompat emojiDisabled =
                 mActivityTestRule.getActivity().findViewById(R.id.emoji_disabled);
@@ -108,7 +103,6 @@
     }
 
     @Test
-    @SdkSuppress(minSdkVersion = 19)
     public void whenSetShowText_doesTransform() {
         SwitchCompat emojiEnabled =
                 mActivityTestRule.getActivity().findViewById(R.id.emoji_enabled);
@@ -120,7 +114,6 @@
     }
 
     @Test
-    @SdkSuppress(minSdkVersion = 19)
     public void whenSetTextOn_doesTransform() {
         SwitchCompat emojiEnabled =
                 mActivityTestRule.getActivity().findViewById(R.id.emoji_enabled);
@@ -133,7 +126,6 @@
     }
 
     @Test
-    @SdkSuppress(minSdkVersion = 19)
     public void whenSetTextOff_doesTransform() {
         SwitchCompat emojiEnabled =
                 mActivityTestRule.getActivity().findViewById(R.id.emoji_enabled);
diff --git a/appsearch/appsearch/api/current.txt b/appsearch/appsearch/api/current.txt
index 4f5cde6..4e448cd 100644
--- a/appsearch/appsearch/api/current.txt
+++ b/appsearch/appsearch/api/current.txt
@@ -292,7 +292,7 @@
     method public String getSchemaType();
     method public int getScore();
     method public long getTtlMillis();
-    method public androidx.appsearch.app.GenericDocument.Builder<androidx.appsearch.app.GenericDocument.Builder<?>!> toBuilder();
+    method @Deprecated public androidx.appsearch.app.GenericDocument.Builder<androidx.appsearch.app.GenericDocument.Builder<?>!> toBuilder();
     method public <T> T toDocumentClass(Class<T!>) throws androidx.appsearch.exceptions.AppSearchException;
   }
 
diff --git a/appsearch/appsearch/api/restricted_current.txt b/appsearch/appsearch/api/restricted_current.txt
index 4f5cde6..4e448cd 100644
--- a/appsearch/appsearch/api/restricted_current.txt
+++ b/appsearch/appsearch/api/restricted_current.txt
@@ -292,7 +292,7 @@
     method public String getSchemaType();
     method public int getScore();
     method public long getTtlMillis();
-    method public androidx.appsearch.app.GenericDocument.Builder<androidx.appsearch.app.GenericDocument.Builder<?>!> toBuilder();
+    method @Deprecated public androidx.appsearch.app.GenericDocument.Builder<androidx.appsearch.app.GenericDocument.Builder<?>!> toBuilder();
     method public <T> T toDocumentClass(Class<T!>) throws androidx.appsearch.exceptions.AppSearchException;
   }
 
diff --git a/appsearch/appsearch/src/main/java/androidx/appsearch/app/GenericDocument.java b/appsearch/appsearch/src/main/java/androidx/appsearch/app/GenericDocument.java
index 16db5be..b7da309 100644
--- a/appsearch/appsearch/src/main/java/androidx/appsearch/app/GenericDocument.java
+++ b/appsearch/appsearch/src/main/java/androidx/appsearch/app/GenericDocument.java
@@ -960,10 +960,13 @@
      * {@link GenericDocument.Builder}.
      *
      * <p>The returned builder is a deep copy whose data is separate from this document.
+     * @deprecated This API is not compliant with API guidelines.
+     * Use {@link Builder#Builder(GenericDocument)} instead.
      * <!--@exportToFramework:hide-->
      */
     // TODO(b/171882200): Expose this API in Android T
     @NonNull
+    @Deprecated
     public GenericDocument.Builder<GenericDocument.Builder<?>> toBuilder() {
         Bundle clonedBundle = BundleUtil.deepCopy(mBundle);
         return new GenericDocument.Builder<>(clonedBundle);
diff --git a/benchmark/baseline-profile-gradle-plugin/build.gradle b/benchmark/baseline-profile-gradle-plugin/build.gradle
index 0fe910f..623ad21 100644
--- a/benchmark/baseline-profile-gradle-plugin/build.gradle
+++ b/benchmark/baseline-profile-gradle-plugin/build.gradle
@@ -29,6 +29,12 @@
     neededForGradleTestKit {
         canBeResolved = true
     }
+    neededForGradleTestKitAgp80 {
+        canBeResolved = true
+    }
+    neededForGradleTestKitAgp81 {
+        canBeResolved = true
+    }
 }
 
 dependencies {
@@ -48,6 +54,9 @@
     neededForGradleTestKit(libs.androidGradlePluginz)
     neededForGradleTestKit(libs.kotlinGradlePluginz)
     neededForGradleTestKit(libs.kotlinStdlib)
+
+    neededForGradleTestKitAgp80("com.android.tools.build:gradle:8.0.0")
+    neededForGradleTestKitAgp81("com.android.tools.build:gradle:8.1.0")
 }
 
 SdkResourceGenerator.generateForHostTest(project)
diff --git a/benchmark/baseline-profile-gradle-plugin/src/main/kotlin/androidx/baselineprofile/gradle/producer/BaselineProfileProducerPlugin.kt b/benchmark/baseline-profile-gradle-plugin/src/main/kotlin/androidx/baselineprofile/gradle/producer/BaselineProfileProducerPlugin.kt
index 9f865e4..0dd8f36 100644
--- a/benchmark/baseline-profile-gradle-plugin/src/main/kotlin/androidx/baselineprofile/gradle/producer/BaselineProfileProducerPlugin.kt
+++ b/benchmark/baseline-profile-gradle-plugin/src/main/kotlin/androidx/baselineprofile/gradle/producer/BaselineProfileProducerPlugin.kt
@@ -75,6 +75,9 @@
     private val shouldSkipGeneration by lazy {
         project.properties.containsKey(PROP_SKIP_GENERATION)
     }
+    private val forceOnlyConnectedDevices: Boolean by lazy {
+        project.properties.containsKey(PROP_FORCE_ONLY_CONNECTED_DEVICES)
+    }
 
     // This maps all the extended build types to the original ones. Note that release does not
     // exist by default so we need to create nonMinifiedRelease and map it manually to `release`.
@@ -233,7 +236,8 @@
         // If this is a benchmark variant sets the instrumentation runner argument to run only
         // tests with MacroBenchmark rules.
         if (enabledRulesNotSet &&
-            variant.buildType in benchmarkExtendedToOriginalTypeMap.keys) {
+            variant.buildType in benchmarkExtendedToOriginalTypeMap.keys
+        ) {
             if (supportsFeature(TEST_VARIANT_SUPPORTS_INSTRUMENTATION_RUNNER_ARGUMENTS)) {
                 InstrumentationTestRunnerArgumentsAgp82.set(
                     variant = variant,
@@ -254,7 +258,8 @@
             // If this is a benchmark variant sets the instrumentation runner argument to run only
             // tests with MacroBenchmark rules.
             if (enabledRulesNotSet &&
-                supportsFeature(TEST_VARIANT_SUPPORTS_INSTRUMENTATION_RUNNER_ARGUMENTS)) {
+                supportsFeature(TEST_VARIANT_SUPPORTS_INSTRUMENTATION_RUNNER_ARGUMENTS)
+            ) {
                 InstrumentationTestRunnerArgumentsAgp82.set(
                     variant = variant,
                     arguments = listOf(
@@ -305,9 +310,17 @@
     ) {
 
         // Prepares the devices list to use to generate the baseline profile.
+        // Note that when running gradle with
+        // `androidx.baselineprofile.forceonlyconnecteddevices=false`
+        // this DSL specification is not respected. This is used by Android Studio to run
+        // baseline profile generation only on the selected devices.
         val devices = mutableSetOf<String>()
-        devices.addAll(baselineProfileExtension.managedDevices)
-        if (baselineProfileExtension.useConnectedDevices) devices.add("connected")
+        if (forceOnlyConnectedDevices) {
+            devices.add("connected")
+        } else {
+            devices.addAll(baselineProfileExtension.managedDevices)
+            if (baselineProfileExtension.useConnectedDevices) devices.add("connected")
+        }
 
         // The test task runs the ui tests
         val testTasks = devices.map { device ->
diff --git a/benchmark/baseline-profile-gradle-plugin/src/main/kotlin/androidx/baselineprofile/gradle/producer/BaselineProfileProducerProperties.kt b/benchmark/baseline-profile-gradle-plugin/src/main/kotlin/androidx/baselineprofile/gradle/producer/BaselineProfileProducerProperties.kt
index 1efc5e8..51161bf 100644
--- a/benchmark/baseline-profile-gradle-plugin/src/main/kotlin/androidx/baselineprofile/gradle/producer/BaselineProfileProducerProperties.kt
+++ b/benchmark/baseline-profile-gradle-plugin/src/main/kotlin/androidx/baselineprofile/gradle/producer/BaselineProfileProducerProperties.kt
@@ -29,3 +29,11 @@
  * -Pandroid.testoptions.manageddevices.emulator.gpu="swiftshader_indirect.
  */
 internal const val PROP_SKIP_GENERATION = "androidx.baselineprofile.skipgeneration"
+
+/**
+ * This property determines whether the baselineProfile dsl specification for `managedDevices` and
+ * `useConnectedDevices` is respected. When this property is set to to true only connected devices
+ * are used and managed devices are ignored.
+ */
+internal const val PROP_FORCE_ONLY_CONNECTED_DEVICES =
+    "androidx.baselineprofile.forceonlyconnecteddevices"
diff --git a/benchmark/baseline-profile-gradle-plugin/src/test/kotlin/androidx/baselineprofile/gradle/consumer/BaselineProfileConsumerPluginTest.kt b/benchmark/baseline-profile-gradle-plugin/src/test/kotlin/androidx/baselineprofile/gradle/consumer/BaselineProfileConsumerPluginTest.kt
index 987a388..b264081 100644
--- a/benchmark/baseline-profile-gradle-plugin/src/test/kotlin/androidx/baselineprofile/gradle/consumer/BaselineProfileConsumerPluginTest.kt
+++ b/benchmark/baseline-profile-gradle-plugin/src/test/kotlin/androidx/baselineprofile/gradle/consumer/BaselineProfileConsumerPluginTest.kt
@@ -24,7 +24,6 @@
 import androidx.baselineprofile.gradle.utils.TestAgpVersion
 import androidx.baselineprofile.gradle.utils.TestAgpVersion.TEST_AGP_VERSION_8_0_0
 import androidx.baselineprofile.gradle.utils.TestAgpVersion.TEST_AGP_VERSION_8_1_0
-import androidx.baselineprofile.gradle.utils.TestAgpVersion.TEST_AGP_VERSION_8_2_0
 import androidx.baselineprofile.gradle.utils.TestAgpVersion.TEST_AGP_VERSION_CURRENT
 import androidx.baselineprofile.gradle.utils.VariantProfile
 import androidx.baselineprofile.gradle.utils.build
@@ -71,8 +70,9 @@
     private fun mergedArtProfile(variantName: String): File {
         // Task name folder in path was first observed in the update to AGP 8.3.0-alpha10.
         // Before that, the folder was omitted in path.
+        // TODO: Add back TEST_AGP_VERSION_8_2_0 after b/309493780
         val taskNameFolder = when (agpVersion) {
-            TEST_AGP_VERSION_8_0_0, TEST_AGP_VERSION_8_1_0, TEST_AGP_VERSION_8_2_0 -> ""
+            TEST_AGP_VERSION_8_0_0, TEST_AGP_VERSION_8_1_0 -> ""
             TEST_AGP_VERSION_CURRENT -> camelCase("merge", variantName, "artProfile")
         }
         return File(
diff --git a/benchmark/baseline-profile-gradle-plugin/src/test/kotlin/androidx/baselineprofile/gradle/producer/BaselineProfileProducerPluginTest.kt b/benchmark/baseline-profile-gradle-plugin/src/test/kotlin/androidx/baselineprofile/gradle/producer/BaselineProfileProducerPluginTest.kt
index 7a47707..797ca38 100644
--- a/benchmark/baseline-profile-gradle-plugin/src/test/kotlin/androidx/baselineprofile/gradle/producer/BaselineProfileProducerPluginTest.kt
+++ b/benchmark/baseline-profile-gradle-plugin/src/test/kotlin/androidx/baselineprofile/gradle/producer/BaselineProfileProducerPluginTest.kt
@@ -20,13 +20,13 @@
 import androidx.baselineprofile.gradle.utils.TestAgpVersion
 import androidx.baselineprofile.gradle.utils.TestAgpVersion.TEST_AGP_VERSION_8_0_0
 import androidx.baselineprofile.gradle.utils.TestAgpVersion.TEST_AGP_VERSION_8_1_0
-import androidx.baselineprofile.gradle.utils.TestAgpVersion.TEST_AGP_VERSION_8_2_0
 import androidx.baselineprofile.gradle.utils.VariantProfile
 import androidx.baselineprofile.gradle.utils.build
 import androidx.baselineprofile.gradle.utils.buildAndAssertThatOutput
 import androidx.baselineprofile.gradle.utils.buildAndFailAndAssertThatOutput
 import androidx.baselineprofile.gradle.utils.require
 import com.google.common.truth.Truth.assertThat
+import org.junit.Ignore
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -103,7 +103,8 @@
 
     @get:Rule
     val projectSetup = BaselineProfileProjectSetupRule(
-        forceAgpVersion = TEST_AGP_VERSION_8_2_0.versionString
+        // TODO: Update after b/309493780
+        forceAgpVersion = null
     )
 
     private val emptyReleaseVariantProfile = VariantProfile(
@@ -112,6 +113,7 @@
         profileFileLines = mapOf()
     )
 
+    @Ignore("b/309493780")
     @Test
     fun verifyInstrumentationRunnerArgumentsAreSet() {
         projectSetup.appTarget.setup()
@@ -151,6 +153,7 @@
             }
     }
 
+    @Ignore("b/309493780")
     @Test
     fun runWhenInstrumentationRunnerArgumentsAreSetManually() {
         projectSetup.appTarget.setup()
@@ -287,4 +290,59 @@
                 assertThat(notFound).isEmpty()
             }
     }
+
+    @Test
+    fun whenUseOnlyConnectedDevicesShouldOverrideDsl() {
+        projectSetup.appTarget.setup()
+        projectSetup.producer.setup(
+            variantProfiles = listOf(emptyReleaseVariantProfile),
+            targetProject = projectSetup.appTarget,
+            managedDevices = listOf("somePixelDevice"),
+            baselineProfileBlock = """
+                managedDevices = ["somePixelDevice"]
+                useConnectedDevices = false
+            """.trimIndent()
+        )
+
+        // Execute any task and check the expected output.
+        // Note that executing `somePixelDeviceSetup` will fail for `LicenseNotAcceptedException`.
+        projectSetup
+            .producer
+            .gradleRunner
+            .buildAndAssertThatOutput(
+                "collectNonMinifiedReleaseBaselineProfile",
+                "--dry-run",
+                "-Pandroidx.baselineprofile.forceonlyconnecteddevices"
+            ) {
+                contains("connectedNonMinifiedReleaseAndroidTest")
+                doesNotContain("somePixelDeviceNonMinifiedReleaseAndroidTest")
+            }
+    }
+
+    @Test
+    fun whenNotUseOnlyConnectedDevicesShouldOverrideDsl() {
+        projectSetup.appTarget.setup()
+        projectSetup.producer.setup(
+            variantProfiles = listOf(emptyReleaseVariantProfile),
+            targetProject = projectSetup.appTarget,
+            managedDevices = listOf("somePixelDevice"),
+            baselineProfileBlock = """
+                managedDevices = ["somePixelDevice"]
+                useConnectedDevices = false
+            """.trimIndent()
+        )
+
+        // Execute any task and check the expected output.
+        // Note that executing `somePixelDeviceSetup` will fail for `LicenseNotAcceptedException`.
+        projectSetup
+            .producer
+            .gradleRunner
+            .buildAndAssertThatOutput(
+                "collectNonMinifiedReleaseBaselineProfile",
+                "--dry-run",
+            ) {
+                doesNotContain("connectedNonMinifiedReleaseAndroidTest")
+                contains("somePixelDeviceNonMinifiedReleaseAndroidTest")
+            }
+    }
 }
diff --git a/benchmark/baseline-profile-gradle-plugin/src/test/kotlin/androidx/baselineprofile/gradle/utils/Constants.kt b/benchmark/baseline-profile-gradle-plugin/src/test/kotlin/androidx/baselineprofile/gradle/utils/Constants.kt
index 93f94b9..063bcd6 100644
--- a/benchmark/baseline-profile-gradle-plugin/src/test/kotlin/androidx/baselineprofile/gradle/utils/Constants.kt
+++ b/benchmark/baseline-profile-gradle-plugin/src/test/kotlin/androidx/baselineprofile/gradle/utils/Constants.kt
@@ -19,6 +19,6 @@
 enum class TestAgpVersion(val versionString: String?) {
     TEST_AGP_VERSION_8_0_0("8.0.0"),
     TEST_AGP_VERSION_8_1_0("8.1.0"),
-    TEST_AGP_VERSION_8_2_0("8.2.0-alpha04"),
+    // TODO: add back `TEST_AGP_VERSION_8_2_0("8.2.0")` after b/309493780,
     TEST_AGP_VERSION_CURRENT(null)
 }
diff --git a/benchmark/benchmark-darwin-gradle-plugin/README.md b/benchmark/benchmark-darwin-gradle-plugin/README.md
index 83118c3..e2cc7e6 100644
--- a/benchmark/benchmark-darwin-gradle-plugin/README.md
+++ b/benchmark/benchmark-darwin-gradle-plugin/README.md
@@ -30,8 +30,7 @@
     scheme = "testapp-ios"
 
     // Destination
-    // ios 13, 17.0
-    destination = "platform=iOS Simulator,name=iPhone 13,OS=17.0"
+    destination = "platform=iOS Simulator,name=iPhone 13,OS=15.2"
     // Or a target device id
     destination = "id=7F61C467-4E4A-437C-B6EF-026FEEF3904C"
 
diff --git a/benchmark/benchmark-darwin-samples/build.gradle b/benchmark/benchmark-darwin-samples/build.gradle
index ae16801..ba8ccdf 100644
--- a/benchmark/benchmark-darwin-samples/build.gradle
+++ b/benchmark/benchmark-darwin-samples/build.gradle
@@ -58,8 +58,8 @@
     )
     xcodeProjectName = "benchmark-darwin-samples-xcode"
     scheme = "testapp-ios"
-    // ios 13, 17.0
-    destination = "platform=iOS Simulator,name=iPhone 13,OS=17.0"
+    // To run locally switch to iOS 17.0 simulators
+    destination = "platform=iOS Simulator,name=iPhone 13,OS=15.2"
     referenceSha.set(androidx.getReferenceSha())
 }
 
diff --git a/benchmark/benchmark-macro/src/androidTest/java/androidx/benchmark/macro/MacrobenchmarkScopeTest.kt b/benchmark/benchmark-macro/src/androidTest/java/androidx/benchmark/macro/MacrobenchmarkScopeTest.kt
index 9dc1b04..cabf352 100644
--- a/benchmark/benchmark-macro/src/androidTest/java/androidx/benchmark/macro/MacrobenchmarkScopeTest.kt
+++ b/benchmark/benchmark-macro/src/androidTest/java/androidx/benchmark/macro/MacrobenchmarkScopeTest.kt
@@ -227,11 +227,11 @@
         }.toSet()
         val testOutputs = outputs - files
         val trace = testOutputs.singleOrNull { file ->
-            file.name.endsWith(".trace") && file.name.contains("-method-")
+            file.name.endsWith(".trace") && file.name.contains("-methodTracing-")
         }
         // One method trace should have been created
         assertNotNull(trace)
-        assertTrue(trace.name.startsWith("TEST-UNIQUE-NAME-method-"))
+        assertTrue(trace.name.startsWith("TEST-UNIQUE-NAME-methodTracing-"))
     }
 
     private fun validateLaunchAndFrameStats(pressHome: Boolean) {
diff --git a/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/MacrobenchmarkScope.kt b/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/MacrobenchmarkScope.kt
index b8ed386..a0cf685 100644
--- a/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/MacrobenchmarkScope.kt
+++ b/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/MacrobenchmarkScope.kt
@@ -328,7 +328,7 @@
             pollDurationMs = 50L
         )
         // unique label so source is clear, dateToFileName so each run of test is unique on host
-        val outputFileName = "$uniqueLabel-method-${dateToFileName()}.trace"
+        val outputFileName = "$uniqueLabel-methodTracing-${dateToFileName()}.trace"
         val stagingFile = File.createTempFile("methodTrace", null, Outputs.dirUsableByAppAndShell)
         // Staging location before we write it again using Outputs.writeFile(...)
         // NOTE: staging copy may be unnecessary if we just use a single `cp`
diff --git a/bluetooth/bluetooth/api/current.txt b/bluetooth/bluetooth/api/current.txt
index 7053a03..d4c194c1 100644
--- a/bluetooth/bluetooth/api/current.txt
+++ b/bluetooth/bluetooth/api/current.txt
@@ -52,8 +52,8 @@
   public final class BluetoothLe {
     ctor public BluetoothLe(android.content.Context context);
     method @RequiresPermission("android.permission.BLUETOOTH_ADVERTISE") public suspend Object? advertise(androidx.bluetooth.AdvertiseParams advertiseParams, optional kotlin.jvm.functions.Function2<? super java.lang.Integer,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,?>? block, kotlin.coroutines.Continuation<? super kotlin.Unit>);
-    method @RequiresPermission("android.permission.BLUETOOTH_CONNECT") public suspend <R> Object? connectGatt(androidx.bluetooth.BluetoothDevice device, kotlin.jvm.functions.Function2<? super androidx.bluetooth.BluetoothLe.GattClientScope,? super kotlin.coroutines.Continuation<? super R>,?> block, kotlin.coroutines.Continuation<? super R>);
-    method public suspend <R> Object? openGattServer(java.util.List<androidx.bluetooth.GattService> services, kotlin.jvm.functions.Function2<? super androidx.bluetooth.BluetoothLe.GattServerConnectScope,? super kotlin.coroutines.Continuation<? super R>,?> block, kotlin.coroutines.Continuation<? super R>);
+    method @RequiresPermission("android.permission.BLUETOOTH_CONNECT") public suspend <R> Object? connectGatt(androidx.bluetooth.BluetoothDevice device, kotlin.jvm.functions.Function2<? super androidx.bluetooth.GattClientScope,? super kotlin.coroutines.Continuation<? super R>,?> block, kotlin.coroutines.Continuation<? super R>);
+    method public suspend <R> Object? openGattServer(java.util.List<androidx.bluetooth.GattService> services, kotlin.jvm.functions.Function2<? super androidx.bluetooth.GattServerConnectScope,? super kotlin.coroutines.Continuation<? super R>,?> block, kotlin.coroutines.Continuation<? super R>);
     method @RequiresPermission("android.permission.BLUETOOTH_SCAN") public kotlinx.coroutines.flow.Flow<androidx.bluetooth.ScanResult> scan(optional java.util.List<androidx.bluetooth.ScanFilter> filters);
     field public static final int ADVERTISE_FAILED_DATA_TOO_LARGE = 102; // 0x66
     field public static final int ADVERTISE_FAILED_FEATURE_UNSUPPORTED = 103; // 0x67
@@ -66,40 +66,6 @@
   public static final class BluetoothLe.Companion {
   }
 
-  public static interface BluetoothLe.GattClientScope {
-    method public androidx.bluetooth.GattService? getService(java.util.UUID uuid);
-    method public default java.util.List<androidx.bluetooth.GattService> getServices();
-    method public kotlinx.coroutines.flow.StateFlow<java.util.List<androidx.bluetooth.GattService>> getServicesFlow();
-    method public suspend Object? readCharacteristic(androidx.bluetooth.GattCharacteristic characteristic, kotlin.coroutines.Continuation<? super kotlin.Result<? extends byte[]>>);
-    method public kotlinx.coroutines.flow.Flow<byte[]> subscribeToCharacteristic(androidx.bluetooth.GattCharacteristic characteristic);
-    method public suspend Object? writeCharacteristic(androidx.bluetooth.GattCharacteristic characteristic, byte[] value, kotlin.coroutines.Continuation<? super kotlin.Result<? extends kotlin.Unit>>);
-    property public default java.util.List<androidx.bluetooth.GattService> services;
-    property public abstract kotlinx.coroutines.flow.StateFlow<java.util.List<androidx.bluetooth.GattService>> servicesFlow;
-  }
-
-  public static final class BluetoothLe.GattServerConnectRequest {
-    method public suspend Object? accept(kotlin.jvm.functions.Function2<? super androidx.bluetooth.BluetoothLe.GattServerSessionScope,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,?> block, kotlin.coroutines.Continuation<? super kotlin.Unit>);
-    method public androidx.bluetooth.BluetoothDevice getDevice();
-    method public void reject();
-    property public final androidx.bluetooth.BluetoothDevice device;
-  }
-
-  public static interface BluetoothLe.GattServerConnectScope {
-    method public kotlinx.coroutines.flow.Flow<androidx.bluetooth.BluetoothLe.GattServerConnectRequest> getConnectRequests();
-    method public void updateServices(java.util.List<androidx.bluetooth.GattService> services);
-    property public abstract kotlinx.coroutines.flow.Flow<androidx.bluetooth.BluetoothLe.GattServerConnectRequest> connectRequests;
-  }
-
-  public static interface BluetoothLe.GattServerSessionScope {
-    method public androidx.bluetooth.BluetoothDevice getDevice();
-    method public kotlinx.coroutines.flow.Flow<androidx.bluetooth.GattServerRequest> getRequests();
-    method public kotlinx.coroutines.flow.StateFlow<java.util.Set<androidx.bluetooth.GattCharacteristic>> getSubscribedCharacteristics();
-    method public suspend Object? notify(androidx.bluetooth.GattCharacteristic characteristic, byte[] value, kotlin.coroutines.Continuation<? super kotlin.Unit>);
-    property public abstract androidx.bluetooth.BluetoothDevice device;
-    property public abstract kotlinx.coroutines.flow.Flow<androidx.bluetooth.GattServerRequest> requests;
-    property public abstract kotlinx.coroutines.flow.StateFlow<java.util.Set<androidx.bluetooth.GattCharacteristic>> subscribedCharacteristics;
-  }
-
   public final class GattCharacteristic {
     ctor public GattCharacteristic(java.util.UUID uuid, int properties);
     method public int getProperties();
@@ -120,6 +86,30 @@
   public static final class GattCharacteristic.Companion {
   }
 
+  public interface GattClientScope {
+    method public androidx.bluetooth.GattService? getService(java.util.UUID uuid);
+    method public default java.util.List<androidx.bluetooth.GattService> getServices();
+    method public kotlinx.coroutines.flow.StateFlow<java.util.List<androidx.bluetooth.GattService>> getServicesFlow();
+    method public suspend Object? readCharacteristic(androidx.bluetooth.GattCharacteristic characteristic, kotlin.coroutines.Continuation<? super kotlin.Result<? extends byte[]>>);
+    method public kotlinx.coroutines.flow.Flow<byte[]> subscribeToCharacteristic(androidx.bluetooth.GattCharacteristic characteristic);
+    method public suspend Object? writeCharacteristic(androidx.bluetooth.GattCharacteristic characteristic, byte[] value, kotlin.coroutines.Continuation<? super kotlin.Result<? extends kotlin.Unit>>);
+    property public default java.util.List<androidx.bluetooth.GattService> services;
+    property public abstract kotlinx.coroutines.flow.StateFlow<java.util.List<androidx.bluetooth.GattService>> servicesFlow;
+  }
+
+  public final class GattServerConnectRequest {
+    method public suspend Object? accept(kotlin.jvm.functions.Function2<? super androidx.bluetooth.GattServerSessionScope,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,?> block, kotlin.coroutines.Continuation<? super kotlin.Unit>);
+    method public androidx.bluetooth.BluetoothDevice getDevice();
+    method public void reject();
+    property public final androidx.bluetooth.BluetoothDevice device;
+  }
+
+  public interface GattServerConnectScope {
+    method public kotlinx.coroutines.flow.Flow<androidx.bluetooth.GattServerConnectRequest> getConnectRequests();
+    method public void updateServices(java.util.List<androidx.bluetooth.GattService> services);
+    property public abstract kotlinx.coroutines.flow.Flow<androidx.bluetooth.GattServerConnectRequest> connectRequests;
+  }
+
   public class GattServerRequest {
   }
 
@@ -146,6 +136,16 @@
     property public final byte[] value;
   }
 
+  public interface GattServerSessionScope {
+    method public androidx.bluetooth.BluetoothDevice getDevice();
+    method public kotlinx.coroutines.flow.Flow<androidx.bluetooth.GattServerRequest> getRequests();
+    method public kotlinx.coroutines.flow.StateFlow<java.util.Set<androidx.bluetooth.GattCharacteristic>> getSubscribedCharacteristics();
+    method public suspend Object? notify(androidx.bluetooth.GattCharacteristic characteristic, byte[] value, kotlin.coroutines.Continuation<? super kotlin.Unit>);
+    property public abstract androidx.bluetooth.BluetoothDevice device;
+    property public abstract kotlinx.coroutines.flow.Flow<androidx.bluetooth.GattServerRequest> requests;
+    property public abstract kotlinx.coroutines.flow.StateFlow<java.util.Set<androidx.bluetooth.GattCharacteristic>> subscribedCharacteristics;
+  }
+
   public final class GattService {
     ctor public GattService(java.util.UUID uuid, java.util.List<androidx.bluetooth.GattCharacteristic> characteristics);
     method public androidx.bluetooth.GattCharacteristic? getCharacteristic(java.util.UUID uuid);
diff --git a/bluetooth/bluetooth/api/restricted_current.txt b/bluetooth/bluetooth/api/restricted_current.txt
index 7053a03..d4c194c1 100644
--- a/bluetooth/bluetooth/api/restricted_current.txt
+++ b/bluetooth/bluetooth/api/restricted_current.txt
@@ -52,8 +52,8 @@
   public final class BluetoothLe {
     ctor public BluetoothLe(android.content.Context context);
     method @RequiresPermission("android.permission.BLUETOOTH_ADVERTISE") public suspend Object? advertise(androidx.bluetooth.AdvertiseParams advertiseParams, optional kotlin.jvm.functions.Function2<? super java.lang.Integer,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,?>? block, kotlin.coroutines.Continuation<? super kotlin.Unit>);
-    method @RequiresPermission("android.permission.BLUETOOTH_CONNECT") public suspend <R> Object? connectGatt(androidx.bluetooth.BluetoothDevice device, kotlin.jvm.functions.Function2<? super androidx.bluetooth.BluetoothLe.GattClientScope,? super kotlin.coroutines.Continuation<? super R>,?> block, kotlin.coroutines.Continuation<? super R>);
-    method public suspend <R> Object? openGattServer(java.util.List<androidx.bluetooth.GattService> services, kotlin.jvm.functions.Function2<? super androidx.bluetooth.BluetoothLe.GattServerConnectScope,? super kotlin.coroutines.Continuation<? super R>,?> block, kotlin.coroutines.Continuation<? super R>);
+    method @RequiresPermission("android.permission.BLUETOOTH_CONNECT") public suspend <R> Object? connectGatt(androidx.bluetooth.BluetoothDevice device, kotlin.jvm.functions.Function2<? super androidx.bluetooth.GattClientScope,? super kotlin.coroutines.Continuation<? super R>,?> block, kotlin.coroutines.Continuation<? super R>);
+    method public suspend <R> Object? openGattServer(java.util.List<androidx.bluetooth.GattService> services, kotlin.jvm.functions.Function2<? super androidx.bluetooth.GattServerConnectScope,? super kotlin.coroutines.Continuation<? super R>,?> block, kotlin.coroutines.Continuation<? super R>);
     method @RequiresPermission("android.permission.BLUETOOTH_SCAN") public kotlinx.coroutines.flow.Flow<androidx.bluetooth.ScanResult> scan(optional java.util.List<androidx.bluetooth.ScanFilter> filters);
     field public static final int ADVERTISE_FAILED_DATA_TOO_LARGE = 102; // 0x66
     field public static final int ADVERTISE_FAILED_FEATURE_UNSUPPORTED = 103; // 0x67
@@ -66,40 +66,6 @@
   public static final class BluetoothLe.Companion {
   }
 
-  public static interface BluetoothLe.GattClientScope {
-    method public androidx.bluetooth.GattService? getService(java.util.UUID uuid);
-    method public default java.util.List<androidx.bluetooth.GattService> getServices();
-    method public kotlinx.coroutines.flow.StateFlow<java.util.List<androidx.bluetooth.GattService>> getServicesFlow();
-    method public suspend Object? readCharacteristic(androidx.bluetooth.GattCharacteristic characteristic, kotlin.coroutines.Continuation<? super kotlin.Result<? extends byte[]>>);
-    method public kotlinx.coroutines.flow.Flow<byte[]> subscribeToCharacteristic(androidx.bluetooth.GattCharacteristic characteristic);
-    method public suspend Object? writeCharacteristic(androidx.bluetooth.GattCharacteristic characteristic, byte[] value, kotlin.coroutines.Continuation<? super kotlin.Result<? extends kotlin.Unit>>);
-    property public default java.util.List<androidx.bluetooth.GattService> services;
-    property public abstract kotlinx.coroutines.flow.StateFlow<java.util.List<androidx.bluetooth.GattService>> servicesFlow;
-  }
-
-  public static final class BluetoothLe.GattServerConnectRequest {
-    method public suspend Object? accept(kotlin.jvm.functions.Function2<? super androidx.bluetooth.BluetoothLe.GattServerSessionScope,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,?> block, kotlin.coroutines.Continuation<? super kotlin.Unit>);
-    method public androidx.bluetooth.BluetoothDevice getDevice();
-    method public void reject();
-    property public final androidx.bluetooth.BluetoothDevice device;
-  }
-
-  public static interface BluetoothLe.GattServerConnectScope {
-    method public kotlinx.coroutines.flow.Flow<androidx.bluetooth.BluetoothLe.GattServerConnectRequest> getConnectRequests();
-    method public void updateServices(java.util.List<androidx.bluetooth.GattService> services);
-    property public abstract kotlinx.coroutines.flow.Flow<androidx.bluetooth.BluetoothLe.GattServerConnectRequest> connectRequests;
-  }
-
-  public static interface BluetoothLe.GattServerSessionScope {
-    method public androidx.bluetooth.BluetoothDevice getDevice();
-    method public kotlinx.coroutines.flow.Flow<androidx.bluetooth.GattServerRequest> getRequests();
-    method public kotlinx.coroutines.flow.StateFlow<java.util.Set<androidx.bluetooth.GattCharacteristic>> getSubscribedCharacteristics();
-    method public suspend Object? notify(androidx.bluetooth.GattCharacteristic characteristic, byte[] value, kotlin.coroutines.Continuation<? super kotlin.Unit>);
-    property public abstract androidx.bluetooth.BluetoothDevice device;
-    property public abstract kotlinx.coroutines.flow.Flow<androidx.bluetooth.GattServerRequest> requests;
-    property public abstract kotlinx.coroutines.flow.StateFlow<java.util.Set<androidx.bluetooth.GattCharacteristic>> subscribedCharacteristics;
-  }
-
   public final class GattCharacteristic {
     ctor public GattCharacteristic(java.util.UUID uuid, int properties);
     method public int getProperties();
@@ -120,6 +86,30 @@
   public static final class GattCharacteristic.Companion {
   }
 
+  public interface GattClientScope {
+    method public androidx.bluetooth.GattService? getService(java.util.UUID uuid);
+    method public default java.util.List<androidx.bluetooth.GattService> getServices();
+    method public kotlinx.coroutines.flow.StateFlow<java.util.List<androidx.bluetooth.GattService>> getServicesFlow();
+    method public suspend Object? readCharacteristic(androidx.bluetooth.GattCharacteristic characteristic, kotlin.coroutines.Continuation<? super kotlin.Result<? extends byte[]>>);
+    method public kotlinx.coroutines.flow.Flow<byte[]> subscribeToCharacteristic(androidx.bluetooth.GattCharacteristic characteristic);
+    method public suspend Object? writeCharacteristic(androidx.bluetooth.GattCharacteristic characteristic, byte[] value, kotlin.coroutines.Continuation<? super kotlin.Result<? extends kotlin.Unit>>);
+    property public default java.util.List<androidx.bluetooth.GattService> services;
+    property public abstract kotlinx.coroutines.flow.StateFlow<java.util.List<androidx.bluetooth.GattService>> servicesFlow;
+  }
+
+  public final class GattServerConnectRequest {
+    method public suspend Object? accept(kotlin.jvm.functions.Function2<? super androidx.bluetooth.GattServerSessionScope,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,?> block, kotlin.coroutines.Continuation<? super kotlin.Unit>);
+    method public androidx.bluetooth.BluetoothDevice getDevice();
+    method public void reject();
+    property public final androidx.bluetooth.BluetoothDevice device;
+  }
+
+  public interface GattServerConnectScope {
+    method public kotlinx.coroutines.flow.Flow<androidx.bluetooth.GattServerConnectRequest> getConnectRequests();
+    method public void updateServices(java.util.List<androidx.bluetooth.GattService> services);
+    property public abstract kotlinx.coroutines.flow.Flow<androidx.bluetooth.GattServerConnectRequest> connectRequests;
+  }
+
   public class GattServerRequest {
   }
 
@@ -146,6 +136,16 @@
     property public final byte[] value;
   }
 
+  public interface GattServerSessionScope {
+    method public androidx.bluetooth.BluetoothDevice getDevice();
+    method public kotlinx.coroutines.flow.Flow<androidx.bluetooth.GattServerRequest> getRequests();
+    method public kotlinx.coroutines.flow.StateFlow<java.util.Set<androidx.bluetooth.GattCharacteristic>> getSubscribedCharacteristics();
+    method public suspend Object? notify(androidx.bluetooth.GattCharacteristic characteristic, byte[] value, kotlin.coroutines.Continuation<? super kotlin.Unit>);
+    property public abstract androidx.bluetooth.BluetoothDevice device;
+    property public abstract kotlinx.coroutines.flow.Flow<androidx.bluetooth.GattServerRequest> requests;
+    property public abstract kotlinx.coroutines.flow.StateFlow<java.util.Set<androidx.bluetooth.GattCharacteristic>> subscribedCharacteristics;
+  }
+
   public final class GattService {
     ctor public GattService(java.util.UUID uuid, java.util.List<androidx.bluetooth.GattCharacteristic> characteristics);
     method public androidx.bluetooth.GattCharacteristic? getCharacteristic(java.util.UUID uuid);
diff --git a/bluetooth/bluetooth/src/main/java/androidx/bluetooth/BluetoothAddress.kt b/bluetooth/bluetooth/src/main/java/androidx/bluetooth/BluetoothAddress.kt
index f711766..a8f4a99 100644
--- a/bluetooth/bluetooth/src/main/java/androidx/bluetooth/BluetoothAddress.kt
+++ b/bluetooth/bluetooth/src/main/java/androidx/bluetooth/BluetoothAddress.kt
@@ -28,6 +28,7 @@
  * @property addressType a valid address type
  */
 class BluetoothAddress(val address: String, @AddressType val addressType: Int) {
+
     companion object {
         /** Address type is public and registered with the IEEE. */
         const val ADDRESS_TYPE_PUBLIC: Int = 0
diff --git a/bluetooth/bluetooth/src/main/java/androidx/bluetooth/BluetoothLe.kt b/bluetooth/bluetooth/src/main/java/androidx/bluetooth/BluetoothLe.kt
index de5ed9a..ff5b70d 100644
--- a/bluetooth/bluetooth/src/main/java/androidx/bluetooth/BluetoothLe.kt
+++ b/bluetooth/bluetooth/src/main/java/androidx/bluetooth/BluetoothLe.kt
@@ -29,7 +29,6 @@
 import androidx.annotation.RequiresPermission
 import androidx.annotation.RestrictTo
 import androidx.annotation.VisibleForTesting
-import java.util.UUID
 import kotlin.coroutines.coroutineContext
 import kotlinx.coroutines.CancellationException
 import kotlinx.coroutines.CompletableDeferred
@@ -38,7 +37,6 @@
 import kotlinx.coroutines.channels.awaitClose
 import kotlinx.coroutines.delay
 import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.callbackFlow
 import kotlinx.coroutines.job
 
@@ -203,66 +201,6 @@
     }
 
     /**
-     * Scope for operations as a GATT client role.
-     *
-     * @see BluetoothLe.connectGatt
-     */
-    interface GattClientScope {
-
-        /**
-         * A flow of GATT services discovered from the remote device.
-         *
-         * If the services of the remote device has changed, the new services will be
-         * discovered and emitted automatically.
-         */
-        val servicesFlow: StateFlow<List<GattService>>
-
-        /**
-         * GATT services recently discovered from the remote device.
-         *
-         * Note that this can be changed, subscribe to [servicesFlow] to get notified
-         * of services changes.
-         */
-        val services: List<GattService> get() = servicesFlow.value
-
-        /**
-         * Gets the service of the remote device by UUID.
-         *
-         * If multiple instances of the same service exist, the first instance of the services
-         * is returned.
-         */
-        fun getService(uuid: UUID): GattService?
-
-        /**
-         * Reads the characteristic value from the server.
-         *
-         * @param characteristic a remote [GattCharacteristic] to read
-         * @return the value of the characteristic
-         */
-        suspend fun readCharacteristic(characteristic: GattCharacteristic): Result<ByteArray>
-
-        /**
-         * Writes the characteristic value to the server.
-         *
-         * @param characteristic a remote [GattCharacteristic] to write
-         * @param value a value to be written.
-         * @throws IllegalArgumentException if the [characteristic] doesn't have the write
-         *     property or the length of the [value] is greater than the maximum
-         *     attribute length (512)
-         * @return the result of the write operation
-         */
-        suspend fun writeCharacteristic(
-            characteristic: GattCharacteristic,
-            value: ByteArray
-        ): Result<Unit>
-
-        /**
-         * Returns a _cold_ [Flow] that contains the indicated value of the given characteristic.
-         */
-        fun subscribeToCharacteristic(characteristic: GattCharacteristic): Flow<ByteArray>
-    }
-
-    /**
      * Connects to the GATT server on the remote Bluetooth device and
      * invokes the given [block] after the connection is made.
      *
@@ -285,108 +223,6 @@
     }
 
     /**
-     * A scope for handling connect requests from remote devices.
-     *
-     * @property connectRequests connect requests from remote devices.
-     *
-     * @see BluetoothLe#openGattServer
-     */
-    interface GattServerConnectScope {
-        /**
-         * A _hot_ flow of [GattServerConnectRequest].
-         */
-        val connectRequests: Flow<GattServerConnectRequest>
-
-        /**
-         * Updates the services of the opened GATT server.
-         *
-         * @param services the new services that will be notified to the clients.
-         */
-        fun updateServices(services: List<GattService>)
-    }
-
-    /**
-     * A scope for operations as a GATT server role.
-     *
-     * A scope is created for each remote device.
-     *
-     * Collect [requests] to respond with requests from the client.
-     *
-     * @see GattServerConnectRequest#accept()
-     */
-    interface GattServerSessionScope {
-        /**
-         * A client device connected to the server.
-         */
-        val device: BluetoothDevice
-
-        /**
-         * A _hot_ [Flow] of incoming requests from the client.
-         *
-         * A request is either [GattServerRequest.ReadCharacteristic] or
-         * [GattServerRequest.WriteCharacteristics]
-         */
-        val requests: Flow<GattServerRequest>
-
-        /**
-         * A [StateFlow] of the set of characteristics that the client has requested to be
-         * notified of.
-         *
-         * The set will be updated whenever the client subscribes to or unsubscribes
-         * a characteristic.
-         *
-         * @see [GattServerSessionScope.notify]
-         */
-        val subscribedCharacteristics: StateFlow<Set<GattCharacteristic>>
-
-        /**
-         * Notifies a client of a characteristic value change.
-         *
-         * @param characteristic the updated characteristic
-         * @param value the new value of the characteristic
-         *
-         * @throws CancellationException if it failed to notify
-         * @throws IllegalArgumentException if the length of the [value] is greater than
-         * the maximum attribute length (512)
-         */
-        suspend fun notify(characteristic: GattCharacteristic, value: ByteArray)
-    }
-
-    /**
-     * Represents a connect request from a remote device.
-     *
-     * @property device the remote device connecting to the server
-     */
-    class GattServerConnectRequest internal constructor(
-        private val session: GattServer.Session,
-    ) {
-        val device: BluetoothDevice
-            get() = session.device
-
-        /**
-         * Accepts the connect request and handles incoming requests after that.
-         *
-         * Requests from the client before calling this should be saved.
-         *
-         * @param block a block of code that is invoked after the connection is made.
-         *
-         * @see GattServerSessionScope
-         */
-        suspend fun accept(block: suspend GattServerSessionScope.() -> Unit) {
-            return session.acceptConnection(block)
-        }
-
-        /**
-         * Rejects the connect request.
-         *
-         * All the requests from the client will be rejected.
-         */
-        fun reject() {
-            return session.rejectConnection()
-        }
-    }
-
-    /**
      * Opens a GATT server.
      *
      * Only one server at a time can be opened.
diff --git a/bluetooth/bluetooth/src/main/java/androidx/bluetooth/GattClient.kt b/bluetooth/bluetooth/src/main/java/androidx/bluetooth/GattClient.kt
index 1dd68d6..42c963a 100644
--- a/bluetooth/bluetooth/src/main/java/androidx/bluetooth/GattClient.kt
+++ b/bluetooth/bluetooth/src/main/java/androidx/bluetooth/GattClient.kt
@@ -62,6 +62,20 @@
  */
 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
 class GattClient(private val context: Context) {
+
+    @VisibleForTesting
+    @RestrictTo(RestrictTo.Scope.LIBRARY)
+    companion object {
+        private const val TAG = "GattClient"
+
+        /**
+         * The maximum ATT size + header(3)
+         */
+        private const val GATT_MAX_MTU = MAX_ATTR_LENGTH + 3
+
+        private const val CONNECT_TIMEOUT_MS = 30_000L
+    }
+
     interface FrameworkAdapter {
         var fwkBluetoothGatt: FwkBluetoothGatt?
         fun connectGatt(
@@ -87,19 +101,6 @@
         fun closeGatt()
     }
 
-    @VisibleForTesting
-    @RestrictTo(RestrictTo.Scope.LIBRARY)
-    companion object {
-        private const val TAG = "GattClient"
-
-        /**
-         * The maximum ATT size + header(3)
-         */
-        private const val GATT_MAX_MTU = MAX_ATTR_LENGTH + 3
-
-        private const val CONNECT_TIMEOUT_MS = 30_000L
-    }
-
     @SuppressLint("ObsoleteSdkInt")
     @VisibleForTesting
     @RestrictTo(RestrictTo.Scope.LIBRARY)
@@ -140,7 +141,7 @@
     @SuppressLint("MissingPermission")
     suspend fun <R> connect(
         device: BluetoothDevice,
-        block: suspend BluetoothLe.GattClientScope.() -> R
+        block: suspend GattClientScope.() -> R
     ): R = coroutineScope {
         val connectResult = CompletableDeferred<Unit>(parent = coroutineContext.job)
         val callbackResultsFlow =
@@ -251,7 +252,7 @@
         withTimeout(CONNECT_TIMEOUT_MS) {
             connectResult.await()
         }
-        val gattScope = object : BluetoothLe.GattClientScope {
+        val gattClientScope = object : GattClientScope {
             val taskMutex = Mutex()
             suspend fun <R> runTask(block: suspend () -> R): R {
                 taskMutex.withLock {
@@ -365,7 +366,6 @@
                         fwkAdapter.setCharacteristicNotification(
                             characteristic.fwkCharacteristic, /*enable=*/false
                         )
-
                         fwkAdapter.writeDescriptor(
                             cccd,
                             FwkBluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE
@@ -398,7 +398,7 @@
         coroutineContext.job.invokeOnCompletion {
             fwkAdapter.closeGatt()
         }
-        gattScope.block()
+        gattClientScope.block()
     }
 
     private suspend inline fun <reified R : CallbackResult> takeMatchingResult(
diff --git a/bluetooth/bluetooth/src/main/java/androidx/bluetooth/GattClientScope.kt b/bluetooth/bluetooth/src/main/java/androidx/bluetooth/GattClientScope.kt
new file mode 100644
index 0000000..60b59b3
--- /dev/null
+++ b/bluetooth/bluetooth/src/main/java/androidx/bluetooth/GattClientScope.kt
@@ -0,0 +1,81 @@
+/*
+ * Copyright 2023 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.bluetooth
+
+import java.util.UUID
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.StateFlow
+
+/**
+ * Scope for operations as a GATT client role.
+ *
+ * @see BluetoothLe.connectGatt
+ */
+interface GattClientScope {
+
+    /**
+     * A flow of GATT services discovered from the remote device.
+     *
+     * If the services of the remote device has changed, the new services will be
+     * discovered and emitted automatically.
+     */
+    val servicesFlow: StateFlow<List<GattService>>
+
+    /**
+     * GATT services recently discovered from the remote device.
+     *
+     * Note that this can be changed, subscribe to [servicesFlow] to get notified
+     * of services changes.
+     */
+    val services: List<GattService> get() = servicesFlow.value
+
+    /**
+     * Gets the service of the remote device by UUID.
+     *
+     * If multiple instances of the same service exist, the first instance of the services
+     * is returned.
+     */
+    fun getService(uuid: UUID): GattService?
+
+    /**
+     * Reads the characteristic value from the server.
+     *
+     * @param characteristic a remote [GattCharacteristic] to read
+     * @return the value of the characteristic
+     */
+    suspend fun readCharacteristic(characteristic: GattCharacteristic): Result<ByteArray>
+
+    /**
+     * Writes the characteristic value to the server.
+     *
+     * @param characteristic a remote [GattCharacteristic] to write
+     * @param value a value to be written.
+     * @throws IllegalArgumentException if the [characteristic] doesn't have the write
+     *     property or the length of the [value] is greater than the maximum
+     *     attribute length (512)
+     * @return the result of the write operation
+     */
+    suspend fun writeCharacteristic(
+        characteristic: GattCharacteristic,
+        value: ByteArray
+    ): Result<Unit>
+
+    /**
+     * Returns a _cold_ [Flow] that contains the indicated value of the given characteristic.
+     */
+    fun subscribeToCharacteristic(characteristic: GattCharacteristic): Flow<ByteArray>
+}
diff --git a/bluetooth/bluetooth/src/main/java/androidx/bluetooth/GattServer.kt b/bluetooth/bluetooth/src/main/java/androidx/bluetooth/GattServer.kt
index ade55ef..eba9769 100644
--- a/bluetooth/bluetooth/src/main/java/androidx/bluetooth/GattServer.kt
+++ b/bluetooth/bluetooth/src/main/java/androidx/bluetooth/GattServer.kt
@@ -20,9 +20,6 @@
 import android.annotation.SuppressLint
 import android.bluetooth.BluetoothDevice as FwkBluetoothDevice
 import android.bluetooth.BluetoothGatt as FwkBluetoothGatt
-import android.bluetooth.BluetoothGatt.GATT_INVALID_ATTRIBUTE_LENGTH
-import android.bluetooth.BluetoothGatt.GATT_SUCCESS
-import android.bluetooth.BluetoothGatt.GATT_WRITE_NOT_PERMITTED
 import android.bluetooth.BluetoothGattCharacteristic as FwkBluetoothGattCharacteristic
 import android.bluetooth.BluetoothGattDescriptor as FwkBluetoothGattDescriptor
 import android.bluetooth.BluetoothGattServer as FwkBluetoothGattServer
@@ -41,10 +38,10 @@
 import androidx.bluetooth.GattCharacteristic.Companion.PROPERTY_INDICATE
 import androidx.bluetooth.GattCharacteristic.Companion.PROPERTY_NOTIFY
 import androidx.bluetooth.GattCommon.UUID_CCCD
-import java.util.concurrent.CancellationException
 import java.util.concurrent.atomic.AtomicBoolean
 import java.util.concurrent.atomic.AtomicInteger
 import kotlin.experimental.and
+import kotlinx.coroutines.CancellationException
 import kotlinx.coroutines.CompletableDeferred
 import kotlinx.coroutines.channels.Channel
 import kotlinx.coroutines.channels.awaitClose
@@ -62,6 +59,11 @@
  */
 @RestrictTo(RestrictTo.Scope.LIBRARY)
 class GattServer(private val context: Context) {
+
+    private companion object {
+        private const val TAG = "GattServer"
+    }
+
     interface FrameworkAdapter {
         var fwkGattServer: FwkBluetoothGattServer?
         fun openGattServer(context: Context, fwkCallback: FwkBluetoothGattServerCallback)
@@ -92,7 +94,7 @@
 
         val device: BluetoothDevice
         var pendingWriteParts: MutableList<GattServerRequest.WriteCharacteristics.Part>
-        suspend fun acceptConnection(block: suspend BluetoothLe.GattServerSessionScope.() -> Unit)
+        suspend fun acceptConnection(block: suspend GattServerSessionScope.() -> Unit)
         fun rejectConnection()
 
         fun sendResponse(requestId: Int, status: Int, offset: Int, value: ByteArray?)
@@ -100,10 +102,6 @@
         fun writeCccd(requestId: Int, characteristic: GattCharacteristic, value: ByteArray?)
     }
 
-    private companion object {
-        private const val TAG = "GattServer"
-    }
-
     @SuppressLint("ObsoleteSdkInt")
     @VisibleForTesting
     @RestrictTo(RestrictTo.Scope.LIBRARY)
@@ -114,13 +112,13 @@
 
     suspend fun <R> open(
         services: List<GattService>,
-        block: suspend BluetoothLe.GattServerConnectScope.() -> R
+        block: suspend GattServerConnectScope.() -> R
     ): R {
         return createServerScope(services).block()
     }
 
-    private fun createServerScope(services: List<GattService>): BluetoothLe.GattServerConnectScope {
-        return object : BluetoothLe.GattServerConnectScope {
+    private fun createServerScope(services: List<GattService>): GattServerConnectScope {
+        return object : GattServerConnectScope {
             private val attributeMap = AttributeMap()
 
             // Should be accessed only from the callback thread
@@ -139,7 +137,7 @@
                         when (newState) {
                             FwkBluetoothProfile.STATE_CONNECTED -> {
                                 trySend(
-                                    BluetoothLe.GattServerConnectRequest(
+                                    GattServerConnectRequest(
                                         addSession(fwkDevice)
                                     )
                                 )
@@ -213,7 +211,7 @@
                         } ?: run {
                             fwkAdapter.sendResponse(
                                 fwkDevice, requestId,
-                                GATT_WRITE_NOT_PERMITTED, offset, /*value=*/null
+                                FwkBluetoothGatt.GATT_WRITE_NOT_PERMITTED, offset, /*value=*/null
                             )
                         }
                     }
@@ -283,7 +281,7 @@
                         fwkDevice: FwkBluetoothDevice,
                         status: Int
                     ) {
-                        notifyJob?.complete(status == GATT_SUCCESS)
+                        notifyJob?.complete(status == FwkBluetoothGatt.GATT_SUCCESS)
                         notifyJob = null
                     }
                 }
@@ -329,7 +327,7 @@
                     mutableListOf<GattServerRequest.WriteCharacteristics.Part>()
 
                 override suspend fun acceptConnection(
-                    block: suspend BluetoothLe.GattServerSessionScope.() -> Unit
+                    block: suspend GattServerSessionScope.() -> Unit
                 ) {
                     if (!state.compareAndSet(
                             GattServer.Session.STATE_CONNECTING,
@@ -339,7 +337,7 @@
                         throw IllegalStateException("the request is already handled")
                     }
 
-                    val scope = object : BluetoothLe.GattServerSessionScope {
+                    val scope = object : GattServerSessionScope {
                         override val device: BluetoothDevice
                             get() = [email protected]
                         override val requests = requestChannel.receiveAsFlow()
@@ -414,7 +412,7 @@
                     if (value == null || value.isEmpty()) {
                         fwkAdapter.sendResponse(
                             device.fwkDevice, requestId,
-                            GATT_INVALID_ATTRIBUTE_LENGTH,
+                            FwkBluetoothGatt.GATT_INVALID_ATTRIBUTE_LENGTH,
                             /*offset=*/0, /*value=*/null
                         )
                         return
@@ -427,7 +425,7 @@
                     ) {
                         fwkAdapter.sendResponse(
                             device.fwkDevice, requestId,
-                            GATT_WRITE_NOT_PERMITTED,
+                            FwkBluetoothGatt.GATT_WRITE_NOT_PERMITTED,
                             /*offset=*/0, /*value=*/null
                         )
                         return
diff --git a/bluetooth/bluetooth/src/main/java/androidx/bluetooth/GattServerConnectRequest.kt b/bluetooth/bluetooth/src/main/java/androidx/bluetooth/GattServerConnectRequest.kt
new file mode 100644
index 0000000..66aada1
--- /dev/null
+++ b/bluetooth/bluetooth/src/main/java/androidx/bluetooth/GattServerConnectRequest.kt
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2023 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.bluetooth
+
+/**
+ * Represents a connect request from a remote device.
+ *
+ * @property device the remote device connecting to the server
+ */
+class GattServerConnectRequest internal constructor(
+    private val session: GattServer.Session,
+) {
+
+    val device: BluetoothDevice
+        get() = session.device
+
+    /**
+     * Accepts the connect request and handles incoming requests after that.
+     *
+     * Requests from the client before calling this should be saved.
+     *
+     * @param block a block of code that is invoked after the connection is made.
+     *
+     * @see GattServerSessionScope
+     */
+    suspend fun accept(block: suspend GattServerSessionScope.() -> Unit) {
+        return session.acceptConnection(block)
+    }
+
+    /**
+     * Rejects the connect request.
+     *
+     * All the requests from the client will be rejected.
+     */
+    fun reject() {
+        return session.rejectConnection()
+    }
+}
diff --git a/bluetooth/bluetooth/src/main/java/androidx/bluetooth/GattServerConnectScope.kt b/bluetooth/bluetooth/src/main/java/androidx/bluetooth/GattServerConnectScope.kt
new file mode 100644
index 0000000..60504bc
--- /dev/null
+++ b/bluetooth/bluetooth/src/main/java/androidx/bluetooth/GattServerConnectScope.kt
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2023 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.bluetooth
+
+import kotlinx.coroutines.flow.Flow
+
+/**
+ * A scope for handling connect requests from remote devices.
+ *
+ * @property connectRequests connect requests from remote devices.
+ *
+ * @see BluetoothLe#openGattServer
+ */
+interface GattServerConnectScope {
+
+    /**
+     * A _hot_ flow of [GattServerConnectRequest].
+     */
+    val connectRequests: Flow<GattServerConnectRequest>
+
+    /**
+     * Updates the services of the opened GATT server.
+     *
+     * @param services the new services that will be notified to the clients.
+     */
+    fun updateServices(services: List<GattService>)
+}
diff --git a/bluetooth/bluetooth/src/main/java/androidx/bluetooth/GattServerRequest.kt b/bluetooth/bluetooth/src/main/java/androidx/bluetooth/GattServerRequest.kt
index 9b5ebce..b9e74ae 100644
--- a/bluetooth/bluetooth/src/main/java/androidx/bluetooth/GattServerRequest.kt
+++ b/bluetooth/bluetooth/src/main/java/androidx/bluetooth/GattServerRequest.kt
@@ -24,7 +24,7 @@
 /**
  * Represents a request to be handled as a GATT server role.
  *
- * @see BluetoothLe.GattServerConnectRequest.accept
+ * @see GattServerConnectRequest.accept
  */
 open class GattServerRequest private constructor() {
     private val handled = AtomicBoolean(false)
diff --git a/bluetooth/bluetooth/src/main/java/androidx/bluetooth/GattServerSessionScope.kt b/bluetooth/bluetooth/src/main/java/androidx/bluetooth/GattServerSessionScope.kt
new file mode 100644
index 0000000..4706a35
--- /dev/null
+++ b/bluetooth/bluetooth/src/main/java/androidx/bluetooth/GattServerSessionScope.kt
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2023 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.bluetooth
+
+import kotlinx.coroutines.CancellationException
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.StateFlow
+
+/**
+ * A scope for operations as a GATT server role.
+ *
+ * A scope is created for each remote device.
+ *
+ * Collect [requests] to respond with requests from the client.
+ *
+ * @see GattServerConnectRequest#accept()
+ */
+interface GattServerSessionScope {
+
+    /**
+     * A client device connected to the server.
+     */
+    val device: BluetoothDevice
+
+    /**
+     * A _hot_ [Flow] of incoming requests from the client.
+     *
+     * A request is either [GattServerRequest.ReadCharacteristic] or
+     * [GattServerRequest.WriteCharacteristics]
+     */
+    val requests: Flow<GattServerRequest>
+
+    /**
+     * A [StateFlow] of the set of characteristics that the client has requested to be
+     * notified of.
+     *
+     * The set will be updated whenever the client subscribes to or unsubscribes
+     * a characteristic.
+     *
+     * @see [GattServerSessionScope.notify]
+     */
+    val subscribedCharacteristics: StateFlow<Set<GattCharacteristic>>
+
+    /**
+     * Notifies a client of a characteristic value change.
+     *
+     * @param characteristic the updated characteristic
+     * @param value the new value of the characteristic
+     *
+     * @throws CancellationException if it failed to notify
+     * @throws IllegalArgumentException if the length of the [value] is greater than
+     * the maximum attribute length (512)
+     */
+    suspend fun notify(characteristic: GattCharacteristic, value: ByteArray)
+}
diff --git a/bluetooth/bluetooth/src/main/java/androidx/bluetooth/ScanFilter.kt b/bluetooth/bluetooth/src/main/java/androidx/bluetooth/ScanFilter.kt
index 7a8127d..65175aa 100644
--- a/bluetooth/bluetooth/src/main/java/androidx/bluetooth/ScanFilter.kt
+++ b/bluetooth/bluetooth/src/main/java/androidx/bluetooth/ScanFilter.kt
@@ -74,6 +74,7 @@
      */
     val serviceSolicitationUuidMask: UUID? = null
 ) {
+
     companion object {
         const val MANUFACTURER_FILTER_NONE: Int = -1
     }
diff --git a/bluetooth/integration-tests/testapp/build.gradle b/bluetooth/integration-tests/testapp/build.gradle
index 9856b9f..1fb244b 100644
--- a/bluetooth/integration-tests/testapp/build.gradle
+++ b/bluetooth/integration-tests/testapp/build.gradle
@@ -58,6 +58,7 @@
     implementation("androidx.navigation:navigation-fragment-ktx:2.7.5")
     implementation("androidx.navigation:navigation-ui-ktx:2.7.5")
     implementation("androidx.recyclerview:recyclerview:1.3.2")
+    implementation("androidx.viewpager2:viewpager2:1.0.0")
 
     implementation(libs.constraintLayout)
     implementation(libs.material)
diff --git a/bluetooth/integration-tests/testapp/src/main/java/androidx/bluetooth/integration/testapp/ui/connections/ConnectionsAdapter.kt b/bluetooth/integration-tests/testapp/src/main/java/androidx/bluetooth/integration/testapp/ui/connections/ConnectionsAdapter.kt
new file mode 100644
index 0000000..043c8e3
--- /dev/null
+++ b/bluetooth/integration-tests/testapp/src/main/java/androidx/bluetooth/integration/testapp/ui/connections/ConnectionsAdapter.kt
@@ -0,0 +1,133 @@
+/*
+ * Copyright 2023 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.bluetooth.integration.testapp.ui.connections
+
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.Button
+import android.widget.ProgressBar
+import android.widget.TextView
+import androidx.bluetooth.GattCharacteristic
+import androidx.bluetooth.integration.testapp.R
+import androidx.bluetooth.integration.testapp.data.connection.DeviceConnection
+import androidx.bluetooth.integration.testapp.data.connection.OnCharacteristicActionClick
+import androidx.bluetooth.integration.testapp.data.connection.Status
+import androidx.core.view.isVisible
+import androidx.recyclerview.widget.DividerItemDecoration
+import androidx.recyclerview.widget.LinearLayoutManager
+import androidx.recyclerview.widget.RecyclerView
+
+class ConnectionsAdapter(
+    private val deviceConnections: Set<DeviceConnection>,
+    private val onClickReconnect: (DeviceConnection) -> Unit,
+    private val onClickDisconnect: (DeviceConnection) -> Unit
+) : RecyclerView.Adapter<ConnectionsAdapter.ViewHolder>() {
+
+    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
+        val view = LayoutInflater.from(parent.context)
+            .inflate(R.layout.item_connection, parent, false)
+        return ViewHolder(view)
+    }
+
+    override fun getItemCount(): Int {
+        return deviceConnections.size
+    }
+
+    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
+        holder.bind(deviceConnections.elementAt(position))
+    }
+
+    inner class ViewHolder(
+        itemView: View
+    ) : RecyclerView.ViewHolder(itemView) {
+
+        private val textViewDeviceConnectionStatus: TextView =
+            itemView.findViewById(R.id.text_view_device_connection_status)
+        private val progressIndicatorDeviceConnection: ProgressBar =
+            itemView.findViewById(R.id.progress_indicator_device_connection)
+
+        private val buttonReconnect: Button = itemView.findViewById(R.id.button_reconnect)
+        private val buttonDisconnect: Button = itemView.findViewById(R.id.button_disconnect)
+
+        private val recyclerViewDeviceServices: RecyclerView =
+            itemView.findViewById(R.id.recycler_view_device_services)
+
+        private val onCharacteristicActionClick = object : OnCharacteristicActionClick {
+            override fun onClick(
+                deviceConnection: DeviceConnection,
+                characteristic: GattCharacteristic,
+                action: @OnCharacteristicActionClick.Action Int
+            ) {
+                deviceConnection.onCharacteristicActionClick?.onClick(
+                    deviceConnection,
+                    characteristic,
+                    action
+                )
+            }
+        }
+
+        private var currentDeviceConnection: DeviceConnection? = null
+
+        init {
+            buttonReconnect.setOnClickListener {
+                currentDeviceConnection?.let(onClickReconnect)
+            }
+            buttonDisconnect.setOnClickListener {
+                currentDeviceConnection?.let(onClickDisconnect)
+            }
+        }
+
+        fun bind(deviceConnection: DeviceConnection) {
+            currentDeviceConnection = deviceConnection
+
+            recyclerViewDeviceServices.adapter =
+                DeviceServicesAdapter(deviceConnection, onCharacteristicActionClick)
+            val context = itemView.context
+            recyclerViewDeviceServices.addItemDecoration(
+                DividerItemDecoration(context, LinearLayoutManager.VERTICAL)
+            )
+
+            progressIndicatorDeviceConnection.isVisible = false
+            buttonReconnect.isVisible = false
+            buttonDisconnect.isVisible = false
+
+            when (deviceConnection.status) {
+                Status.DISCONNECTED -> {
+                    textViewDeviceConnectionStatus.text = context.getString(R.string.disconnected)
+                    textViewDeviceConnectionStatus
+                        .setTextColor(context.getColor(R.color.green_500))
+                    buttonReconnect.isVisible = true
+                }
+
+                Status.CONNECTING -> {
+                    progressIndicatorDeviceConnection.isVisible = true
+                    textViewDeviceConnectionStatus.text = context.getString(R.string.connecting)
+                    textViewDeviceConnectionStatus
+                        .setTextColor(context.getColor(R.color.indigo_500))
+                }
+
+                Status.CONNECTED -> {
+                    textViewDeviceConnectionStatus.text = context.getString(R.string.connected)
+                    textViewDeviceConnectionStatus
+                        .setTextColor(context.getColor(R.color.indigo_500))
+                    buttonDisconnect.isVisible = true
+                }
+            }
+        }
+    }
+}
diff --git a/bluetooth/integration-tests/testapp/src/main/java/androidx/bluetooth/integration/testapp/ui/connections/ConnectionsFragment.kt b/bluetooth/integration-tests/testapp/src/main/java/androidx/bluetooth/integration/testapp/ui/connections/ConnectionsFragment.kt
index dfef4b3..c24ebbd 100644
--- a/bluetooth/integration-tests/testapp/src/main/java/androidx/bluetooth/integration/testapp/ui/connections/ConnectionsFragment.kt
+++ b/bluetooth/integration-tests/testapp/src/main/java/androidx/bluetooth/integration/testapp/ui/connections/ConnectionsFragment.kt
@@ -1,42 +1,47 @@
+/*
+ * Copyright 2023 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.bluetooth.integration.testapp.ui.connections
 
 import android.annotation.SuppressLint
+import android.app.AlertDialog
 import android.os.Bundle
-import android.util.Log
 import android.view.LayoutInflater
 import android.view.View
 import android.view.ViewGroup
 import android.widget.Button
 import android.widget.EditText
 import android.widget.TextView
-import androidx.appcompat.app.AlertDialog
 import androidx.bluetooth.BluetoothDevice
-import androidx.bluetooth.BluetoothLe
 import androidx.bluetooth.GattCharacteristic
+import androidx.bluetooth.GattClientScope
 import androidx.bluetooth.integration.testapp.R
 import androidx.bluetooth.integration.testapp.data.connection.DeviceConnection
-import androidx.bluetooth.integration.testapp.data.connection.OnCharacteristicActionClick
-import androidx.bluetooth.integration.testapp.data.connection.Status
 import androidx.bluetooth.integration.testapp.databinding.FragmentConnectionsBinding
-import androidx.bluetooth.integration.testapp.ui.common.getColor
 import androidx.bluetooth.integration.testapp.ui.common.toast
 import androidx.bluetooth.integration.testapp.ui.main.MainViewModel
 import androidx.core.view.isVisible
 import androidx.fragment.app.Fragment
 import androidx.fragment.app.activityViewModels
 import androidx.fragment.app.viewModels
-import androidx.recyclerview.widget.DividerItemDecoration
-import androidx.recyclerview.widget.LinearLayoutManager
-import com.google.android.material.tabs.TabLayout
+import androidx.lifecycle.flowWithLifecycle
+import androidx.lifecycle.lifecycleScope
 import com.google.android.material.tabs.TabLayout.Tab
+import com.google.android.material.tabs.TabLayoutMediator
 import dagger.hilt.android.AndroidEntryPoint
-import javax.inject.Inject
-import kotlin.coroutines.cancellation.CancellationException
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.Job
-import kotlinx.coroutines.awaitCancellation
-import kotlinx.coroutines.cancel
 import kotlinx.coroutines.launch
 
 @AndroidEntryPoint
@@ -48,39 +53,6 @@
         internal const val MANUAL_DISCONNECT = "MANUAL_DISCONNECT"
     }
 
-    @Inject
-    lateinit var bluetoothLe: BluetoothLe
-
-    private var deviceServicesAdapter: DeviceServicesAdapter? = null
-
-    private val connectScope = CoroutineScope(Dispatchers.Main + Job())
-
-    private val onTabSelectedListener = object : TabLayout.OnTabSelectedListener {
-        override fun onTabSelected(tab: Tab) {
-            updateDeviceUI(viewModel.deviceConnection(tab.position))
-        }
-
-        override fun onTabUnselected(tab: Tab) {
-        }
-
-        override fun onTabReselected(tab: Tab) {
-        }
-    }
-
-    private val onCharacteristicActionClick = object : OnCharacteristicActionClick {
-        override fun onClick(
-            deviceConnection: DeviceConnection,
-            characteristic: GattCharacteristic,
-            action: @OnCharacteristicActionClick.Action Int
-        ) {
-            deviceConnection.onCharacteristicActionClick?.onClick(
-                deviceConnection,
-                characteristic,
-                action
-            )
-        }
-    }
-
     private val viewModel by viewModels<ConnectionsViewModel>()
 
     private val mainViewModel by activityViewModels<MainViewModel>()
@@ -100,176 +72,92 @@
     override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
         super.onViewCreated(view, savedInstanceState)
 
-        binding.tabLayout.addOnTabSelectedListener(onTabSelectedListener)
-
-        deviceServicesAdapter = DeviceServicesAdapter(null, onCharacteristicActionClick)
-        binding.recyclerViewDeviceServices.adapter = deviceServicesAdapter
-        binding.recyclerViewDeviceServices.addItemDecoration(
-            DividerItemDecoration(context, LinearLayoutManager.VERTICAL)
+        binding.viewPager.adapter = ConnectionsAdapter(
+            viewModel.deviceConnections,
+            ::onClickReconnect,
+            ::onClickDisconnect
         )
 
-        binding.buttonReconnect.setOnClickListener {
-            connectTo(viewModel.deviceConnection(binding.tabLayout.selectedTabPosition))
+        TabLayoutMediator(binding.tabLayout, binding.viewPager) { tab, position ->
+            setCustomViewTab(tab, viewModel.deviceConnections.elementAt(position))
+        }.attach()
+
+        viewLifecycleOwner.lifecycleScope.launch {
+            viewModel.uiState
+                .flowWithLifecycle(viewLifecycleOwner.lifecycle)
+                .collect(::updateUi)
         }
 
-        binding.buttonDisconnect.setOnClickListener {
-            disconnect(viewModel.deviceConnection(binding.tabLayout.selectedTabPosition))
-        }
-
-        initData()
-    }
-
-    override fun onDestroyView() {
-        super.onDestroyView()
-        connectScope.cancel()
-        _binding = null
-    }
-
-    private fun initData() {
-        viewModel.deviceConnections.map { it.bluetoothDevice }.forEach(::addNewTab)
-
         mainViewModel.selectedBluetoothDevice?.let { selectedBluetoothDevice ->
             onClickConnect(selectedBluetoothDevice)
             mainViewModel.selectedBluetoothDevice = null
         }
     }
 
-    private fun onClickConnect(bluetoothDevice: BluetoothDevice) {
-        Log.d(TAG, "onClickConnect() called with: bluetoothDevice = $bluetoothDevice")
-
-        val index = viewModel.addDeviceConnectionIfNew(bluetoothDevice)
-
-        val deviceTab = if (index == ConnectionsViewModel.NEW_DEVICE) {
-            addNewTab(bluetoothDevice)
-        } else {
-            binding.tabLayout.getTabAt(index)
-        }
-
-        // To prevent TabSelectedListener being triggered when a tab is programmatically selected.
-        binding.tabLayout.removeOnTabSelectedListener(onTabSelectedListener)
-        binding.tabLayout.selectTab(deviceTab)
-        binding.tabLayout.addOnTabSelectedListener(onTabSelectedListener)
-
-        connectTo(viewModel.deviceConnection(binding.tabLayout.selectedTabPosition))
+    override fun onDestroyView() {
+        super.onDestroyView()
+        _binding = null
     }
 
-    @SuppressLint("MissingPermission")
-    private fun addNewTab(bluetoothDevice: BluetoothDevice): Tab {
-        Log.d(TAG, "addNewTab() called with: bluetoothDevice = $bluetoothDevice")
+    private fun setCustomViewTab(tab: Tab, deviceConnection: DeviceConnection) {
+        tab.setCustomView(R.layout.tab_item_device)
 
+        val bluetoothDevice = deviceConnection.bluetoothDevice
         val deviceId = bluetoothDevice.id.toString()
         val deviceName = bluetoothDevice.name
 
-        val newTab = binding.tabLayout.newTab()
-        newTab.setCustomView(R.layout.tab_item_device)
-
-        val customView = newTab.customView
+        val customView = tab.customView
         customView?.findViewById<TextView>(R.id.text_view_device_id)?.text = deviceId
         val textViewName = customView?.findViewById<TextView>(R.id.text_view_name)
         textViewName?.text = deviceName
         textViewName?.isVisible = deviceName.isNullOrEmpty().not()
         customView?.findViewById<Button>(R.id.image_button_remove)?.setOnClickListener {
-            Log.d(TAG, "removeTab() called with: bluetoothDevice = $bluetoothDevice")
-
-            viewModel.remove(bluetoothDevice)
-            binding.tabLayout.removeTab(newTab)
-        }
-
-        binding.tabLayout.addTab(newTab)
-        return newTab
-    }
-
-    @SuppressLint("MissingPermission")
-    private fun connectTo(deviceConnection: DeviceConnection) {
-        Log.d(TAG, "connectTo() called with: deviceConnection = $deviceConnection")
-
-        deviceConnection.job = connectScope.launch {
-            deviceConnection.status = Status.CONNECTING
-            updateDeviceUI(deviceConnection)
-
-            try {
-                Log.d(
-                    TAG, "bluetoothLe.connectGatt() called with: " +
-                        "deviceConnection.bluetoothDevice = ${deviceConnection.bluetoothDevice}"
-                )
-
-                bluetoothLe.connectGatt(deviceConnection.bluetoothDevice) {
-                    Log.d(TAG, "bluetoothLe.connectGatt result: services() = $services")
-
-                    deviceConnection.status = Status.CONNECTED
-                    deviceConnection.services = services
-                    updateDeviceUI(deviceConnection)
-
-                    deviceConnection.onCharacteristicActionClick =
-                        object : OnCharacteristicActionClick {
-                            override fun onClick(
-                                deviceConnection: DeviceConnection,
-                                characteristic: GattCharacteristic,
-                                action: @OnCharacteristicActionClick.Action Int
-                            ) {
-                                Log.d(
-                                    TAG,
-                                    "onClick() called with: " +
-                                        "deviceConnection = $deviceConnection, " +
-                                        "characteristic = $characteristic, " +
-                                        "action = $action"
-                                )
-
-                                when (action) {
-                                    OnCharacteristicActionClick.READ -> readCharacteristic(
-                                        this@connectGatt,
-                                        deviceConnection,
-                                        characteristic
-                                    )
-
-                                    OnCharacteristicActionClick.WRITE -> writeCharacteristic(
-                                        this@connectGatt,
-                                        characteristic
-                                    )
-
-                                    OnCharacteristicActionClick.SUBSCRIBE ->
-                                        subscribeToCharacteristic(
-                                            this@connectGatt,
-                                            characteristic
-                                        )
-                                }
-                            }
-                        }
-
-                    awaitCancellation()
-                }
-            } catch (exception: Exception) {
-                if (exception is CancellationException) {
-                    Log.e(TAG, "connectGatt() CancellationException", exception)
-                } else {
-                    Log.e(TAG, "connectGatt() exception", exception)
-                }
-
-                deviceConnection.status = Status.DISCONNECTED
-                updateDeviceUI(deviceConnection)
-            }
+            viewModel.removeDeviceConnection(deviceConnection)
         }
     }
 
-    private fun readCharacteristic(
-        gattClientScope: BluetoothLe.GattClientScope,
-        deviceConnection: DeviceConnection,
-        characteristic: GattCharacteristic
-    ) {
-        connectScope.launch {
-            Log.d(TAG, "readCharacteristic() called with: characteristic = $characteristic")
+    @SuppressLint("NotifyDataSetChanged")
+    private fun updateUi(connectionsUiState: ConnectionsUiState) {
+        binding.viewPager.adapter?.notifyDataSetChanged()
 
-            val result = gattClientScope.readCharacteristic(characteristic)
-            Log.d(TAG, "readCharacteristic() result: result = $result")
+        connectionsUiState.showDialogForWrite?.let {
+            showDialogForWrite(it)
+            viewModel.writeDialogShown()
+        }
 
-            deviceConnection.storeValueFor(characteristic, result.getOrNull())
-            updateDeviceUI(deviceConnection)
+        connectionsUiState.resultMessage?.let {
+            toast(it).show()
+            viewModel.resultMessageShown()
         }
     }
 
-    private fun writeCharacteristic(
-        gattClientScope: BluetoothLe.GattClientScope,
-        characteristic: GattCharacteristic
+    private fun onClickConnect(bluetoothDevice: BluetoothDevice) {
+        val index = viewModel.addDeviceConnectionIfNew(bluetoothDevice)
+        updateUi(viewModel.uiState.value)
+
+        val deviceTab = if (index == ConnectionsViewModel.NEW_DEVICE) {
+            val tabCount = binding.tabLayout.tabCount
+            binding.tabLayout.getTabAt(tabCount - 1)
+        } else {
+            binding.tabLayout.getTabAt(index)
+        }
+
+        binding.tabLayout.selectTab(deviceTab)
+
+        val selectedTabPosition = binding.tabLayout.selectedTabPosition
+        viewModel.connect(viewModel.deviceConnections.elementAt(selectedTabPosition))
+    }
+
+    private fun onClickReconnect(deviceConnection: DeviceConnection) {
+        viewModel.connect(deviceConnection)
+    }
+
+    private fun onClickDisconnect(deviceConnection: DeviceConnection) {
+        viewModel.disconnect(deviceConnection)
+    }
+
+    private fun showDialogForWrite(
+        gattCharacteristicPair: Pair<GattClientScope, GattCharacteristic>
     ) {
         val view = layoutInflater.inflate(R.layout.dialog_write_characteristic, null)
         val editTextValue = view.findViewById<EditText>(R.id.edit_text_value)
@@ -278,83 +166,15 @@
             .setTitle(getString(R.string.write))
             .setView(view).setPositiveButton(getString(R.string.write)) { _, _ ->
                 val editTextValueString = editTextValue.text.toString()
-                val value = editTextValueString.toByteArray()
 
-                connectScope.launch {
-                    Log.d(
-                        TAG,
-                        "writeCharacteristic() called with: " +
-                            "characteristic = $characteristic, " +
-                            "value = ${value.decodeToString()}"
-                    )
-
-                    val result = gattClientScope.writeCharacteristic(characteristic, value)
-                    Log.d(TAG, "writeCharacteristic() result: result = $result")
-
-                    toast("Called write with: $editTextValueString, result = $result").show()
-                }
+                viewModel.writeCharacteristic(
+                    gattCharacteristicPair.first,
+                    gattCharacteristicPair.second,
+                    editTextValueString
+                )
             }
             .setNegativeButton(getString(R.string.cancel), null)
             .create()
             .show()
     }
-
-    private fun subscribeToCharacteristic(
-        gattClientScope: BluetoothLe.GattClientScope,
-        characteristic: GattCharacteristic
-    ) {
-        connectScope.launch {
-            gattClientScope.subscribeToCharacteristic(characteristic)
-                .collect {
-                    Log.d(
-                        TAG,
-                        "subscribeToCharacteristic() collected: " +
-                            "characteristic = $characteristic, " +
-                            "value.decodeToString() = ${it.decodeToString()}"
-                    )
-                }
-
-            Log.d(TAG, "subscribeToCharacteristic completed")
-        }
-    }
-
-    private fun disconnect(deviceConnection: DeviceConnection) {
-        Log.d(TAG, "disconnect() called with: deviceConnection = $deviceConnection")
-
-        deviceConnection.job?.cancel(MANUAL_DISCONNECT)
-        deviceConnection.job = null
-        deviceConnection.status = Status.DISCONNECTED
-        updateDeviceUI(deviceConnection)
-    }
-
-    @SuppressLint("NotifyDataSetChanged")
-    private fun updateDeviceUI(deviceConnection: DeviceConnection) {
-        if (_binding == null) return
-
-        binding.progressIndicatorDeviceConnection.isVisible = false
-        binding.buttonReconnect.isVisible = false
-        binding.buttonDisconnect.isVisible = false
-
-        when (deviceConnection.status) {
-            Status.DISCONNECTED -> {
-                binding.textViewDeviceConnectionStatus.text = getString(R.string.disconnected)
-                binding.textViewDeviceConnectionStatus.setTextColor(getColor(R.color.green_500))
-                binding.buttonReconnect.isVisible = true
-            }
-
-            Status.CONNECTING -> {
-                binding.progressIndicatorDeviceConnection.isVisible = true
-                binding.textViewDeviceConnectionStatus.text = getString(R.string.connecting)
-                binding.textViewDeviceConnectionStatus.setTextColor(getColor(R.color.indigo_500))
-            }
-
-            Status.CONNECTED -> {
-                binding.textViewDeviceConnectionStatus.text = getString(R.string.connected)
-                binding.textViewDeviceConnectionStatus.setTextColor(getColor(R.color.indigo_500))
-                binding.buttonDisconnect.isVisible = true
-            }
-        }
-        deviceServicesAdapter?.deviceConnection = deviceConnection
-        deviceServicesAdapter?.notifyDataSetChanged()
-    }
 }
diff --git a/bluetooth/integration-tests/testapp/src/main/java/androidx/bluetooth/integration/testapp/ui/connections/ConnectionsUiState.kt b/bluetooth/integration-tests/testapp/src/main/java/androidx/bluetooth/integration/testapp/ui/connections/ConnectionsUiState.kt
new file mode 100644
index 0000000..06199ef
--- /dev/null
+++ b/bluetooth/integration-tests/testapp/src/main/java/androidx/bluetooth/integration/testapp/ui/connections/ConnectionsUiState.kt
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2023 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.bluetooth.integration.testapp.ui.connections
+
+import androidx.bluetooth.GattCharacteristic
+import androidx.bluetooth.GattClientScope
+
+data class ConnectionsUiState(
+    val lastConnectionUpdate: Long = System.currentTimeMillis(),
+    val showDialogForWrite: Pair<GattClientScope, GattCharacteristic>? = null,
+    val resultMessage: String? = null
+)
diff --git a/bluetooth/integration-tests/testapp/src/main/java/androidx/bluetooth/integration/testapp/ui/connections/ConnectionsViewModel.kt b/bluetooth/integration-tests/testapp/src/main/java/androidx/bluetooth/integration/testapp/ui/connections/ConnectionsViewModel.kt
index 65afd95..0305623 100644
--- a/bluetooth/integration-tests/testapp/src/main/java/androidx/bluetooth/integration/testapp/ui/connections/ConnectionsViewModel.kt
+++ b/bluetooth/integration-tests/testapp/src/main/java/androidx/bluetooth/integration/testapp/ui/connections/ConnectionsViewModel.kt
@@ -16,12 +16,36 @@
 
 package androidx.bluetooth.integration.testapp.ui.connections
 
+import android.annotation.SuppressLint
+import android.util.Log
 import androidx.bluetooth.BluetoothDevice
+import androidx.bluetooth.BluetoothLe
+import androidx.bluetooth.GattCharacteristic
+import androidx.bluetooth.GattClientScope
 import androidx.bluetooth.integration.testapp.data.connection.DeviceConnection
+import androidx.bluetooth.integration.testapp.data.connection.OnCharacteristicActionClick
+import androidx.bluetooth.integration.testapp.data.connection.Status
 import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import dagger.hilt.android.lifecycle.HiltViewModel
+import javax.inject.Inject
+import kotlin.coroutines.cancellation.CancellationException
+import kotlinx.coroutines.awaitCancellation
 import kotlinx.coroutines.cancel
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onCompletion
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.flow.onStart
+import kotlinx.coroutines.flow.update
+import kotlinx.coroutines.launch
 
-class ConnectionsViewModel : ViewModel() {
+@HiltViewModel
+class ConnectionsViewModel @Inject constructor(
+    private val bluetoothLe: BluetoothLe
+) : ViewModel() {
 
     internal companion object {
         private const val TAG = "ConnectionsViewModel"
@@ -32,6 +56,9 @@
     internal val deviceConnections: Set<DeviceConnection> get() = _deviceConnections
     private val _deviceConnections = mutableSetOf<DeviceConnection>()
 
+    private val _uiState = MutableStateFlow(ConnectionsUiState())
+    val uiState: StateFlow<ConnectionsUiState> = _uiState.asStateFlow()
+
     override fun onCleared() {
         super.onCleared()
 
@@ -39,26 +66,177 @@
     }
 
     fun addDeviceConnectionIfNew(bluetoothDevice: BluetoothDevice): Int {
-        val deviceConnection = DeviceConnection(bluetoothDevice)
-
         val indexOf = _deviceConnections.map { it.bluetoothDevice }.indexOf(bluetoothDevice)
         if (indexOf != -1) {
             return indexOf
         }
 
-        _deviceConnections.add(deviceConnection)
+        _deviceConnections.add(DeviceConnection(bluetoothDevice))
         return NEW_DEVICE
     }
 
-    fun remove(bluetoothDevice: BluetoothDevice) {
-        val deviceConnection = _deviceConnections.find { it.bluetoothDevice == bluetoothDevice }
-        deviceConnection?.job?.cancel(ConnectionsFragment.MANUAL_DISCONNECT)
-        deviceConnection?.job = null
-
+    fun removeDeviceConnection(deviceConnection: DeviceConnection) {
+        deviceConnection.job?.cancel(ConnectionsFragment.MANUAL_DISCONNECT)
+        deviceConnection.job = null
         _deviceConnections.remove(deviceConnection)
+
+        updateUi()
     }
 
-    fun deviceConnection(position: Int): DeviceConnection {
-        return deviceConnections.elementAt(position)
+    @SuppressLint("MissingPermission")
+    fun connect(deviceConnection: DeviceConnection) {
+        Log.d(TAG, "connect() called with: deviceConnection = $deviceConnection")
+
+        deviceConnection.job = viewModelScope.launch {
+            deviceConnection.status = Status.CONNECTING
+            updateUi()
+
+            try {
+                Log.d(
+                    TAG, "bluetoothLe.connectGatt() called with: " +
+                        "deviceConnection.bluetoothDevice = ${deviceConnection.bluetoothDevice}"
+                )
+
+                bluetoothLe.connectGatt(deviceConnection.bluetoothDevice) {
+                    Log.d(TAG, "bluetoothLe.connectGatt result: services() = $services")
+
+                    deviceConnection.status = Status.CONNECTED
+                    deviceConnection.services = services
+                    updateUi()
+
+                    deviceConnection.onCharacteristicActionClick =
+                        object : OnCharacteristicActionClick {
+                            override fun onClick(
+                                deviceConnection: DeviceConnection,
+                                characteristic: GattCharacteristic,
+                                action: @OnCharacteristicActionClick.Action Int
+                            ) {
+                                Log.d(
+                                    TAG,
+                                    "onClick() called with: " +
+                                        "deviceConnection = $deviceConnection, " +
+                                        "characteristic = $characteristic, " +
+                                        "action = $action"
+                                )
+
+                                when (action) {
+                                    OnCharacteristicActionClick.READ -> readCharacteristic(
+                                        this@connectGatt,
+                                        deviceConnection,
+                                        characteristic
+                                    )
+
+                                    OnCharacteristicActionClick.WRITE -> _uiState.update {
+                                        it.copy(
+                                            showDialogForWrite = Pair(
+                                                this@connectGatt,
+                                                characteristic
+                                            )
+                                        )
+                                    }
+
+                                    OnCharacteristicActionClick.SUBSCRIBE ->
+                                        subscribeToCharacteristic(
+                                            this@connectGatt,
+                                            characteristic
+                                        )
+                                }
+                            }
+                        }
+
+                    awaitCancellation()
+                }
+            } catch (exception: Exception) {
+                if (exception is CancellationException) {
+                    Log.e(TAG, "connectGatt() CancellationException", exception)
+                } else {
+                    Log.e(TAG, "connectGatt() exception", exception)
+                }
+
+                deviceConnection.status = Status.DISCONNECTED
+                updateUi()
+            }
+        }
+    }
+
+    fun disconnect(deviceConnection: DeviceConnection) {
+        deviceConnection.job?.cancel(ConnectionsFragment.MANUAL_DISCONNECT)
+        deviceConnection.job = null
+
+        updateUi()
+    }
+
+    fun writeDialogShown() {
+        _uiState.update {
+            it.copy(showDialogForWrite = null)
+        }
+    }
+
+    fun resultMessageShown() {
+        _uiState.update {
+            it.copy(resultMessage = null)
+        }
+    }
+
+    private fun updateUi() {
+        _uiState.update {
+            it.copy(lastConnectionUpdate = System.currentTimeMillis())
+        }
+    }
+
+    private fun readCharacteristic(
+        gattClientScope: GattClientScope,
+        deviceConnection: DeviceConnection,
+        characteristic: GattCharacteristic
+    ) {
+        viewModelScope.launch {
+            Log.d(TAG, "readCharacteristic() called with: characteristic = $characteristic")
+
+            val result = gattClientScope.readCharacteristic(characteristic)
+            Log.d(TAG, "readCharacteristic() result: result = $result")
+
+            deviceConnection.storeValueFor(characteristic, result.getOrNull())
+            updateUi()
+        }
+    }
+
+    fun writeCharacteristic(
+        gattClientScope: GattClientScope,
+        characteristic: GattCharacteristic,
+        valueString: String
+    ) {
+        val value = valueString.toByteArray()
+
+        viewModelScope.launch {
+            Log.d(
+                TAG,
+                "writeCharacteristic() called with: " +
+                    "characteristic = $characteristic, " +
+                    "value = ${value.decodeToString()}"
+            )
+
+            val result = gattClientScope.writeCharacteristic(characteristic, value)
+            Log.d(TAG, "writeCharacteristic() result: result = $result")
+
+            _uiState.update {
+                it.copy(resultMessage = "Called write with: $valueString, result = $result")
+            }
+        }
+    }
+
+    private fun subscribeToCharacteristic(
+        gattClientScope: GattClientScope,
+        characteristic: GattCharacteristic
+    ) {
+        gattClientScope.subscribeToCharacteristic(characteristic)
+            .onStart {
+                Log.d(TAG, "subscribeToCharacteristic() onStart")
+            }
+            .onEach {
+                Log.d(TAG, "subscribeToCharacteristic() onEach: ${it.decodeToString()}")
+            }.onCompletion {
+                Log.e(TAG, "subscribeToCharacteristic onCompletion", it)
+            }
+            .launchIn(viewModelScope)
     }
 }
diff --git a/bluetooth/integration-tests/testapp/src/main/java/androidx/bluetooth/integration/testapp/ui/connections/DeviceServicesAdapter.kt b/bluetooth/integration-tests/testapp/src/main/java/androidx/bluetooth/integration/testapp/ui/connections/DeviceServicesAdapter.kt
index ecfa597..2b9e811 100644
--- a/bluetooth/integration-tests/testapp/src/main/java/androidx/bluetooth/integration/testapp/ui/connections/DeviceServicesAdapter.kt
+++ b/bluetooth/integration-tests/testapp/src/main/java/androidx/bluetooth/integration/testapp/ui/connections/DeviceServicesAdapter.kt
@@ -27,7 +27,7 @@
 import androidx.recyclerview.widget.RecyclerView
 
 class DeviceServicesAdapter(
-    var deviceConnection: DeviceConnection? = null,
+    private val deviceConnection: DeviceConnection,
     private val onCharacteristicActionClick: OnCharacteristicActionClick,
 ) : RecyclerView.Adapter<DeviceServicesAdapter.ViewHolder>() {
 
@@ -38,14 +38,12 @@
     }
 
     override fun getItemCount(): Int {
-        return deviceConnection?.services.orEmpty().size
+        return deviceConnection.services.size
     }
 
     override fun onBindViewHolder(holder: ViewHolder, position: Int) {
-        deviceConnection?.let {
-            val service = it.services[position]
-            holder.bind(it, service)
-        }
+        val service = deviceConnection.services[position]
+        holder.bind(deviceConnection, service)
     }
 
     inner class ViewHolder(
diff --git a/bluetooth/integration-tests/testapp/src/main/java/androidx/bluetooth/integration/testapp/ui/gatt_server/GattServerViewModel.kt b/bluetooth/integration-tests/testapp/src/main/java/androidx/bluetooth/integration/testapp/ui/gatt_server/GattServerViewModel.kt
index 6d06ac8..44918ba 100644
--- a/bluetooth/integration-tests/testapp/src/main/java/androidx/bluetooth/integration/testapp/ui/gatt_server/GattServerViewModel.kt
+++ b/bluetooth/integration-tests/testapp/src/main/java/androidx/bluetooth/integration/testapp/ui/gatt_server/GattServerViewModel.kt
@@ -100,7 +100,6 @@
                                     "requests collected: gattServerRequest = $gattServerRequest"
                                 )
 
-                                // TODO(b/269390098): Handle requests correctly
                                 when (gattServerRequest) {
                                     is GattServerRequest.ReadCharacteristic -> {
                                         val characteristic = gattServerRequest.characteristic
diff --git a/bluetooth/integration-tests/testapp/src/main/res/layout/fragment_connections.xml b/bluetooth/integration-tests/testapp/src/main/res/layout/fragment_connections.xml
index 1427a28..ea7945f 100644
--- a/bluetooth/integration-tests/testapp/src/main/res/layout/fragment_connections.xml
+++ b/bluetooth/integration-tests/testapp/src/main/res/layout/fragment_connections.xml
@@ -1,5 +1,4 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
+<?xml version="1.0" encoding="utf-8"?><!--
   Copyright 2023 The Android Open Source Project
 
   Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,7 +15,6 @@
   -->
 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:app="http://schemas.android.com/apk/res-auto"
-    xmlns:tools="http://schemas.android.com/tools"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
     android:orientation="vertical">
@@ -25,65 +23,13 @@
         android:id="@+id/tab_layout"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
-        app:layout_constraintTop_toTopOf="parent"
         app:tabGravity="start"
         app:tabMode="scrollable" />
 
-    <LinearLayout
+    <androidx.viewpager2.widget.ViewPager2
+        android:id="@+id/view_pager"
         android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:gravity="center_vertical">
-
-        <TextView
-            android:id="@+id/text_view_device_connection_status"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:padding="8dp"
-            android:text="@string/disconnected"
-            android:textAllCaps="true"
-            android:textColor="@color/green_500"
-            android:textSize="16sp" />
-
-        <com.google.android.material.progressindicator.CircularProgressIndicator
-            android:id="@+id/progress_indicator_device_connection"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:indeterminate="true"
-            android:visibility="gone"
-            app:indicatorSize="24dp" />
-
-        <View
-            android:layout_width="0dp"
-            android:layout_height="0dp"
-            android:layout_weight="1" />
-
-        <Button
-            android:id="@+id/button_reconnect"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:text="@string/reconnect"
-            android:visibility="gone" />
-
-        <Button
-            android:id="@+id/button_disconnect"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:text="@string/disconnect"
-            android:visibility="gone" />
-
-    </LinearLayout>
-
-    <View
-        android:layout_width="match_parent"
-        android:layout_height="1dp"
-        android:background="?android:attr/dividerVertical" />
-
-    <androidx.recyclerview.widget.RecyclerView
-        android:id="@+id/recycler_view_device_services"
-        android:layout_width="match_parent"
-        android:layout_height="match_parent"
-        app:layoutManager="LinearLayoutManager"
-        tools:itemCount="3"
-        tools:listitem="@layout/item_device_service" />
+        android:layout_height="0dp"
+        android:layout_weight="1" />
 
 </LinearLayout>
diff --git a/bluetooth/integration-tests/testapp/src/main/res/layout/item_connection.xml b/bluetooth/integration-tests/testapp/src/main/res/layout/item_connection.xml
new file mode 100644
index 0000000..487ddd8
--- /dev/null
+++ b/bluetooth/integration-tests/testapp/src/main/res/layout/item_connection.xml
@@ -0,0 +1,81 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  Copyright 2023 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.
+  -->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:id="@+id/linear_layout_device"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical">
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:gravity="center_vertical"
+        android:padding="8dp">
+
+        <TextView
+            android:id="@+id/text_view_device_connection_status"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="@string/disconnected"
+            android:textAllCaps="true"
+            android:textColor="@color/green_500"
+            android:textSize="16sp" />
+
+        <com.google.android.material.progressindicator.CircularProgressIndicator
+            android:id="@+id/progress_indicator_device_connection"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:indeterminate="true"
+            android:visibility="gone"
+            app:indicatorSize="24dp" />
+
+        <View
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            android:layout_weight="1" />
+
+        <Button
+            android:id="@+id/button_reconnect"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="@string/reconnect"
+            android:visibility="gone" />
+
+        <Button
+            android:id="@+id/button_disconnect"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="@string/disconnect"
+            android:visibility="gone"
+            tools:visibility="visible" />
+
+    </LinearLayout>
+
+    <View
+        android:layout_width="match_parent"
+        android:layout_height="1dp"
+        android:background="?android:attr/dividerVertical" />
+
+    <androidx.recyclerview.widget.RecyclerView
+        android:id="@+id/recycler_view_device_services"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        app:layoutManager="LinearLayoutManager"
+        tools:itemCount="3"
+        tools:listitem="@layout/item_device_service" />
+</LinearLayout>
diff --git a/browser/browser/src/main/java/androidx/browser/customtabs/CustomTabsIntent.java b/browser/browser/src/main/java/androidx/browser/customtabs/CustomTabsIntent.java
index 9312d79..a8d4f44 100644
--- a/browser/browser/src/main/java/androidx/browser/customtabs/CustomTabsIntent.java
+++ b/browser/browser/src/main/java/androidx/browser/customtabs/CustomTabsIntent.java
@@ -47,7 +47,6 @@
 import androidx.annotation.RequiresApi;
 import androidx.annotation.RestrictTo;
 import androidx.core.app.ActivityOptionsCompat;
-import androidx.core.app.BundleCompat;
 import androidx.core.content.ContextCompat;
 
 import java.lang.annotation.Retention;
@@ -734,7 +733,7 @@
         private void setSessionParameters(@Nullable IBinder binder,
                 @Nullable PendingIntent sessionId) {
             Bundle bundle = new Bundle();
-            BundleCompat.putBinder(bundle, EXTRA_SESSION, binder);
+            bundle.putBinder(EXTRA_SESSION, binder);
             if (sessionId != null) {
                 bundle.putParcelable(EXTRA_SESSION_ID, sessionId);
             }
diff --git a/browser/browser/src/main/java/androidx/browser/customtabs/CustomTabsSessionToken.java b/browser/browser/src/main/java/androidx/browser/customtabs/CustomTabsSessionToken.java
index 3d6b1d8..cc354551 100644
--- a/browser/browser/src/main/java/androidx/browser/customtabs/CustomTabsSessionToken.java
+++ b/browser/browser/src/main/java/androidx/browser/customtabs/CustomTabsSessionToken.java
@@ -28,7 +28,6 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.annotation.RestrictTo;
-import androidx.core.app.BundleCompat;
 
 /**
  * Wrapper class that can be used as a unique identifier for a session. Also contains an accessor
@@ -102,7 +101,7 @@
             @NonNull Intent intent) {
         Bundle b = intent.getExtras();
         if (b == null) return null;
-        IBinder binder = BundleCompat.getBinder(b, CustomTabsIntent.EXTRA_SESSION);
+        IBinder binder = b.getBinder(CustomTabsIntent.EXTRA_SESSION);
         PendingIntent sessionId = intent.getParcelableExtra(CustomTabsIntent.EXTRA_SESSION_ID);
         if (binder == null && sessionId == null) return null;
         ICustomTabsCallback callback = binder == null ? null :
diff --git a/browser/browser/src/main/java/androidx/browser/customtabs/TrustedWebUtils.java b/browser/browser/src/main/java/androidx/browser/customtabs/TrustedWebUtils.java
index dcad116..89f33bc 100644
--- a/browser/browser/src/main/java/androidx/browser/customtabs/TrustedWebUtils.java
+++ b/browser/browser/src/main/java/androidx/browser/customtabs/TrustedWebUtils.java
@@ -30,7 +30,6 @@
 import androidx.annotation.RestrictTo;
 import androidx.annotation.WorkerThread;
 import androidx.browser.trusted.TrustedWebActivityIntentBuilder;
-import androidx.core.app.BundleCompat;
 import androidx.core.content.FileProvider;
 
 import java.io.File;
@@ -96,7 +95,7 @@
         intent.setData(uri);
 
         Bundle bundle = new Bundle();
-        BundleCompat.putBinder(bundle, CustomTabsIntent.EXTRA_SESSION, session.getBinder());
+        bundle.putBinder(CustomTabsIntent.EXTRA_SESSION, session.getBinder());
         intent.putExtras(bundle);
         PendingIntent id = session.getId();
         if (id != null) {
@@ -173,8 +172,8 @@
     @Deprecated
     public static void launchAsTrustedWebActivity(@NonNull Context context,
             @NonNull CustomTabsIntent customTabsIntent, @NonNull Uri uri) {
-        if (BundleCompat.getBinder(
-                customTabsIntent.intent.getExtras(), CustomTabsIntent.EXTRA_SESSION) == null) {
+        if (customTabsIntent.intent.getExtras().getBinder(
+                CustomTabsIntent.EXTRA_SESSION) == null) {
             throw new IllegalArgumentException(
                     "Given CustomTabsIntent should be associated with a valid CustomTabsSession");
         }
diff --git a/buildSrc-tests/src/test/java/androidx/build/ProjectConfigValidatorsTest.kt b/buildSrc-tests/src/test/java/androidx/build/ProjectConfigValidatorsTest.kt
new file mode 100644
index 0000000..10b96f4
--- /dev/null
+++ b/buildSrc-tests/src/test/java/androidx/build/ProjectConfigValidatorsTest.kt
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2023 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.build
+
+import org.gradle.api.GradleException
+import org.gradle.testfixtures.ProjectBuilder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@RunWith(JUnit4::class)
+class ProjectConfigValidatorsTest {
+
+    @Test
+    fun tesValidateProjectMavenGroup() {
+        val project = ProjectBuilder.builder().withName("collection").build()
+        project.validateProjectMavenGroup("collection")
+    }
+
+    @Test(expected = GradleException::class)
+    fun tesValidateProjectMavenGroup_invalid() {
+        // Group name cannot contain a hyphen.
+        val project = ProjectBuilder.builder().withName("floatingpoint").build()
+        project.validateProjectMavenGroup("collection-floatingpoint")
+    }
+
+    @Test
+    fun testValidateProjectMavenName() {
+        val project1 = ProjectBuilder.builder().withName("collection").build()
+        project1.validateProjectMavenName("collection", "collection")
+
+        val project2 = ProjectBuilder.builder().withName("collection-floatingpoint").build()
+        project2.validateProjectMavenName("collection-floatingpoint", "collection")
+
+        // Not ideal, but we allow "-s" it because of existing projects.
+        val project3 = ProjectBuilder.builder().withName("collection").build()
+        project3.validateProjectMavenName("collections", "collection")
+    }
+
+    @Test(expected = GradleException::class)
+    fun testValidateProjectMavenName_invalid1() {
+        // Maven name must match project name, modulo some exceptions.
+        val project = ProjectBuilder.builder().withName("collection").build()
+        project.validateProjectMavenName("collection-floatingpoint", "collection")
+    }
+
+    @Test(expected = GradleException::class)
+    fun testValidateProjectMavenName_invalid2() {
+        // Maven name must match project name, modulo some exceptions.
+        val project = ProjectBuilder.builder().withName("collection").build()
+        project.validateProjectMavenName("floatingpoint", "collection")
+    }
+}
diff --git a/buildSrc-tests/src/test/java/androidx/build/clang/AndroidXClangTest.kt b/buildSrc-tests/src/test/java/androidx/build/clang/AndroidXClangTest.kt
index f73b700..edcfc17 100644
--- a/buildSrc-tests/src/test/java/androidx/build/clang/AndroidXClangTest.kt
+++ b/buildSrc-tests/src/test/java/androidx/build/clang/AndroidXClangTest.kt
@@ -16,6 +16,7 @@
 
 package androidx.build.clang
 
+import androidx.build.KonanPrebuiltsSetup
 import androidx.testutils.gradle.ProjectSetupRule
 import com.google.common.truth.Truth.assertThat
 import java.io.File
@@ -23,6 +24,7 @@
 import org.gradle.api.file.ConfigurableFileCollection
 import org.gradle.api.plugins.ExtraPropertiesExtension
 import org.gradle.testfixtures.ProjectBuilder
+import org.jetbrains.kotlin.gradle.plugin.KotlinMultiplatformPluginWrapper
 import org.jetbrains.kotlin.gradle.plugin.KotlinPluginWrapper
 import org.jetbrains.kotlin.konan.target.HostManager
 import org.jetbrains.kotlin.konan.target.KonanTarget
@@ -50,8 +52,9 @@
             "prebuiltsRoot", File(projectSetup.props.rootProjectPath).resolve("../../prebuilts")
         )
         // register components required by NativeCompilerDownloader
-        project.pluginManager.apply(KotlinPluginWrapper::class.java)
+        project.pluginManager.apply(KotlinMultiplatformPluginWrapper::class.java)
         clangExtension = AndroidXClang(project)
+        KonanPrebuiltsSetup.configureKonanDirectory(project)
     }
 
     @Test
diff --git a/buildSrc-tests/src/test/java/androidx/build/clang/KonanBuildServiceTest.kt b/buildSrc-tests/src/test/java/androidx/build/clang/KonanBuildServiceTest.kt
index 6fb4c69..3a6cdec 100644
--- a/buildSrc-tests/src/test/java/androidx/build/clang/KonanBuildServiceTest.kt
+++ b/buildSrc-tests/src/test/java/androidx/build/clang/KonanBuildServiceTest.kt
@@ -16,6 +16,7 @@
 
 package androidx.build.clang
 
+import androidx.build.KonanPrebuiltsSetup
 import androidx.testutils.assertThrows
 import androidx.testutils.gradle.ProjectSetupRule
 import com.google.common.truth.Truth.assertThat
@@ -26,6 +27,7 @@
 import org.gradle.api.file.DirectoryProperty
 import org.gradle.api.plugins.ExtraPropertiesExtension
 import org.gradle.testfixtures.ProjectBuilder
+import org.jetbrains.kotlin.gradle.plugin.KotlinMultiplatformPluginWrapper
 import org.jetbrains.kotlin.gradle.plugin.KotlinPluginWrapper
 import org.jetbrains.kotlin.konan.target.KonanTarget
 import org.junit.Before
@@ -53,7 +55,8 @@
             File(projectSetup.props.rootProjectPath).resolve("../../prebuilts")
         )
         // register components required by NativeCompilerDownloader
-        project.pluginManager.apply(KotlinPluginWrapper::class.java)
+        project.pluginManager.apply(KotlinMultiplatformPluginWrapper::class.java)
+        KonanPrebuiltsSetup.configureKonanDirectory(project)
         buildService = KonanBuildService.obtain(project).get()
     }
 
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/AndroidXImplPlugin.kt b/buildSrc/private/src/main/kotlin/androidx/build/AndroidXImplPlugin.kt
index 3de58fd..ea611b3 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/AndroidXImplPlugin.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/AndroidXImplPlugin.kt
@@ -97,10 +97,8 @@
 import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType
 import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinAndroidTarget
 import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTargetWithSimulatorTests
-import org.jetbrains.kotlin.gradle.tasks.CInteropProcess
 import org.jetbrains.kotlin.gradle.tasks.KotlinCompilationTask
 import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
-import org.jetbrains.kotlin.gradle.tasks.KotlinNativeCompile
 
 /**
  * A plugin which enables all of the Gradle customizations for AndroidX. This plugin reacts to other
@@ -443,7 +441,7 @@
             }
         }
         if (plugin is KotlinMultiplatformPluginWrapper) {
-            project.configureKonanDirectory()
+            KonanPrebuiltsSetup.configureKonanDirectory(project)
 
             val libraryExtension = project.extensions.findByType<LibraryExtension>()
             if (libraryExtension != null) {
@@ -746,8 +744,9 @@
                 extension.type == LibraryType.PUBLISHED_LIBRARY ||
                     extension.type == LibraryType.UNSET
             if (mavenGroup != null && isProbablyPublished && extension.shouldPublish()) {
-                validateProjectStructure(mavenGroup.group)
+                validateProjectMavenGroup(mavenGroup.group)
                 validateProjectMavenName(extension.name.get(), mavenGroup.group)
+                validateProjectStructure(mavenGroup.group)
             }
         }
     }
@@ -951,37 +950,6 @@
             .srcFile("src/androidInstrumentedTest/AndroidManifest.xml")
     }
 
-    /** Sets the konan distribution url to the prebuilts directory. */
-    private fun Project.configureKonanDirectory() {
-        if (ProjectLayoutType.isPlayground(this)) {
-            return // playground does not use prebuilts
-        }
-        overrideKotlinNativeDistributionUrlToLocalDirectory()
-        overrideKotlinNativeDependenciesUrlToLocalDirectory()
-    }
-
-    private fun Project.overrideKotlinNativeDependenciesUrlToLocalDirectory() {
-        val konanPrebuiltsFolder = getKonanPrebuiltsFolder()
-        // use relative path so it doesn't affect gradle remote cache.
-        val relativeRootPath = konanPrebuiltsFolder.relativeTo(rootProject.projectDir).path
-        val relativeProjectPath = konanPrebuiltsFolder.relativeTo(projectDir).path
-        tasks.withType(KotlinNativeCompile::class.java).configureEach {
-            it.kotlinOptions.freeCompilerArgs +=
-                listOf("-Xoverride-konan-properties=dependenciesUrl=file:$relativeRootPath")
-        }
-        tasks.withType(CInteropProcess::class.java).configureEach {
-            it.settings.extraOpts +=
-                listOf("-Xoverride-konan-properties", "dependenciesUrl=file:$relativeProjectPath")
-        }
-    }
-
-    private fun Project.overrideKotlinNativeDistributionUrlToLocalDirectory() {
-        val relativePath =
-            getKonanPrebuiltsFolder().resolve("nativeCompilerPrebuilts").relativeTo(projectDir).path
-        val url = "file:$relativePath"
-        extensions.extraProperties["kotlin.native.distribution.baseDownloadUrl"] = url
-    }
-
     private fun Project.configureKmp() {
         val kmpExtension =
             checkNotNull(project.extensions.findByType<KotlinMultiplatformExtension>()) {
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/KonanPrebuiltsSetup.kt b/buildSrc/private/src/main/kotlin/androidx/build/KonanPrebuiltsSetup.kt
new file mode 100644
index 0000000..1424f4e
--- /dev/null
+++ b/buildSrc/private/src/main/kotlin/androidx/build/KonanPrebuiltsSetup.kt
@@ -0,0 +1,92 @@
+/*
+ * Copyright 2023 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.build
+
+import java.io.File
+import org.gradle.api.Project
+import org.jetbrains.kotlin.gradle.tasks.CInteropProcess
+import org.jetbrains.kotlin.gradle.tasks.KotlinNativeCompile
+import org.jetbrains.kotlin.konan.target.Distribution
+
+/**
+ * Helper class to override Konan prebuilts directories to use local konan prebuilts.
+ */
+object KonanPrebuiltsSetup {
+    /**
+     * Flag to notify we've updated the konan properties so that we can avoid re-doing it
+     * if [configureKonanDirectory] call comes from multiple code paths.
+     */
+    private const val DID_SETUP_KONAN_PROPERTIES_FLAG = "androidx.didSetupKonanProperties"
+
+    /**
+     * Creates a Konan distribution with the given [prebuiltsDirectory] and [konanHome].
+     */
+    fun createKonanDistribution(
+        prebuiltsDirectory: File,
+        konanHome: File
+    ) = Distribution(
+        konanHome = konanHome.canonicalPath,
+        onlyDefaultProfiles = false,
+        propertyOverrides = mapOf(
+            "dependenciesUrl" to "file://${prebuiltsDirectory.canonicalPath}"
+        )
+    )
+
+    /**
+     * Returns `true` if the project's konan prebuilts is already configured.
+     */
+    fun isConfigured(project: Project): Boolean {
+        return project.extensions.extraProperties.has(DID_SETUP_KONAN_PROPERTIES_FLAG)
+    }
+
+    /** Sets the konan distribution url to the prebuilts directory. */
+    fun configureKonanDirectory(project: Project) {
+        check(!isConfigured(project)) {
+            "Konan prebuilts directories for project ${project.path} are already configured"
+        }
+        if (ProjectLayoutType.isPlayground(project)) {
+            // playground does not use prebuilts
+        } else {
+            // set konan prebuilts download URLs to AndroidX prebuilts
+            project.overrideKotlinNativeDistributionUrlToLocalDirectory()
+            project.overrideKotlinNativeDependenciesUrlToLocalDirectory()
+        }
+        project.extensions.extraProperties.set(DID_SETUP_KONAN_PROPERTIES_FLAG, true)
+    }
+
+    private fun Project.overrideKotlinNativeDependenciesUrlToLocalDirectory() {
+        val konanPrebuiltsFolder = getKonanPrebuiltsFolder()
+        // use relative path so it doesn't affect gradle remote cache.
+        val relativeRootPath = konanPrebuiltsFolder.relativeTo(rootProject.projectDir).path
+        val relativeProjectPath = konanPrebuiltsFolder.relativeTo(projectDir).path
+        tasks.withType(KotlinNativeCompile::class.java).configureEach {
+            it.kotlinOptions.freeCompilerArgs +=
+                listOf("-Xoverride-konan-properties=dependenciesUrl=file:$relativeRootPath")
+        }
+        tasks.withType(CInteropProcess::class.java).configureEach {
+            it.settings.extraOpts +=
+                listOf("-Xoverride-konan-properties", "dependenciesUrl=file:$relativeProjectPath")
+        }
+    }
+
+    private fun Project.overrideKotlinNativeDistributionUrlToLocalDirectory() {
+        val relativePath =
+            getKonanPrebuiltsFolder().resolve("nativeCompilerPrebuilts").relativeTo(projectDir).path
+        val url = "file:$relativePath"
+        extensions.extraProperties["kotlin.native.distribution.baseDownloadUrl"] = url
+    }
+}
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/LibraryVersionsService.kt b/buildSrc/private/src/main/kotlin/androidx/build/LibraryVersionsService.kt
index e4b6d89..a8c1752 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/LibraryVersionsService.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/LibraryVersionsService.kt
@@ -130,8 +130,7 @@
                         "doesn't exist"
                 )
         }
-        val result = mutableListOf<LibraryGroupAssociation>()
-        for (name in groups.keySet()) {
+        groups.keySet().sorted().map { name ->
             // get group name
             val groupDefinition = groups.getTable(name)!!
             val groupName = groupDefinition.getString("group")!!
@@ -153,10 +152,8 @@
                 }
 
             val group = LibraryGroup(finalGroupName, atomicGroupVersion)
-            val association = LibraryGroupAssociation(name, group, overrideApplyToProjects)
-            result.add(association)
+            LibraryGroupAssociation(name, group, overrideApplyToProjects)
         }
-        result
     }
 }
 
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/ProjectConfigValidators.kt b/buildSrc/private/src/main/kotlin/androidx/build/ProjectConfigValidators.kt
index 42a0035..cb53c40 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/ProjectConfigValidators.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/ProjectConfigValidators.kt
@@ -20,6 +20,17 @@
 import org.gradle.api.GradleException
 import org.gradle.api.Project
 
+/** Validates the project's Maven group against Jetpack guidelines. */
+fun Project.validateProjectMavenGroup(groupId: String) {
+   if (groupId.contains('-')) {
+       throw GradleException(
+           "Invalid Maven group! Found invalid character '-' in Maven group \"$groupId\" for " +
+               "$displayName.\n\nWas this supposed to be a sub-artifact of an existing group, " +
+               "ex. \"x.y:y-z\" rather than \"x.y-z:z\"?"
+       )
+   }
+}
+
 // Translate common phrases and marketing names into Maven name component equivalents.
 private val mavenNameMap =
     mapOf(
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/clang/KonanBuildService.kt b/buildSrc/private/src/main/kotlin/androidx/build/clang/KonanBuildService.kt
index 94cb369..5d3c3aa 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/clang/KonanBuildService.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/clang/KonanBuildService.kt
@@ -16,6 +16,8 @@
 
 package androidx.build.clang
 
+import androidx.build.KonanPrebuiltsSetup
+import androidx.build.clang.KonanBuildService.Companion.obtain
 import androidx.build.getKonanPrebuiltsFolder
 import java.io.ByteArrayOutputStream
 import javax.inject.Inject
@@ -29,8 +31,8 @@
 import org.gradle.api.services.BuildServiceParameters
 import org.gradle.process.ExecOperations
 import org.gradle.process.ExecSpec
+import org.jetbrains.kotlin.gradle.plugin.KotlinMultiplatformPluginWrapper
 import org.jetbrains.kotlin.gradle.utils.NativeCompilerDownloader
-import org.jetbrains.kotlin.konan.target.Distribution
 import org.jetbrains.kotlin.konan.target.LinkerOutputKind
 import org.jetbrains.kotlin.konan.target.Platform
 import org.jetbrains.kotlin.konan.target.PlatformManager
@@ -49,12 +51,9 @@
     private val execOperations: ExecOperations
 ) : BuildService<KonanBuildService.Parameters> {
     private val dist by lazy {
-        Distribution(
-            konanHome = parameters.konanHome.get().asFile.absolutePath,
-            onlyDefaultProfiles = false,
-            propertyOverrides = mapOf(
-                "dependenciesUrl" to "file://${parameters.prebuilts.get().asFile}"
-            )
+        KonanPrebuiltsSetup.createKonanDistribution(
+            prebuiltsDirectory = parameters.prebuilts.get().asFile,
+            konanHome = parameters.konanHome.get().asFile
         )
     }
 
@@ -220,6 +219,14 @@
                 KEY,
                 KonanBuildService::class.java
             ) {
+                check(
+                    project.plugins.hasPlugin(KotlinMultiplatformPluginWrapper::class.java)
+                ) {
+                    "KonanBuildService can only be used in projects that applied the KMP plugin"
+                }
+                check(KonanPrebuiltsSetup.isConfigured(project)) {
+                    "Konan prebuilt directories are not configured for project \"${project.path}\""
+                }
                 val nativeCompilerDownloader = NativeCompilerDownloader(project)
                 nativeCompilerDownloader.downloadIfNeeded()
 
diff --git a/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/internal/AdvancedSessionProcessorTest.kt b/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/internal/AdvancedSessionProcessorTest.kt
index 28563e3..6828f69 100644
--- a/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/internal/AdvancedSessionProcessorTest.kt
+++ b/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/internal/AdvancedSessionProcessorTest.kt
@@ -14,22 +14,6 @@
  * limitations under the License.
  */
 
-/*
- * Copyright 2022 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.camera.extensions.internal
 
 import android.content.Context
@@ -392,26 +376,27 @@
     }
 
     private fun createOutputSurface(width: Int, height: Int, format: Int): OutputSurface {
-        val captureImageReader = ImageReader.newInstance(width, height, format, 1);
+        val captureImageReader = ImageReader.newInstance(width, height, format, 1)
         return OutputSurface.create(captureImageReader.surface, Size(width, height), format)
     }
 
     @Test
     fun canSetSessionTypeFromOemImpl() {
-        assumeTrue(ExtensionVersion.isMinimumCompatibleVersion(Version.VERSION_1_4))
+        assumeTrue(ClientVersion.isMinimumCompatibleVersion(Version.VERSION_1_4) &&
+            ExtensionVersion.isMinimumCompatibleVersion(Version.VERSION_1_4))
         // 1. Arrange.
-        val sessionTypeToVerify = 4;
+        val sessionTypeToVerify = 4
         val fakeSessionProcessImpl = FakeSessionProcessImpl()
-        fakeSessionProcessImpl.sessionType = sessionTypeToVerify;
+        fakeSessionProcessImpl.sessionType = sessionTypeToVerify
         val advancedSessionProcessor = AdvancedSessionProcessor(fakeSessionProcessImpl,
             emptyList(), context)
-        val fakeCameraInfo = Camera2CameraInfoImpl("0", CameraManagerCompat.from(context));
-        val previewOutputSurface = createOutputSurface(640, 480, ImageFormat.YUV_420_888);
-        val imageCaptureSurface = createOutputSurface(640, 480, ImageFormat.JPEG);
+        val fakeCameraInfo = Camera2CameraInfoImpl("0", CameraManagerCompat.from(context))
+        val previewOutputSurface = createOutputSurface(640, 480, ImageFormat.YUV_420_888)
+        val imageCaptureSurface = createOutputSurface(640, 480, ImageFormat.JPEG)
 
         // 2. Act.
         val sessionConfig = advancedSessionProcessor
-            .initSession(fakeCameraInfo, previewOutputSurface, imageCaptureSurface, null);
+            .initSession(fakeCameraInfo, previewOutputSurface, imageCaptureSurface, null)
 
         // 3. Assert.
         assertThat(sessionConfig.sessionType).isEqualTo(sessionTypeToVerify)
@@ -421,16 +406,16 @@
     fun defaultSessionType() {
         // 1. Arrange.
         val fakeSessionProcessImpl = FakeSessionProcessImpl()
-        fakeSessionProcessImpl.sessionType = -1;
+        fakeSessionProcessImpl.sessionType = -1
         val advancedSessionProcessor = AdvancedSessionProcessor(fakeSessionProcessImpl,
             emptyList(), context)
-        val fakeCameraInfo = Camera2CameraInfoImpl("0", CameraManagerCompat.from(context));
-        val previewOutputSurface = createOutputSurface(640, 480, ImageFormat.YUV_420_888);
-        val imageCaptureSurface = createOutputSurface(640, 480, ImageFormat.JPEG);
+        val fakeCameraInfo = Camera2CameraInfoImpl("0", CameraManagerCompat.from(context))
+        val previewOutputSurface = createOutputSurface(640, 480, ImageFormat.YUV_420_888)
+        val imageCaptureSurface = createOutputSurface(640, 480, ImageFormat.JPEG)
 
         // 2. Act.
         val sessionConfig = advancedSessionProcessor
-            .initSession(fakeCameraInfo, previewOutputSurface, imageCaptureSurface, null);
+            .initSession(fakeCameraInfo, previewOutputSurface, imageCaptureSurface, null)
 
         // 3. Assert.
         assertThat(sessionConfig.sessionType).isEqualTo(SessionConfiguration.SESSION_REGULAR)
@@ -547,7 +532,7 @@
     private var startTriggerParametersDeferred =
         CompletableDeferred<MutableMap<CaptureRequest.Key<*>, Any>>()
 
-    var sessionType: Int = -1;
+    var sessionType: Int = -1
     override fun initSession(
         cameraId: String,
         cameraCharacteristicsMap: MutableMap<String, CameraCharacteristics>,
@@ -575,7 +560,7 @@
         }
 
         if (ExtensionVersion.isMinimumCompatibleVersion(Version.VERSION_1_4)) {
-            sessionBuilder.setSessionType(sessionType);
+            sessionBuilder.setSessionType(sessionType)
         }
         return sessionBuilder.build()
     }
diff --git a/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/internal/sessionprocessor/BasicExtenderSessionProcessorTest.kt b/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/internal/sessionprocessor/BasicExtenderSessionProcessorTest.kt
index c26ed45..e809de6 100644
--- a/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/internal/sessionprocessor/BasicExtenderSessionProcessorTest.kt
+++ b/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/internal/sessionprocessor/BasicExtenderSessionProcessorTest.kt
@@ -211,20 +211,21 @@
     }
 
     private fun createOutputSurface(width: Int, height: Int, format: Int): OutputSurface {
-        val captureImageReader = ImageReader.newInstance(width, height, format, 1);
+        val captureImageReader = ImageReader.newInstance(width, height, format, 1)
         return OutputSurface.create(captureImageReader.surface, Size(width, height), format)
     }
 
     @Test
     fun canSetSessionTypeFromOem() {
-        assumeTrue(ExtensionVersion.isMinimumCompatibleVersion(Version.VERSION_1_4));
+        assumeTrue(ClientVersion.isMinimumCompatibleVersion(Version.VERSION_1_4) &&
+            ExtensionVersion.isMinimumCompatibleVersion(Version.VERSION_1_4))
         val sessionTypeToVerify = 4
         fakeCaptureExtenderImpl.sessionType = sessionTypeToVerify
         fakePreviewExtenderImpl.sessionType = sessionTypeToVerify
 
-        val fakeCameraInfo = Camera2CameraInfoImpl("0", CameraManagerCompat.from(context));
-        val previewOutputSurface = createOutputSurface(640, 480, ImageFormat.YUV_420_888);
-        val imageCaptureSurface = createOutputSurface(640, 480, ImageFormat.JPEG);
+        val fakeCameraInfo = Camera2CameraInfoImpl("0", CameraManagerCompat.from(context))
+        val previewOutputSurface = createOutputSurface(640, 480, ImageFormat.YUV_420_888)
+        val imageCaptureSurface = createOutputSurface(640, 480, ImageFormat.JPEG)
 
         val sessionConfig = basicExtenderSessionProcessor.initSession(
             fakeCameraInfo, previewOutputSurface, imageCaptureSurface, null)
@@ -234,13 +235,14 @@
 
     @Test
     fun setDifferentSessionTypes_throwException() {
-        assumeTrue(ExtensionVersion.isMinimumCompatibleVersion(Version.VERSION_1_4));
+        assumeTrue(ClientVersion.isMinimumCompatibleVersion(Version.VERSION_1_4) &&
+            ExtensionVersion.isMinimumCompatibleVersion(Version.VERSION_1_4))
         fakeCaptureExtenderImpl.sessionType = 2
         fakePreviewExtenderImpl.sessionType = 3
 
-        val fakeCameraInfo = Camera2CameraInfoImpl("0", CameraManagerCompat.from(context));
-        val previewOutputSurface = createOutputSurface(640, 480, ImageFormat.YUV_420_888);
-        val imageCaptureSurface = createOutputSurface(640, 480, ImageFormat.JPEG);
+        val fakeCameraInfo = Camera2CameraInfoImpl("0", CameraManagerCompat.from(context))
+        val previewOutputSurface = createOutputSurface(640, 480, ImageFormat.YUV_420_888)
+        val imageCaptureSurface = createOutputSurface(640, 480, ImageFormat.JPEG)
 
         assertThrows<IllegalArgumentException> {
              basicExtenderSessionProcessor.initSession(
@@ -251,13 +253,14 @@
 
     @Test
     fun defaultSessionType() {
-        assumeTrue(ExtensionVersion.isMinimumCompatibleVersion(Version.VERSION_1_4));
+        assumeTrue(ClientVersion.isMinimumCompatibleVersion(Version.VERSION_1_4) &&
+            ExtensionVersion.isMinimumCompatibleVersion(Version.VERSION_1_4))
         fakeCaptureExtenderImpl.sessionType = -1
         fakePreviewExtenderImpl.sessionType = -1
 
-        val fakeCameraInfo = Camera2CameraInfoImpl("0", CameraManagerCompat.from(context));
-        val previewOutputSurface = createOutputSurface(640, 480, ImageFormat.YUV_420_888);
-        val imageCaptureSurface = createOutputSurface(640, 480, ImageFormat.JPEG);
+        val fakeCameraInfo = Camera2CameraInfoImpl("0", CameraManagerCompat.from(context))
+        val previewOutputSurface = createOutputSurface(640, 480, ImageFormat.YUV_420_888)
+        val imageCaptureSurface = createOutputSurface(640, 480, ImageFormat.JPEG)
 
         val sessionConfig = basicExtenderSessionProcessor.initSession(
             fakeCameraInfo, previewOutputSurface, imageCaptureSurface, null)
@@ -377,7 +380,7 @@
         }
 
         withContext(Dispatchers.Main) { fakeLifecycleOwner.pauseAndStop() }
-        assertThat(cameraClosedLatch.await(1, TimeUnit.SECONDS)).isTrue()
+        assertThat(cameraClosedLatch.await(3, TimeUnit.SECONDS)).isTrue()
 
         fakeCaptureExtenderImpl.assertInvokeOrder(
             listOf(
diff --git a/camera/camera-viewfinder-core/build.gradle b/camera/camera-viewfinder-core/build.gradle
index 7cad02d..67247c2 100644
--- a/camera/camera-viewfinder-core/build.gradle
+++ b/camera/camera-viewfinder-core/build.gradle
@@ -24,12 +24,23 @@
 }
 
 dependencies {
-    api(libs.kotlinStdlib)
-    // Add dependencies here
+    api("androidx.annotation:annotation:1.2.0")
+    implementation("androidx.annotation:annotation-experimental:1.3.1")
+    implementation(libs.guavaListenableFuture)
+    implementation("androidx.core:core:1.7.0")
+    implementation("androidx.concurrent:concurrent-futures:1.1.0")
+    implementation("androidx.concurrent:concurrent-futures-ktx:1.1.0")
+    implementation(libs.autoValueAnnotations)
+    implementation("androidx.appcompat:appcompat:1.1.0")
+    implementation("androidx.test.espresso:espresso-idling-resource:3.1.0")
+    implementation(libs.kotlinCoroutinesCore)
+    implementation(libs.kotlinCoroutinesAndroid)
+
+    annotationProcessor(libs.autoValue)
 }
 
 android {
-    namespace "androidx.camera.viewfinder"
+    namespace "androidx.camera.viewfinder.core"
 }
 
 androidx {
diff --git a/camera/camera-viewfinder/src/main/java/androidx/camera/viewfinder/internal/utils/executor/package-info.java b/camera/camera-viewfinder-core/src/main/java/androidx/camera/impl/package-info.java
similarity index 71%
rename from camera/camera-viewfinder/src/main/java/androidx/camera/viewfinder/internal/utils/executor/package-info.java
rename to camera/camera-viewfinder-core/src/main/java/androidx/camera/impl/package-info.java
index 4312b3d..e1d0eef 100644
--- a/camera/camera-viewfinder/src/main/java/androidx/camera/viewfinder/internal/utils/executor/package-info.java
+++ b/camera/camera-viewfinder-core/src/main/java/androidx/camera/impl/package-info.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2022 The Android Open Source Project
+ * Copyright 2023 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.
@@ -15,8 +15,11 @@
  */
 
 /**
+ *
  */
-@RestrictTo(RestrictTo.Scope.LIBRARY)
-package androidx.camera.viewfinder.internal.utils.executor;
+@RequiresApi(21)
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+package androidx.camera.impl; // TODO(b/309517170): move util classes into their own module
 
+import androidx.annotation.RequiresApi;
 import androidx.annotation.RestrictTo;
diff --git a/camera/camera-viewfinder-core/src/main/java/androidx/camera/impl/utils/Logger.kt b/camera/camera-viewfinder-core/src/main/java/androidx/camera/impl/utils/Logger.kt
new file mode 100644
index 0000000..d6b02d1
--- /dev/null
+++ b/camera/camera-viewfinder-core/src/main/java/androidx/camera/impl/utils/Logger.kt
@@ -0,0 +1,221 @@
+/*
+ * Copyright 2023 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.camera.impl.utils
+
+import android.os.Build
+import android.util.Log
+
+/**
+ * Handles logging requests inside CameraX. Log messages are output only if:
+ * - The minimum logging level allows for it. The minimum logging level is set via
+ * [.setMinLogLevel], which should typically be called during the process of configuring
+ * CameraX.
+ * - The log tag is [loggable][Log.isLoggable]. This can be configured
+ * by setting the system property `setprop log.tag.TAG LEVEL`, where TAG is the log tag, and
+ * LEVEL is [Log.DEBUG], [Log.INFO], [Log.WARN] or [Log.ERROR].
+ *
+ *  A typical usage of the Logger looks as follows:
+ * <pre>
+ * try {
+ * int quotient = dividend / divisor;
+ * } catch (ArithmeticException exception) {
+ * Logger.e(TAG, "Divide operation error", exception);
+ * }
+</pre> *
+ *
+ *  If an action has to be performed alongside logging, or if building the log message is costly,
+ * perform a log level check before attempting to log.
+ * <pre>
+ * try {
+ * int quotient = dividend / divisor;
+ * } catch (ArithmeticException exception) {
+ * if (Logger.isErrorEnabled(TAG)) {
+ * Logger.e(TAG, "Divide operation error", exception);
+ * doSomething();
+ * }
+ * }
+</pre> *
+ */
+object Logger {
+    /** On API levels strictly below 24, the log tag's length must not exceed 23 characters.  */
+    private const val MAX_TAG_LENGTH = 23
+    private const val DEFAULT_MIN_LOG_LEVEL = Log.DEBUG
+    /**
+     * Returns current minimum logging level.
+     */
+    /**
+     * Sets the minimum logging level to use in [Logger]. After calling this method, only logs
+     * at the level `logLevel` and above are output.
+     */
+    private var minLogLevel = DEFAULT_MIN_LOG_LEVEL
+
+    /**
+     * Returns `true` if logging with the truncated tag `truncatedTag` is
+     * enabled at the `logLevel` level.
+     */
+    private fun isLogLevelEnabled(truncatedTag: String, logLevel: Int): Boolean {
+        return minLogLevel <= logLevel || Log.isLoggable(truncatedTag, logLevel)
+    }
+
+    /**
+     * Resets the minimum logging level to use in [Logger] to the default minimum logging
+     * level. After calling this method, only logs at the default level and above are output.
+     */
+    fun resetMinLogLevel() {
+        minLogLevel = DEFAULT_MIN_LOG_LEVEL
+    }
+
+    /**
+     * Returns `true` if logging with the tag `tag` is enabled at the [Log.DEBUG]
+     * level. This is true when the minimum logging level is less than or equal to
+     * [Log.DEBUG], or if the log level of `tag` was explicitly set to
+     * [Log.DEBUG] at least.
+     */
+    fun isDebugEnabled(tag: String): Boolean {
+        return isLogLevelEnabled(truncateTag(tag), Log.DEBUG)
+    }
+
+    /**
+     * Returns `true` if logging with the tag `tag` is enabled at the [Log.INFO]
+     * level. This is true when the minimum logging level is less than or equal to
+     * [Log.INFO], or if the log level of `tag` was explicitly set to
+     * [Log.INFO] at least.
+     */
+    fun isInfoEnabled(tag: String): Boolean {
+        return isLogLevelEnabled(truncateTag(tag), Log.INFO)
+    }
+
+    /**
+     * Returns `true` if logging with the tag `tag` is enabled at the [Log.WARN]
+     * level. This is true when the minimum logging level is less than or equal to
+     * [Log.WARN], or if the log level of `tag` was explicitly set to
+     * [Log.WARN] at least.
+     */
+    fun isWarnEnabled(tag: String): Boolean {
+        return isLogLevelEnabled(truncateTag(tag), Log.WARN)
+    }
+
+    /**
+     * Returns `true` if logging with the tag `tag` is enabled at the [Log.ERROR]
+     * level. This is true when the minimum logging level is less than or equal to
+     * [Log.ERROR], or if the log level of `tag` was explicitly set to
+     * [Log.ERROR] at least.
+     */
+    fun isErrorEnabled(tag: String): Boolean {
+        return isLogLevelEnabled(truncateTag(tag), Log.ERROR)
+    }
+
+    /**
+     * Logs the given [Log.DEBUG] message if the tag is
+     * [loggable][.isDebugEnabled].
+     */
+    fun d(tag: String, message: String) {
+        val truncatedTag = truncateTag(tag)
+        if (isLogLevelEnabled(truncatedTag, Log.DEBUG)) {
+            Log.d(truncatedTag, message)
+        }
+    }
+
+    /**
+     * Logs the given [Log.DEBUG] message and the exception's stacktrace if the tag is
+     * [loggable][.isDebugEnabled].
+     */
+    fun d(tag: String, message: String, throwable: Throwable) {
+        val truncatedTag = truncateTag(tag)
+        if (isLogLevelEnabled(truncatedTag, Log.DEBUG)) {
+            Log.d(truncatedTag, message, throwable)
+        }
+    }
+
+    /**
+     * Logs the given [Log.INFO] message if the tag is
+     * [loggable][.isInfoEnabled].
+     */
+    fun i(tag: String, message: String) {
+        val truncatedTag = truncateTag(tag)
+        if (isLogLevelEnabled(truncatedTag, Log.INFO)) {
+            Log.i(truncatedTag, message)
+        }
+    }
+
+    /**
+     * Logs the given [Log.INFO] message and the exception's stacktrace if the tag is
+     * [loggable][.isInfoEnabled].
+     */
+    fun i(tag: String, message: String, throwable: Throwable) {
+        val truncatedTag = truncateTag(tag)
+        if (isLogLevelEnabled(truncatedTag, Log.INFO)) {
+            Log.i(truncatedTag, message, throwable)
+        }
+    }
+
+    /**
+     * Logs the given [Log.WARN] message if the tag is
+     * [loggable][.isWarnEnabled].
+     */
+    fun w(tag: String, message: String) {
+        val truncatedTag = truncateTag(tag)
+        if (isLogLevelEnabled(truncatedTag, Log.WARN)) {
+            Log.w(truncatedTag, message)
+        }
+    }
+
+    /**
+     * Logs the given [Log.WARN] message and the exception's stacktrace if the tag is
+     * [loggable][.isWarnEnabled].
+     */
+    fun w(tag: String, message: String, throwable: Throwable) {
+        val truncatedTag = truncateTag(tag)
+        if (isLogLevelEnabled(truncatedTag, Log.WARN)) {
+            Log.w(truncatedTag, message, throwable)
+        }
+    }
+
+    /**
+     * Logs the given [Log.ERROR] message if the tag is
+     * [loggable][.isErrorEnabled].
+     */
+    fun e(tag: String, message: String) {
+        val truncatedTag = truncateTag(tag)
+        if (isLogLevelEnabled(truncatedTag, Log.ERROR)) {
+            Log.e(truncatedTag, message)
+        }
+    }
+
+    /**
+     * Logs the given [Log.ERROR] message and the exception's stacktrace if the tag is
+     * [loggable][.isErrorEnabled].
+     */
+    fun e(tag: String, message: String, throwable: Throwable) {
+        val truncatedTag = truncateTag(tag)
+        if (isLogLevelEnabled(truncatedTag, Log.ERROR)) {
+            Log.e(truncatedTag, message, throwable)
+        }
+    }
+
+    /**
+     * Truncates the tag so it can be used to log.
+     *
+     *
+     * On API 24, the tag length limit of 23 characters was removed.
+     */
+    private fun truncateTag(tag: String): String {
+        return if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N && MAX_TAG_LENGTH < tag.length) {
+            tag.substring(0, MAX_TAG_LENGTH)
+        } else tag
+    }
+}
diff --git a/camera/camera-viewfinder-core/src/main/java/androidx/camera/impl/utils/executor/CameraExecutors.kt b/camera/camera-viewfinder-core/src/main/java/androidx/camera/impl/utils/executor/CameraExecutors.kt
new file mode 100644
index 0000000..acd04ef
--- /dev/null
+++ b/camera/camera-viewfinder-core/src/main/java/androidx/camera/impl/utils/executor/CameraExecutors.kt
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2023 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.camera.impl.utils.executor
+
+import java.util.concurrent.Executor
+import java.util.concurrent.ScheduledExecutorService
+
+/**
+ * Utility class for generating specific implementations of [Executor].
+ */
+object CameraExecutors {
+    /** Returns a cached [ScheduledExecutorService] which posts to the main thread.  */
+    @JvmStatic
+    fun mainThreadExecutor(): ScheduledExecutorService {
+        return MainThreadExecutor.instance
+    }
+
+    /** Returns a cached executor that runs tasks directly from the calling thread.  */
+    @JvmStatic
+    fun directExecutor(): Executor {
+        return DirectExecutor.instance
+    }
+}
diff --git a/camera/camera-viewfinder-core/src/main/java/androidx/camera/impl/utils/executor/DirectExecutor.kt b/camera/camera-viewfinder-core/src/main/java/androidx/camera/impl/utils/executor/DirectExecutor.kt
new file mode 100644
index 0000000..0bdad3a
--- /dev/null
+++ b/camera/camera-viewfinder-core/src/main/java/androidx/camera/impl/utils/executor/DirectExecutor.kt
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2023 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.camera.impl.utils.executor
+
+import java.util.concurrent.Executor
+
+/**
+ * An [Executor] that runs each task in the thread that invokes [Executor.execute].
+ */
+internal class DirectExecutor : Executor {
+    override fun execute(command: Runnable) {
+        command.run()
+    }
+
+    companion object {
+        @Volatile
+        private var directExecutor: DirectExecutor? = null
+        val instance: Executor
+            get() {
+                if (directExecutor != null) {
+                    return directExecutor!!
+                }
+                synchronized(DirectExecutor::class.java) {
+                    if (directExecutor == null) {
+                        directExecutor = DirectExecutor()
+                    }
+                }
+                return directExecutor!!
+            }
+    }
+}
diff --git a/camera/camera-viewfinder-core/src/main/java/androidx/camera/impl/utils/executor/HandlerScheduledExecutorService.kt b/camera/camera-viewfinder-core/src/main/java/androidx/camera/impl/utils/executor/HandlerScheduledExecutorService.kt
new file mode 100644
index 0000000..1f8cf56
--- /dev/null
+++ b/camera/camera-viewfinder-core/src/main/java/androidx/camera/impl/utils/executor/HandlerScheduledExecutorService.kt
@@ -0,0 +1,215 @@
+/*
+ * Copyright 2023 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.camera.impl.utils.executor
+
+import android.os.Handler
+import android.os.SystemClock
+import androidx.camera.impl.utils.futures.Futures
+import androidx.concurrent.futures.CallbackToFutureAdapter
+import com.google.common.util.concurrent.ListenableFuture
+import java.util.concurrent.AbstractExecutorService
+import java.util.concurrent.Callable
+import java.util.concurrent.Delayed
+import java.util.concurrent.ExecutionException
+import java.util.concurrent.RejectedExecutionException
+import java.util.concurrent.RunnableScheduledFuture
+import java.util.concurrent.ScheduledExecutorService
+import java.util.concurrent.ScheduledFuture
+import java.util.concurrent.TimeUnit
+import java.util.concurrent.TimeoutException
+import java.util.concurrent.atomic.AtomicReference
+
+/**
+ * An implementation of [ScheduledExecutorService] which delegates all scheduled task to
+ * the given [Handler].
+ *
+ * Currently, can only be used to schedule future non-repeating tasks.
+ */
+internal class HandlerScheduledExecutorService(private val handler: Handler) :
+    AbstractExecutorService(),
+    ScheduledExecutorService {
+    override fun schedule(
+        command: Runnable,
+        delay: Long,
+        unit: TimeUnit
+    ): ScheduledFuture<*> {
+        val wrapper: Callable<Void> = Callable {
+            command.run()
+            null
+        }
+        return schedule(wrapper, delay, unit)
+    }
+
+    override fun <V> schedule(
+        callable: Callable<V>,
+        delay: Long,
+        unit: TimeUnit
+    ): ScheduledFuture<V> {
+        val runAtMillis = SystemClock.uptimeMillis() + TimeUnit.MILLISECONDS.convert(delay, unit)
+        val future = HandlerScheduledFuture(
+            handler, runAtMillis,
+            callable
+        )
+        return if (handler.postAtTime(future, runAtMillis)) {
+            future
+        } else Futures.immediateFailedScheduledFuture(
+            createPostFailedException()
+        )
+    }
+
+    override fun scheduleAtFixedRate(
+        command: Runnable,
+        initialDelay: Long,
+        period: Long,
+        unit: TimeUnit
+    ): ScheduledFuture<*> {
+        throw UnsupportedOperationException(
+            HandlerScheduledExecutorService::class.java.getSimpleName() +
+                " does not yet support fixed-rate scheduling."
+        )
+    }
+
+    override fun scheduleWithFixedDelay(
+        command: Runnable,
+        initialDelay: Long,
+        delay: Long,
+        unit: TimeUnit
+    ): ScheduledFuture<*> {
+        throw UnsupportedOperationException(
+            HandlerScheduledExecutorService::class.java.getSimpleName() +
+                " does not yet support fixed-delay scheduling."
+        )
+    }
+
+    override fun shutdown() {
+        throw UnsupportedOperationException(
+            HandlerScheduledExecutorService::class.java.getSimpleName() +
+                " cannot be shut down. Use Looper.quitSafely()."
+        )
+    }
+
+    override fun shutdownNow(): List<Runnable> {
+        throw UnsupportedOperationException(
+            HandlerScheduledExecutorService::class.java.getSimpleName() +
+                " cannot be shut down. Use Looper.quitSafely()."
+        )
+    }
+
+    override fun isShutdown(): Boolean {
+        return false
+    }
+
+    override fun isTerminated(): Boolean {
+        return false
+    }
+
+    override fun awaitTermination(timeout: Long, unit: TimeUnit): Boolean {
+        throw UnsupportedOperationException(
+            HandlerScheduledExecutorService::class.java.getSimpleName() +
+                " cannot be shut down. Use Looper.quitSafely()."
+        )
+    }
+
+    override fun execute(command: Runnable) {
+        if (!handler.post(command)) {
+            throw createPostFailedException()
+        }
+    }
+
+    private fun createPostFailedException(): RejectedExecutionException {
+        return RejectedExecutionException("$handler is shutting down")
+    }
+
+    private class HandlerScheduledFuture<V>(
+        handler: Handler,
+        private val mRunAtMillis: Long,
+        private val mTask: Callable<V>
+    ) :
+        RunnableScheduledFuture<V> {
+        val mCompleter = AtomicReference<CallbackToFutureAdapter.Completer<V>?>(null)
+        private val mDelegate: ListenableFuture<V>
+
+        init {
+            mDelegate = CallbackToFutureAdapter.getFuture { completer ->
+                completer.addCancellationListener(
+                    { // Remove the completer if we're cancelled so the task won't
+                        // run.
+                        if (mCompleter.getAndSet(null) != null) {
+                            handler.removeCallbacks(this@HandlerScheduledFuture)
+                        }
+                    },
+                    CameraExecutors.directExecutor()
+                )
+                mCompleter.set(completer)
+                "HandlerScheduledFuture-$mTask"
+            }
+        }
+
+        override fun isPeriodic(): Boolean {
+            return false
+        }
+
+        override fun getDelay(unit: TimeUnit): Long {
+            return unit.convert(
+                mRunAtMillis - System.currentTimeMillis(),
+                TimeUnit.MILLISECONDS
+            )
+        }
+
+        override fun compareTo(other: Delayed): Int {
+            return getDelay(TimeUnit.MILLISECONDS).compareTo(other.getDelay(TimeUnit.MILLISECONDS))
+        }
+
+        override fun run() {
+            // If completer is null, it has already run or is cancelled.
+            val completer = mCompleter.getAndSet(null)
+            if (completer != null) {
+                try {
+                    completer.set(mTask.call())
+                } catch (e: Exception) {
+                    completer.setException(e)
+                }
+            }
+        }
+
+        override fun cancel(mayInterruptIfRunning: Boolean): Boolean {
+            return mDelegate.cancel(mayInterruptIfRunning)
+        }
+
+        override fun isCancelled(): Boolean {
+            return mDelegate.isCancelled
+        }
+
+        override fun isDone(): Boolean {
+            return mDelegate.isDone
+        }
+
+        @Throws(ExecutionException::class, InterruptedException::class)
+        override fun get(): V {
+            return mDelegate.get()
+        }
+
+        @Throws(
+            ExecutionException::class,
+            InterruptedException::class,
+            TimeoutException::class
+        )
+        override fun get(timeout: Long, unit: TimeUnit): V {
+            return mDelegate[timeout, unit]
+        }
+    }
+}
diff --git a/camera/camera-viewfinder-core/src/main/java/androidx/camera/impl/utils/executor/MainThreadExecutor.kt b/camera/camera-viewfinder-core/src/main/java/androidx/camera/impl/utils/executor/MainThreadExecutor.kt
new file mode 100644
index 0000000..bbb7dc8
--- /dev/null
+++ b/camera/camera-viewfinder-core/src/main/java/androidx/camera/impl/utils/executor/MainThreadExecutor.kt
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2023 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.camera.impl.utils.executor
+
+import android.os.Handler
+import android.os.Looper
+import java.util.concurrent.Executor
+import java.util.concurrent.ScheduledExecutorService
+
+/**
+ * Helper class for retrieving an [ScheduledExecutorService] which will post to the main
+ * thread.
+ *
+ * Since [ScheduledExecutorService] implements [Executor], this can also be used
+ * as a simple Executor.
+ */
+
+internal class MainThreadExecutor {
+    companion object {
+        val instance: ScheduledExecutorService by lazy {
+            HandlerScheduledExecutorService(
+                Handler(Looper.getMainLooper())
+            )
+        }
+    }
+}
diff --git a/camera/camera-viewfinder-core/src/main/java/androidx/camera/impl/utils/futures/AsyncFunction.kt b/camera/camera-viewfinder-core/src/main/java/androidx/camera/impl/utils/futures/AsyncFunction.kt
new file mode 100644
index 0000000..7cfad78
--- /dev/null
+++ b/camera/camera-viewfinder-core/src/main/java/androidx/camera/impl/utils/futures/AsyncFunction.kt
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2023 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.camera.impl.utils.futures
+
+import com.google.common.util.concurrent.ListenableFuture
+import java.util.concurrent.Future
+
+/**
+ * Cloned from concurrent-futures package in Guava to AndroidX namespace since we would need
+ * ListenableFuture related implementation but not want to include whole Guava library.
+ *
+ * Transforms a value, possibly asynchronously. For an example usage and more information, see
+ * [Futures.transformAsync].
+ *
+ * @author Chris Povirk
+ * @since 11.0
+ * @param <I>
+ * @param <O>
+</O></I> */
+fun interface AsyncFunction<I, O> {
+    /**
+     * Returns an output `Future` to use in place of the given `input`. The output
+     * `Future` need not be [done][Future.isDone], making `AsyncFunction`
+     * suitable for asynchronous derivations.
+     *
+     *
+     * Throwing an exception from this method is equivalent to returning a failing `Future`.
+     */
+    @Throws(Exception::class)
+    fun apply(input: I?): ListenableFuture<O>
+}
diff --git a/camera/camera-viewfinder-core/src/main/java/androidx/camera/impl/utils/futures/ChainingListenableFuture.kt b/camera/camera-viewfinder-core/src/main/java/androidx/camera/impl/utils/futures/ChainingListenableFuture.kt
new file mode 100644
index 0000000..8c68dff
--- /dev/null
+++ b/camera/camera-viewfinder-core/src/main/java/androidx/camera/impl/utils/futures/ChainingListenableFuture.kt
@@ -0,0 +1,284 @@
+/*
+ * Copyright 2023 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.camera.impl.utils.futures
+
+import androidx.camera.impl.utils.executor.CameraExecutors
+import androidx.core.util.Preconditions
+import com.google.common.util.concurrent.ListenableFuture
+import java.lang.reflect.UndeclaredThrowableException
+import java.util.concurrent.BlockingQueue
+import java.util.concurrent.CancellationException
+import java.util.concurrent.CountDownLatch
+import java.util.concurrent.ExecutionException
+import java.util.concurrent.Future
+import java.util.concurrent.LinkedBlockingQueue
+import java.util.concurrent.TimeUnit
+import java.util.concurrent.TimeoutException
+import kotlin.math.max
+
+/**
+ * The Class is based on the ChainingListenableFuture in Guava, the constructor of FutureChain
+ * will use the CallbackToFutureAdapter instead of the AbstractFuture.
+ *
+ * An implementation of `ListenableFuture` that also implements
+ * `Runnable` so that it can be used to nest ListenableFutures.
+ * Once the passed-in `ListenableFuture` is complete, it calls the
+ * passed-in `Function` to generate the result.
+ *
+ *
+ * If the Function throws any checked exceptions, they should be wrapped
+ * in a `UndeclaredThrowableException` so that this class can get access to the cause.
+ *
+ */
+internal class ChainingListenableFuture<I, O>(
+    function: AsyncFunction<in I, out O>,
+    inputFuture: ListenableFuture<out I>
+) :
+    FutureChain<O>(), Runnable {
+    private var mFunction: AsyncFunction<in I, out O>?
+    private val mMayInterruptIfRunningChannel: BlockingQueue<Boolean> = LinkedBlockingQueue(1)
+    private val mOutputCreated = CountDownLatch(1)
+    private var mInputFuture: ListenableFuture<out I>?
+
+    @Volatile
+    var mOutputFuture: ListenableFuture<out O>? = null
+
+    init {
+        mFunction = Preconditions.checkNotNull(function)
+        mInputFuture = Preconditions.checkNotNull(inputFuture)
+    }
+
+    /**
+     * Delegate the get() to the input and output mFutures, in case
+     * their implementations defer starting computation until their
+     * own get() is invoked.
+     */
+    @Throws(InterruptedException::class, ExecutionException::class)
+    override fun get(): O? {
+        if (!isDone) {
+            // Invoking get on the mInputFuture will ensure our own run()
+            // method below is invoked as a listener when mInputFuture sets
+            // its value.  Therefore when get() returns we should then see
+            // the mOutputFuture be created.
+            val inputFuture = mInputFuture
+            inputFuture?.get()
+
+            // If our listener was scheduled to run on an executor we may
+            // need to wait for our listener to finish running before the
+            // mOutputFuture has been constructed by the mFunction.
+            mOutputCreated.await()
+
+            // Like above with the mInputFuture, we have a listener on
+            // the mOutputFuture that will set our own value when its
+            // value is set.  Invoking get will ensure the output can
+            // complete and invoke our listener, so that we can later
+            // get the mResult.
+            val outputFuture = mOutputFuture
+            outputFuture?.get()
+        }
+        return super.get()
+    }
+
+    /**
+     * Delegate the get() to the input and output mFutures, in case
+     * their implementations defer starting computation until their
+     * own get() is invoked.
+     */
+    @Throws(
+        TimeoutException::class,
+        ExecutionException::class,
+        InterruptedException::class
+    )
+    override operator fun get(timeout: Long, unit: TimeUnit): O? {
+        var resultTimeout = timeout
+        var resultUnit = unit
+        if (!isDone) {
+            // Use a single time unit so we can decrease mRemaining timeout
+            // as we wait for various phases to complete.
+            if (resultUnit != TimeUnit.NANOSECONDS) {
+                resultTimeout = TimeUnit.NANOSECONDS.convert(resultTimeout, resultUnit)
+                resultUnit = TimeUnit.NANOSECONDS
+            }
+
+            // Invoking get on the mInputFuture will ensure our own run()
+            // method below is invoked as a listener when mInputFuture sets
+            // its value.  Therefore when get() returns we should then see
+            // the mOutputFuture be created.
+            val inputFuture = mInputFuture
+            if (inputFuture != null) {
+                val start = System.nanoTime()
+                inputFuture[resultTimeout, resultUnit]
+                resultTimeout -= max(0L, (System.nanoTime() - start))
+            }
+
+            // If our listener was scheduled to run on an executor we may
+            // need to wait for our listener to finish running before the
+            // mOutputFuture has been constructed by the mFunction.
+            val start = System.nanoTime()
+            if (!mOutputCreated.await(resultTimeout, resultUnit)) {
+                throw TimeoutException()
+            }
+            resultTimeout -= max(0L, (System.nanoTime() - start))
+
+            // Like above with the mInputFuture, we have a listener on
+            // the mOutputFuture that will set our own value when its
+            // value is set.  Invoking get will ensure the output can
+            // complete and invoke our listener, so that we can later
+            // get the mResult.
+            val outputFuture = mOutputFuture
+            outputFuture?.get(resultTimeout, resultUnit)
+        }
+        return super.get(resultTimeout, resultUnit)
+    }
+
+    override fun cancel(mayInterruptIfRunning: Boolean): Boolean {
+        /*
+         * Our additional cancellation work needs to occur even if
+         * !mayInterruptIfRunning, so we can't move it into interruptTask().
+         */
+        if (super.cancel(mayInterruptIfRunning)) {
+            // This should never block since only one thread is allowed to cancel
+            // this Future.
+            putUninterruptibly(mMayInterruptIfRunningChannel, mayInterruptIfRunning)
+            cancel(mInputFuture, mayInterruptIfRunning)
+            cancel(mOutputFuture, mayInterruptIfRunning)
+            return true
+        }
+        return false
+    }
+
+    private fun cancel(
+        future: Future<*>?,
+        mayInterruptIfRunning: Boolean
+    ) {
+        future?.cancel(mayInterruptIfRunning)
+    }
+
+    override fun run() {
+        try {
+            val sourceResult: I = try {
+                Futures.getUninterruptibly(
+                    mInputFuture!!
+                )
+            } catch (e: CancellationException) {
+                // Cancel this future and return.
+                // At this point, mInputFuture is cancelled and mOutputFuture doesn't
+                // exist, so the value of mayInterruptIfRunning is irrelevant.
+                cancel(false)
+                return
+            } catch (e: ExecutionException) {
+                // Set the cause of the exception as this future's exception
+                e.cause?.let { setException(it) }
+                return
+            }
+            mOutputFuture = mFunction!!.apply(sourceResult)
+            val outputFuture = mOutputFuture
+            if (isCancelled) {
+                // Handles the case where cancel was called while the mFunction was
+                // being applied.
+                // There is a gap in cancel(boolean) between calling sync.cancel()
+                // and storing the value of mayInterruptIfRunning, so this thread
+                // needs to block, waiting for that value.
+                outputFuture!!.cancel(takeUninterruptibly(mMayInterruptIfRunningChannel))
+                mOutputFuture = null
+                return
+            }
+            outputFuture!!.addListener(Runnable {
+                try {
+                    // Here it would have been nice to have had an
+                    // UninterruptibleListenableFuture, but we don't want to start a
+                    // combinatorial explosion of interfaces, so we have to make do.
+                    set(
+                        Futures.getUninterruptibly(
+                            outputFuture
+                        )
+                    )
+                } catch (e: CancellationException) {
+                    // Cancel this future and return.
+                    // At this point, mInputFuture and mOutputFuture are done, so the
+                    // value of mayInterruptIfRunning is irrelevant.
+                    cancel(false)
+                    return@Runnable
+                } catch (e: ExecutionException) {
+                    // Set the cause of the exception as this future's exception
+                    e.cause?.let { setException(it) }
+                } finally {
+                    // Don't pin inputs beyond completion
+                    mOutputFuture = null
+                }
+            }, CameraExecutors.directExecutor())
+        } catch (e: UndeclaredThrowableException) {
+            // Set the cause of the exception as this future's exception
+            e.cause?.let { setException(it) }
+        } catch (e: Exception) {
+            // This exception is irrelevant in this thread, but useful for the
+            // client
+            setException(e)
+        } catch (e: Error) {
+            // Propagate errors up ASAP - our superclass will rethrow the error
+            setException(e)
+        } finally {
+            // Don't pin inputs beyond completion
+            mFunction = null
+            mInputFuture = null
+            // Allow our get routines to examine mOutputFuture now.
+            mOutputCreated.countDown()
+        }
+    }
+
+    /**
+     * Invokes `queue.`[take()][BlockingQueue.take] uninterruptibly.
+     */
+    private fun <E> takeUninterruptibly(queue: BlockingQueue<E>): E {
+        var interrupted = false
+        try {
+            while (true) {
+                interrupted = try {
+                    return queue.take()
+                } catch (e: InterruptedException) {
+                    true
+                }
+            }
+        } finally {
+            if (interrupted) {
+                Thread.currentThread().interrupt()
+            }
+        }
+    }
+
+    /**
+     * Invokes `queue.`[put(element)][BlockingQueue.put]
+     * uninterruptibly.
+     */
+    private fun <E> putUninterruptibly(queue: BlockingQueue<E>, element: E) {
+        var interrupted = false
+        try {
+            while (true) {
+                interrupted = try {
+                    queue.put(element)
+                    return
+                } catch (e: InterruptedException) {
+                    true
+                }
+            }
+        } finally {
+            if (interrupted) {
+                Thread.currentThread().interrupt()
+            }
+        }
+    }
+}
diff --git a/camera/camera-viewfinder-core/src/main/java/androidx/camera/impl/utils/futures/FutureCallback.kt b/camera/camera-viewfinder-core/src/main/java/androidx/camera/impl/utils/futures/FutureCallback.kt
new file mode 100644
index 0000000..597ad91
--- /dev/null
+++ b/camera/camera-viewfinder-core/src/main/java/androidx/camera/impl/utils/futures/FutureCallback.kt
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2023 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.camera.impl.utils.futures
+
+import com.google.common.util.concurrent.ListenableFuture
+import java.util.concurrent.ExecutionException
+import java.util.concurrent.Future
+
+/**
+ * Cloned from concurrent-futures package in Guava to AndroidX namespace since we would need
+ * ListenableFuture related implementation but not want to include whole Guava library.
+ *
+ * A callback for accepting the results of a [Future] computation
+ * asynchronously.
+ *
+ *
+ * To attach to a [ListenableFuture] use [Futures.addCallback].
+ *
+ * @author Anthony Zana
+ * @since 10.0
+ * @param <V>
+</V> */
+interface FutureCallback<V> {
+    /** Invoked with the result of the `Future` computation when it is successful.  */
+    fun onSuccess(result: V?)
+
+    /**
+     * Invoked when a `Future` computation fails or is canceled.
+     *
+     *
+     * If the future's [get][Future.get] method throws an [ExecutionException], then
+     * the cause is passed to this method. Any other thrown object is passed unaltered.
+     */
+    fun onFailure(t: Throwable)
+}
diff --git a/camera/camera-viewfinder-core/src/main/java/androidx/camera/impl/utils/futures/FutureChain.kt b/camera/camera-viewfinder-core/src/main/java/androidx/camera/impl/utils/futures/FutureChain.kt
new file mode 100644
index 0000000..79af374
--- /dev/null
+++ b/camera/camera-viewfinder-core/src/main/java/androidx/camera/impl/utils/futures/FutureChain.kt
@@ -0,0 +1,136 @@
+/*
+ * Copyright 2023 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.camera.impl.utils.futures
+
+import androidx.arch.core.util.Function
+import androidx.concurrent.futures.CallbackToFutureAdapter
+import androidx.core.util.Preconditions
+import com.google.common.util.concurrent.ListenableFuture
+import java.util.concurrent.ExecutionException
+import java.util.concurrent.Executor
+import java.util.concurrent.TimeUnit
+import java.util.concurrent.TimeoutException
+
+/**
+ * A [ListenableFuture] that supports chains of operations. For example:
+ *
+ * <pre>`ListenableFuture<Boolean> adminIsLoggedIn =
+ * FutureChain.from(usersDatabase.getAdminUser())
+ * .transform(User::getId, directExecutor())
+ * .transform(ActivityService::isLoggedIn, threadPool);
+`</pre> *
+ * @param <V>
+</V> */
+
+open class FutureChain<V> : ListenableFuture<V> {
+    private val mDelegate: ListenableFuture<V>
+    private var mCompleter: CallbackToFutureAdapter.Completer<V>? = null
+
+    /**
+     * Returns a new `Future` whose result is derived from the result of this `Future`.
+     * If this input `Future` fails, the returned `Future` fails with the same
+     * exception (and the function is not invoked).
+     *
+     * @param function A Function to transform the results of this future to the results of the
+     * returned future.
+     * @param executor Executor to run the function in.
+     * @return A future that holds result of the transformation.
+     */
+    fun <T> transform(
+        function: Function<in V?, out T>,
+        executor: Executor
+    ): FutureChain<T> {
+        return Futures.transform(
+            this,
+            function,
+            executor
+        ) as FutureChain<T>
+    }
+
+    internal constructor(delegate: ListenableFuture<V>) {
+        mDelegate = Preconditions.checkNotNull(delegate)
+    }
+
+    internal constructor() {
+        mDelegate = CallbackToFutureAdapter.getFuture { completer ->
+            Preconditions.checkState(
+                mCompleter == null,
+                "The result can only set once!"
+            )
+            mCompleter = completer
+            "FutureChain[" + this@FutureChain + "]"
+        }
+    }
+
+    override fun addListener(listener: Runnable, executor: Executor) {
+        mDelegate.addListener(listener, executor)
+    }
+
+    override fun cancel(mayInterruptIfRunning: Boolean): Boolean {
+        return mDelegate.cancel(mayInterruptIfRunning)
+    }
+
+    override fun isCancelled(): Boolean {
+        return mDelegate.isCancelled
+    }
+
+    override fun isDone(): Boolean {
+        return mDelegate.isDone
+    }
+
+    @Throws(InterruptedException::class, ExecutionException::class)
+    override fun get(): V? {
+        return mDelegate.get()
+    }
+
+    @Throws(
+        InterruptedException::class,
+        ExecutionException::class,
+        TimeoutException::class
+    )
+    override fun get(timeout: Long, unit: TimeUnit): V? {
+        return mDelegate[timeout, unit]
+    }
+
+    fun set(value: V?): Boolean {
+        return if (mCompleter != null) {
+            mCompleter!!.set(value)
+        } else false
+    }
+
+    fun setException(throwable: Throwable): Boolean {
+        return if (mCompleter != null) {
+            mCompleter!!.setException(throwable)
+        } else false
+    }
+
+    companion object {
+        /**
+         * Converts the given `ListenableFuture` to an equivalent `FutureChain`.
+         *
+         *
+         * If the given `ListenableFuture` is already a `FutureChain`, it is returned
+         * directly. If not, it is wrapped in a `FutureChain` that delegates all calls to the
+         * original `ListenableFuture`.
+         *
+         * @return directly if input a FutureChain or a ListenableFuture wrapped by FutureChain.
+         */
+        fun <V> from(future: ListenableFuture<V>): FutureChain<V> {
+            return if (future is FutureChain<*>) future as FutureChain<V> else FutureChain(future)
+        }
+    }
+}
diff --git a/camera/camera-viewfinder-core/src/main/java/androidx/camera/impl/utils/futures/Futures.kt b/camera/camera-viewfinder-core/src/main/java/androidx/camera/impl/utils/futures/Futures.kt
new file mode 100644
index 0000000..5d6a6c4
--- /dev/null
+++ b/camera/camera-viewfinder-core/src/main/java/androidx/camera/impl/utils/futures/Futures.kt
@@ -0,0 +1,394 @@
+/*
+ * Copyright 2023 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.camera.impl.utils.futures
+
+import androidx.arch.core.util.Function
+import androidx.camera.impl.utils.executor.CameraExecutors
+import androidx.concurrent.futures.CallbackToFutureAdapter
+import androidx.core.util.Preconditions
+import com.google.common.util.concurrent.ListenableFuture
+import java.util.concurrent.CancellationException
+import java.util.concurrent.ExecutionException
+import java.util.concurrent.Executor
+import java.util.concurrent.Future
+import java.util.concurrent.ScheduledFuture
+
+/**
+ * Utility class for generating specific implementations of [ListenableFuture].
+ */
+object Futures {
+    /**
+     * Returns an implementation of [ListenableFuture] which immediately contains a result.
+     *
+     * @param value The result that is immediately set on the future.
+     * @param <V>   The type of the result.
+     * @return A future which immediately contains the result.
+    </V> */
+    private fun <V> immediateFuture(value: V?): ListenableFuture<V> {
+        return if (value == null) {
+            ImmediateFuture.nullFuture()
+        } else ImmediateFuture.ImmediateSuccessfulFuture(
+            value
+        )
+    }
+
+    /**
+     * Returns an implementation of [ScheduledFuture] which immediately contains an
+     * exception that will be thrown by [Future.get].
+     *
+     * @param cause The cause of the [ExecutionException] that will be thrown by
+     * [Future.get].
+     * @param <V>   The type of the result.
+     * @return A future which immediately contains an exception.
+    </V> */
+    fun <V> immediateFailedScheduledFuture(cause: Throwable): ScheduledFuture<V> {
+        return ImmediateFuture.ImmediateFailedScheduledFuture(
+            cause
+        )
+    }
+
+    /**
+     * Returns a new `Future` whose result is asynchronously derived from the result
+     * of the given `Future`. If the given `Future` fails, the returned `Future`
+     * fails with the same exception (and the function is not invoked).
+     *
+     * @param input    The future to transform
+     * @param function A function to transform the result of the input future to the result of the
+     * output future
+     * @param executor Executor to run the function in.
+     * @return A future that holds result of the function (if the input succeeded) or the original
+     * input's failure (if not)
+     */
+    fun <I, O> transformAsync(
+        input: ListenableFuture<I>,
+        function: AsyncFunction<in I, out O>,
+        executor: Executor
+    ): ListenableFuture<O> {
+        val output: ChainingListenableFuture<I, O> =
+            ChainingListenableFuture(
+                function,
+                input
+            )
+        input.addListener(output, executor)
+        return output
+    }
+
+    /**
+     * Returns a new `Future` whose result is derived from the result of the given `Future`. If `input` fails, the returned `Future` fails with the same
+     * exception (and the function is not invoked)
+     *
+     * @param input    The future to transform
+     * @param function A function to transform the results of the provided future to the results of
+     * the returned future.
+     * @param executor Executor to run the function in.
+     * @return A future that holds result of the transformation.
+     */
+    fun <I, O> transform(
+        input: ListenableFuture<I>,
+        function: Function<in I?, out O>,
+        executor: Executor
+    ): ListenableFuture<O> {
+        Preconditions.checkNotNull(function)
+        return transformAsync(
+            input,
+            { immediateFuture(function.apply(it)) },
+            executor
+        )
+    }
+
+    /**
+     * Propagates the result of the given `ListenableFuture` to the given [ ] directly.
+     *
+     *
+     * If `input` fails, the failure will be propagated to the `completer`.
+     *
+     * @param input     The future being propagated.
+     * @param completer The completer which will receive the result of the provided future.
+     */
+    // ListenableFuture not needed for SAM conversion
+    @JvmStatic
+    fun <V> propagate(
+        input: ListenableFuture<V>,
+        completer: CallbackToFutureAdapter.Completer<V>
+    ) {
+        // Use direct executor here since function is just unpacking the output and should be quick
+        propagateTransform(
+            input,
+            { functionInput -> functionInput },
+            completer,
+            CameraExecutors.directExecutor()
+        )
+    }
+
+    /**
+     * Propagates the result of the given `ListenableFuture` to the given [ ] by applying the provided transformation function.
+     *
+     *
+     * If `input` fails, the failure will be propagated to the `completer` (and the
+     * function is not invoked)
+     *
+     * @param input     The future to transform.
+     * @param function  A function to transform the results of the provided future to the results of
+     * the provided completer.
+     * @param completer The completer which will receive the result of the provided future.
+     * @param executor  Executor to run the function in.
+     */
+    private fun <I, O> propagateTransform(
+        input: ListenableFuture<I>,
+        function: Function<in I, out O>,
+        completer: CallbackToFutureAdapter.Completer<O>,
+        executor: Executor
+    ) {
+        propagateTransform(true, input, function, completer, executor)
+    }
+
+    /**
+     * Propagates the result of the given `ListenableFuture` to the given [ ] by applying the provided transformation function.
+     *
+     *
+     * If `input` fails, the failure will be propagated to the `completer` (and the
+     * function is not invoked)
+     *
+     * @param propagateCancellation `true` to propagate the cancellation from completer to
+     * input future.
+     * @param input                 The future to transform.
+     * @param function              A function to transform the results of the provided future to
+     * the results of the provided completer.
+     * @param completer             The completer which will receive the result of the provided
+     * future.
+     * @param executor              Executor to run the function in.
+     */
+    private fun <I, O> propagateTransform(
+        propagateCancellation: Boolean,
+        input: ListenableFuture<I>,
+        function: Function<in I, out O>,
+        completer: CallbackToFutureAdapter.Completer<O>,
+        executor: Executor
+    ) {
+        Preconditions.checkNotNull(input)
+        Preconditions.checkNotNull(function)
+        Preconditions.checkNotNull(completer)
+        Preconditions.checkNotNull(executor)
+        addCallback(
+            input,
+            object : FutureCallback<I> {
+                override fun onSuccess(result: I?) {
+                    try {
+                        completer.set(function.apply(result))
+                    } catch (t: Throwable) {
+                        completer.setException(t)
+                    }
+                }
+
+                override fun onFailure(t: Throwable) {
+                    completer.setException(t)
+                }
+            },
+            executor
+        )
+        if (propagateCancellation) {
+            // Propagate cancellation from completer to input future
+            completer.addCancellationListener(
+                { input.cancel(true) },
+                CameraExecutors.directExecutor()
+            )
+        }
+    }
+
+    /**
+     * Returns a `ListenableFuture` whose result is set from the supplied future when it
+     * completes.
+     *
+     *
+     * Cancelling the supplied future will also cancel the returned future, but
+     * cancelling the returned future will have no effect on the supplied future.
+     */
+    @JvmStatic
+    fun <V> nonCancellationPropagating(
+        future: ListenableFuture<V>
+    ): ListenableFuture<V> {
+        Preconditions.checkNotNull(
+            future
+        )
+        return if (future.isDone) {
+            future
+        } else CallbackToFutureAdapter.getFuture {
+            // Input of function is same as output
+            propagateTransform(
+                false, future, { input -> input }, it,
+                CameraExecutors.directExecutor()
+            )
+            "nonCancellationPropagating[$future]"
+        }
+    }
+
+    /**
+     * Creates a new `ListenableFuture` whose value is a list containing the values of all its
+     * successful input futures. The list of results is in the same order as the input list, and if
+     * any of the provided futures fails or is canceled, its corresponding position will contain
+     * `null` (which is indistinguishable from the future having a successful value of `null`).
+     *
+     *
+     * Canceling this future will attempt to cancel all the component futures.
+     *
+     * @param futures futures to combine
+     * @return a future that provides a list of the results of the component futures
+     */
+    fun <V> successfulAsList(
+        futures: Collection<ListenableFuture<out V>>
+    ): ListenableFuture<List<V?>?> {
+        return ListFuture(
+            futures.toList(), false,
+            CameraExecutors.directExecutor()
+        )
+    }
+
+    /**
+     * Creates a new `ListenableFuture` whose value is a list containing the values of all its
+     * input futures, if all succeed.
+     *
+     *
+     * The list of results is in the same order as the input list.
+     *
+     *
+     * Canceling this future will attempt to cancel all the component futures, and if any of the
+     * provided futures fails or is canceled, this one is, too.
+     *
+     * @param futures futures to combine
+     * @return a future that provides a list of the results of the component futures
+     */
+    fun <V> allAsList(
+        futures: Collection<ListenableFuture<out V>>
+    ): ListenableFuture<List<V?>?> {
+        return ListFuture(
+            futures.toList(),
+            true,
+            CameraExecutors.directExecutor()
+        )
+    }
+
+    /**
+     * Registers separate success and failure callbacks to be run when the `Future`'s
+     * computation is [complete][Future.isDone] or, if the
+     * computation is already complete, immediately.
+     *
+     * @param future   The future attach the callback to.
+     * @param callback The callback to invoke when `future` is completed.
+     * @param executor The executor to run `callback` when the future completes.
+     */
+    @JvmStatic
+    fun <V> addCallback(
+        future: ListenableFuture<V>,
+        callback: FutureCallback<in V>,
+        executor: Executor
+    ) {
+        Preconditions.checkNotNull(callback)
+        future.addListener(CallbackListener(future, callback), executor)
+    }
+
+    /**
+     * Returns the result of the input `Future`, which must have already completed.
+     *
+     *
+     * The benefits of this method are twofold. First, the name "getDone" suggests to readers
+     * that the `Future` is already done. Second, if buggy code calls `getDone` on a
+     * `Future` that is still pending, the program will throw instead of block.
+     *
+     * @throws ExecutionException    if the `Future` failed with an exception
+     * @throws CancellationException if the `Future` was cancelled
+     * @throws IllegalStateException if the `Future` is not done
+     */
+    @Throws(ExecutionException::class)
+    fun <V> getDone(future: Future<V>): V? {
+        /*
+         * We throw IllegalStateException, since the call could succeed later. Perhaps we
+         * "should" throw IllegalArgumentException, since the call could succeed with a different
+         * argument. Those exceptions' docs suggest that either is acceptable. Google's Java
+         * Practices page recommends IllegalArgumentException here, in part to keep its
+         * recommendation simple: Static methods should throw IllegalStateException only when
+         * they use static state.
+         *
+         * Why do we deviate here? The answer: We want for fluentFuture.getDone() to throw the same
+         * exception as Futures.getDone(fluentFuture).
+         */
+        Preconditions.checkState(
+            future.isDone,
+            "Future was expected to be done, $future"
+        )
+        return getUninterruptibly(future)
+    }
+
+    /**
+     * Invokes `Future.`[get()][Future.get] uninterruptibly.
+     *
+     * @throws ExecutionException    if the computation threw an exception
+     * @throws CancellationException if the computation was cancelled
+     */
+    @Throws(ExecutionException::class)
+    fun <V> getUninterruptibly(future: Future<V>): V {
+        var interrupted = false
+        try {
+            while (true) {
+                interrupted = try {
+                    return future.get()
+                } catch (e: InterruptedException) {
+                    true
+                }
+            }
+        } finally {
+            if (interrupted) {
+                Thread.currentThread().interrupt()
+            }
+        }
+    }
+
+    /**
+     * See [.addCallback] for behavioral notes.
+     */
+    private class CallbackListener<V>(
+        val mFuture: Future<V>,
+        callback: FutureCallback<in V>
+    ) :
+        Runnable {
+        val mCallback: FutureCallback<in V>
+
+        init {
+            mCallback = callback
+        }
+
+        override fun run() {
+            val value: V?
+            try {
+                value = getDone(mFuture)
+            } catch (e: ExecutionException) {
+                e.cause?.let { mCallback.onFailure(it) }
+                return
+            } catch (e: RuntimeException) {
+                mCallback.onFailure(e)
+                return
+            } catch (e: Error) {
+                mCallback.onFailure(e)
+                return
+            }
+            mCallback.onSuccess(value)
+        }
+
+        override fun toString(): String {
+            return javaClass.getSimpleName() + "," + mCallback
+        }
+    }
+}
diff --git a/camera/camera-viewfinder-core/src/main/java/androidx/camera/impl/utils/futures/ImmediateFuture.kt b/camera/camera-viewfinder-core/src/main/java/androidx/camera/impl/utils/futures/ImmediateFuture.kt
new file mode 100644
index 0000000..7cd3aa8
--- /dev/null
+++ b/camera/camera-viewfinder-core/src/main/java/androidx/camera/impl/utils/futures/ImmediateFuture.kt
@@ -0,0 +1,121 @@
+/*
+ * Copyright 2023 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.camera.impl.utils.futures
+
+import androidx.camera.impl.utils.Logger
+import androidx.core.util.Preconditions
+import com.google.common.util.concurrent.ListenableFuture
+import java.util.concurrent.Delayed
+import java.util.concurrent.ExecutionException
+import java.util.concurrent.Executor
+import java.util.concurrent.ScheduledFuture
+import java.util.concurrent.TimeUnit
+
+/**
+ * An implementation of [ListenableFuture] which immediately contains a result.
+ *
+ *
+ * This implementation is based off of the Guava ImmediateSuccessfulFuture class.
+ * @param <V> The type of the value stored in the future.
+</V> */
+internal abstract class ImmediateFuture<V> : ListenableFuture<V> {
+    override fun addListener(listener: Runnable, executor: Executor) {
+        Preconditions.checkNotNull(listener)
+        Preconditions.checkNotNull(executor)
+        try {
+            executor.execute(listener)
+        } catch (e: RuntimeException) {
+            // ListenableFuture does not throw runtime exceptions, so swallow the exception and
+            // log it here.
+            Logger.e(
+                TAG, "Experienced RuntimeException while attempting to notify " +
+                    listener + " on Executor " + executor, e
+            )
+        }
+    }
+
+    override fun cancel(mayInterruptIfRunning: Boolean): Boolean {
+        return false
+    }
+
+    override fun isCancelled(): Boolean {
+        return false
+    }
+
+    override fun isDone(): Boolean {
+        return true
+    }
+
+    @Throws(ExecutionException::class)
+    abstract override fun get(): V?
+
+    @Throws(ExecutionException::class)
+    override fun get(timeout: Long, unit: TimeUnit): V? {
+        Preconditions.checkNotNull(unit)
+        return get()
+    }
+
+    internal class ImmediateSuccessfulFuture<V>(private val mValue: V?) : ImmediateFuture<V>() {
+        override fun get(): V? {
+            return mValue
+        }
+
+        override fun toString(): String {
+            // Behaviour analogous to AbstractResolvableFuture#toString().
+            return super.toString() + "[status=SUCCESS, result=[" + mValue + "]]"
+        }
+    }
+
+    internal open class ImmediateFailedFuture<V>(private val mCause: Throwable) :
+        ImmediateFuture<V>() {
+        @Throws(ExecutionException::class)
+        override fun get(): V? {
+            throw ExecutionException(mCause)
+        }
+
+        override fun toString(): String {
+            // Behaviour analogous to AbstractResolvableFuture#toString().
+            return super.toString() + "[status=FAILURE, cause=[" + mCause + "]]"
+        }
+    }
+
+    internal class ImmediateFailedScheduledFuture<V>(cause: Throwable) :
+        ImmediateFailedFuture<V>(cause), ScheduledFuture<V> {
+        override fun getDelay(timeUnit: TimeUnit): Long {
+            return 0
+        }
+
+        override fun compareTo(other: Delayed): Int {
+            return -1
+        }
+    }
+
+    companion object {
+        private const val TAG = "ImmediateFuture"
+
+        /**
+         * Returns a future that contains a null value.
+         *
+         *
+         * This should be used any time a null value is needed as it uses a static ListenableFuture
+         * that contains null, and thus will not allocate.
+         */
+        fun <V> nullFuture(): ListenableFuture<V> {
+            return ImmediateSuccessfulFuture(null)
+        }
+    }
+}
diff --git a/camera/camera-viewfinder-core/src/main/java/androidx/camera/impl/utils/futures/ListFuture.kt b/camera/camera-viewfinder-core/src/main/java/androidx/camera/impl/utils/futures/ListFuture.kt
new file mode 100644
index 0000000..cc83d93
--- /dev/null
+++ b/camera/camera-viewfinder-core/src/main/java/androidx/camera/impl/utils/futures/ListFuture.kt
@@ -0,0 +1,244 @@
+/*
+ * Copyright 2023 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.camera.impl.utils.futures
+
+import androidx.camera.impl.utils.executor.CameraExecutors
+import androidx.concurrent.futures.CallbackToFutureAdapter
+import androidx.core.util.Preconditions
+import com.google.common.util.concurrent.ListenableFuture
+import java.util.concurrent.CancellationException
+import java.util.concurrent.ExecutionException
+import java.util.concurrent.Executor
+import java.util.concurrent.Future
+import java.util.concurrent.TimeUnit
+import java.util.concurrent.TimeoutException
+import java.util.concurrent.atomic.AtomicInteger
+
+/**
+ * The Class is based on the ListFuture in Guava and to use the CallbackToFutureAdapter instead
+ * of the AbstractFuture.
+ *
+ * Class that implements [Futures.allAsList] and
+ * [Futures.successfulAsList].
+ * The idea is to create a (null-filled) List and register a listener with
+ * each component future to fill out the value in the List when that future
+ * completes.
+
+ * @param futures          all the futures to build the list from
+ * @param allMustSucceed   whether a single failure or cancellation should
+ * propagate to this future
+ * @param listenerExecutor used to run listeners on all the passed in futures.
+ */
+
+internal class ListFuture<V>(
+    private var futures: List<ListenableFuture<out V>>,
+    private val allMustSucceed: Boolean,
+    listenerExecutor: Executor
+) :
+    ListenableFuture<List<V?>?> {
+    private var futuresInternal: List<ListenableFuture<out V>>?
+    private var values: MutableList<V?>?
+    private val remaining: AtomicInteger
+    private val result: ListenableFuture<List<V?>?>
+    var resultNotifier: CallbackToFutureAdapter.Completer<List<V?>?>? = null
+
+    init {
+        futuresInternal = futures
+        values = ArrayList(futures.size)
+        remaining = AtomicInteger(futures.size)
+        result = CallbackToFutureAdapter.getFuture(
+            object : CallbackToFutureAdapter.Resolver<List<V?>?> {
+                override fun attachCompleter(
+                    completer: CallbackToFutureAdapter.Completer<List<V?>?>
+                ): Any {
+                    Preconditions.checkState(
+                        resultNotifier == null,
+                        "The result can only set once!"
+                    )
+                    resultNotifier = completer
+                    return "ListFuture[$this]"
+                }
+            })
+        init(listenerExecutor)
+    }
+
+    private fun init(listenerExecutor: Executor) {
+        // First, schedule cleanup to execute when the Future is done.
+        addListener({ // By now the values array has either been set as the Future's value,
+            // or (in case of failure) is no longer useful.
+            values = null
+
+            // Let go of the memory held by other futuresInternal
+            futuresInternal = null
+        }, CameraExecutors.directExecutor())
+
+        // Now begin the "real" initialization.
+
+        // Corner case: List is empty.
+        if (futuresInternal!!.isEmpty()) {
+            resultNotifier!!.set(ArrayList(values!!))
+            return
+        }
+
+        // Populate the results list with null initially.
+        for (i in futuresInternal!!.indices) {
+            values!!.add(null)
+        }
+
+        // Register a listener on each Future in the list to update
+        // the state of this future.
+        // Note that if all the futuresInternal on the list are done prior to completing
+        // this loop, the last call to addListener() will callback to
+        // setOneValue(), transitively call our cleanup listener, and set
+        // futuresInternal to null.
+        // We store a reference to futuresInternal to avoid the NPE.
+        val localFutures = futuresInternal
+        for (i in localFutures!!.indices) {
+            val listenable = localFutures[i]
+            listenable.addListener({ setOneValue(i, listenable) }, listenerExecutor)
+        }
+    }
+
+    /**
+     * Sets the value at the given index to that of the given future.
+     */
+    private fun setOneValue(index: Int, future: Future<out V>) {
+        var localValues = values
+        if (isDone || localValues == null) {
+            // Some other future failed or has been cancelled, causing this one to
+            // also be cancelled or have an exception set. This should only happen
+            // if mAllMustSucceed is true.
+            Preconditions.checkState(
+                this.allMustSucceed,
+                "Future was done before all dependencies completed"
+            )
+            return
+        }
+        try {
+            Preconditions.checkState(
+                future.isDone,
+                "Tried to set value from future which is not done"
+            )
+            localValues[index] =
+                Futures.getUninterruptibly(future)
+        } catch (e: CancellationException) {
+            if (this.allMustSucceed) {
+                // Set ourselves as cancelled. Let the input futures keep running
+                // as some of them may be used elsewhere.
+                // (Currently we don't override interruptTask, so
+                // mayInterruptIfRunning==false isn't technically necessary.)
+                cancel(false)
+            }
+        } catch (e: ExecutionException) {
+            if (this.allMustSucceed) {
+                // As soon as the first one fails, throw the exception up.
+                // The result of all other inputs is then ignored.
+                resultNotifier!!.setException(e.cause!!)
+            }
+        } catch (e: RuntimeException) {
+            if (this.allMustSucceed) {
+                resultNotifier!!.setException(e)
+            }
+        } catch (e: Error) {
+            // Propagate errors up ASAP - our superclass will rethrow the error
+            resultNotifier!!.setException(e)
+        } finally {
+            val newRemaining = remaining.decrementAndGet()
+            Preconditions.checkState(newRemaining >= 0, "Less than 0 remaining futures")
+            if (newRemaining == 0) {
+                localValues = values
+                if (localValues != null) {
+                    resultNotifier!!.set(ArrayList(localValues))
+                } else {
+                    Preconditions.checkState(isDone)
+                }
+            }
+        }
+    }
+
+    override fun addListener(listener: Runnable, executor: Executor) {
+        result.addListener(listener, executor)
+    }
+
+    override fun cancel(mayInterruptIfRunning: Boolean): Boolean {
+        if (futuresInternal != null) {
+            for (f in futuresInternal!!) {
+                f.cancel(mayInterruptIfRunning)
+            }
+        }
+        return result.cancel(mayInterruptIfRunning)
+    }
+
+    override fun isCancelled(): Boolean {
+        return result.isCancelled
+    }
+
+    override fun isDone(): Boolean {
+        return result.isDone
+    }
+
+    @Throws(InterruptedException::class, ExecutionException::class)
+    override fun get(): List<V?>? {
+        callAllGets()
+
+        // This may still block in spite of the calls above, as the listeners may
+        // be scheduled for execution in other threads.
+        return result.get()
+    }
+
+    @Throws(
+        InterruptedException::class,
+        ExecutionException::class,
+        TimeoutException::class
+    )
+    override fun get(timeout: Long, unit: TimeUnit): List<V?>? {
+        return result[timeout, unit]
+    }
+
+    /**
+     * Calls the get method of all dependency futures to work around a bug in
+     * some ListenableFutures where the listeners aren't called until get() is
+     * called.
+     */
+    @Throws(InterruptedException::class)
+    private fun callAllGets() {
+        val oldFutures = futuresInternal
+        if (oldFutures != null && !isDone) {
+            for (future in oldFutures) {
+                // We wait for a little while for the future, but if it's not done,
+                // we check that no other futures caused a cancellation or failure.
+                // This can introduce a delay of up to 10ms in reporting an exception.
+                while (!future.isDone) {
+                    try {
+                        future.get()
+                    } catch (e: Error) {
+                        throw e
+                    } catch (e: InterruptedException) {
+                        throw e
+                    } catch (e: Throwable) {
+                        // ExecutionException / CancellationException / RuntimeException
+                        if (this.allMustSucceed) {
+                            return
+                        } else {
+                            continue
+                        }
+                    }
+                }
+            }
+        }
+    }
+}
diff --git a/camera/camera-viewfinder/build.gradle b/camera/camera-viewfinder/build.gradle
index f1bd1d8..2f498f2 100644
--- a/camera/camera-viewfinder/build.gradle
+++ b/camera/camera-viewfinder/build.gradle
@@ -35,6 +35,7 @@
     implementation("androidx.test.espresso:espresso-idling-resource:3.1.0")
     implementation(libs.kotlinCoroutinesCore)
     implementation(libs.kotlinCoroutinesAndroid)
+    implementation project(':camera:camera-viewfinder-core')
 
     annotationProcessor(libs.autoValue)
 
diff --git a/camera/camera-viewfinder/src/androidTest/java/androidx/camera/viewfinder/CameraViewfinderBitmapTest.kt b/camera/camera-viewfinder/src/androidTest/java/androidx/camera/viewfinder/CameraViewfinderBitmapTest.kt
index 25b8313..20d606e 100644
--- a/camera/camera-viewfinder/src/androidTest/java/androidx/camera/viewfinder/CameraViewfinderBitmapTest.kt
+++ b/camera/camera-viewfinder/src/androidTest/java/androidx/camera/viewfinder/CameraViewfinderBitmapTest.kt
@@ -21,9 +21,9 @@
 import android.hardware.camera2.CameraManager
 import android.util.Size
 import android.view.Surface
+import androidx.camera.impl.utils.futures.FutureCallback
+import androidx.camera.impl.utils.futures.Futures
 import androidx.camera.viewfinder.CameraViewfinder.ScaleType.FILL_CENTER
-import androidx.camera.viewfinder.internal.utils.futures.FutureCallback
-import androidx.camera.viewfinder.internal.utils.futures.Futures
 import androidx.camera.viewfinder.utils.CoreAppTestUtil
 import androidx.camera.viewfinder.utils.FakeActivity
 import androidx.core.content.ContextCompat
@@ -94,13 +94,13 @@
 
         // assert
         runOnMainThread(Runnable {
-            val surfaceListenableFuture: ListenableFuture<Surface> =
+            val surfaceListenableFuture: ListenableFuture<Surface?> =
                 viewfinder.requestSurfaceAsync(mSurfaceRequest)
 
             Futures.addCallback<Surface?>(
                 surfaceListenableFuture,
                 object : FutureCallback<Surface?> {
-                    override fun onSuccess(surface: Surface?) {
+                    override fun onSuccess(result: Surface?) {
                         val bitmap: Bitmap? = viewfinder.getBitmap()
                         Truth.assertThat(bitmap).isNotNull()
                         Truth.assertThat(bitmap?.width).isNotEqualTo(0)
@@ -123,13 +123,13 @@
 
         // assert
         runOnMainThread(Runnable {
-            val surfaceListenableFuture: ListenableFuture<Surface> =
+            val surfaceListenableFuture: ListenableFuture<Surface?> =
                 viewfinder.requestSurfaceAsync(mSurfaceRequest)
 
             Futures.addCallback<Surface?>(
                 surfaceListenableFuture,
                 object : FutureCallback<Surface?> {
-                    override fun onSuccess(surface: Surface?) {
+                    override fun onSuccess(result: Surface?) {
                         val bitmap: Bitmap? = viewfinder.getBitmap()
                         Truth.assertThat(bitmap).isNotNull()
                         Truth.assertThat(bitmap?.width).isNotEqualTo(0)
diff --git a/camera/camera-viewfinder/src/main/java/androidx/camera/viewfinder/TextureViewImplementation.java b/camera/camera-viewfinder/src/main/java/androidx/camera/viewfinder/TextureViewImplementation.java
index 583ec1b..e45a70d 100644
--- a/camera/camera-viewfinder/src/main/java/androidx/camera/viewfinder/TextureViewImplementation.java
+++ b/camera/camera-viewfinder/src/main/java/androidx/camera/viewfinder/TextureViewImplementation.java
@@ -26,11 +26,11 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.annotation.RequiresApi;
+import androidx.camera.impl.utils.executor.CameraExecutors;
+import androidx.camera.impl.utils.futures.FutureCallback;
+import androidx.camera.impl.utils.futures.Futures;
 import androidx.camera.viewfinder.ViewfinderSurfaceRequest.Result;
 import androidx.camera.viewfinder.internal.utils.Logger;
-import androidx.camera.viewfinder.internal.utils.executor.CameraExecutors;
-import androidx.camera.viewfinder.internal.utils.futures.FutureCallback;
-import androidx.camera.viewfinder.internal.utils.futures.Futures;
 import androidx.concurrent.futures.CallbackToFutureAdapter;
 import androidx.core.content.ContextCompat;
 import androidx.core.util.Consumer;
diff --git a/camera/camera-viewfinder/src/main/java/androidx/camera/viewfinder/ViewfinderSurfaceRequest.java b/camera/camera-viewfinder/src/main/java/androidx/camera/viewfinder/ViewfinderSurfaceRequest.java
index 49b2ae6..9eace4a 100644
--- a/camera/camera-viewfinder/src/main/java/androidx/camera/viewfinder/ViewfinderSurfaceRequest.java
+++ b/camera/camera-viewfinder/src/main/java/androidx/camera/viewfinder/ViewfinderSurfaceRequest.java
@@ -35,12 +35,12 @@
 import androidx.annotation.RequiresApi;
 import androidx.annotation.RestrictTo;
 import androidx.annotation.RestrictTo.Scope;
+import androidx.camera.impl.utils.executor.CameraExecutors;
+import androidx.camera.impl.utils.futures.FutureCallback;
+import androidx.camera.impl.utils.futures.Futures;
 import androidx.camera.viewfinder.CameraViewfinder.ImplementationMode;
 import androidx.camera.viewfinder.internal.surface.ViewfinderSurface;
 import androidx.camera.viewfinder.internal.utils.Logger;
-import androidx.camera.viewfinder.internal.utils.executor.CameraExecutors;
-import androidx.camera.viewfinder.internal.utils.futures.FutureCallback;
-import androidx.camera.viewfinder.internal.utils.futures.Futures;
 import androidx.concurrent.futures.CallbackToFutureAdapter;
 import androidx.core.util.Consumer;
 import androidx.core.util.Preconditions;
diff --git a/camera/camera-viewfinder/src/main/java/androidx/camera/viewfinder/internal/surface/ViewfinderSurface.java b/camera/camera-viewfinder/src/main/java/androidx/camera/viewfinder/internal/surface/ViewfinderSurface.java
index 49d9fc4..24423ac 100644
--- a/camera/camera-viewfinder/src/main/java/androidx/camera/viewfinder/internal/surface/ViewfinderSurface.java
+++ b/camera/camera-viewfinder/src/main/java/androidx/camera/viewfinder/internal/surface/ViewfinderSurface.java
@@ -24,8 +24,8 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.annotation.RequiresApi;
+import androidx.camera.impl.utils.futures.Futures;
 import androidx.camera.viewfinder.internal.utils.Logger;
-import androidx.camera.viewfinder.internal.utils.futures.Futures;
 import androidx.concurrent.futures.CallbackToFutureAdapter;
 
 import com.google.common.util.concurrent.ListenableFuture;
diff --git a/camera/camera-viewfinder/src/main/java/androidx/camera/viewfinder/internal/utils/executor/CameraExecutors.java b/camera/camera-viewfinder/src/main/java/androidx/camera/viewfinder/internal/utils/executor/CameraExecutors.java
deleted file mode 100644
index 23dbce8..0000000
--- a/camera/camera-viewfinder/src/main/java/androidx/camera/viewfinder/internal/utils/executor/CameraExecutors.java
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright 2022 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.camera.viewfinder.internal.utils.executor;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.RequiresApi;
-
-import java.util.concurrent.Executor;
-import java.util.concurrent.ScheduledExecutorService;
-
-/**
- * Utility class for generating specific implementations of {@link Executor}.
- */
-@RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
-public final class CameraExecutors {
-
-    // Should not be instantiated
-    private CameraExecutors() {
-    }
-
-    /** Returns a cached {@link ScheduledExecutorService} which posts to the main thread. */
-    @NonNull
-    public static ScheduledExecutorService mainThreadExecutor() {
-        return MainThreadExecutor.getInstance();
-    }
-
-    /** Returns a cached executor that runs tasks directly from the calling thread. */
-    @NonNull
-    public static Executor directExecutor() {
-        return DirectExecutor.getInstance();
-    }
-}
diff --git a/camera/camera-viewfinder/src/main/java/androidx/camera/viewfinder/internal/utils/executor/DirectExecutor.java b/camera/camera-viewfinder/src/main/java/androidx/camera/viewfinder/internal/utils/executor/DirectExecutor.java
deleted file mode 100644
index dafe1d2..0000000
--- a/camera/camera-viewfinder/src/main/java/androidx/camera/viewfinder/internal/utils/executor/DirectExecutor.java
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * Copyright 2022 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.camera.viewfinder.internal.utils.executor;
-
-import androidx.annotation.RequiresApi;
-
-import java.util.concurrent.Executor;
-
-/**
- * An {@link Executor} that runs each task in the thread that invokes {@link Executor#execute
- * execute}.
- */
-@RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
-final class DirectExecutor implements Executor {
-    private static volatile DirectExecutor sDirectExecutor;
-
-    static Executor getInstance() {
-        if (sDirectExecutor != null) {
-            return sDirectExecutor;
-        }
-        synchronized (DirectExecutor.class) {
-            if (sDirectExecutor == null) {
-                sDirectExecutor = new DirectExecutor();
-            }
-        }
-
-        return sDirectExecutor;
-    }
-
-    @Override
-    public void execute(Runnable command) {
-        command.run();
-    }
-}
diff --git a/camera/camera-viewfinder/src/main/java/androidx/camera/viewfinder/internal/utils/executor/HandlerScheduledExecutorService.java b/camera/camera-viewfinder/src/main/java/androidx/camera/viewfinder/internal/utils/executor/HandlerScheduledExecutorService.java
deleted file mode 100644
index 2b13fec..0000000
--- a/camera/camera-viewfinder/src/main/java/androidx/camera/viewfinder/internal/utils/executor/HandlerScheduledExecutorService.java
+++ /dev/null
@@ -1,278 +0,0 @@
-/*
- * Copyright 2022 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.camera.viewfinder.internal.utils.executor;
-
-import android.os.Handler;
-import android.os.Looper;
-import android.os.SystemClock;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.RequiresApi;
-import androidx.camera.viewfinder.internal.utils.futures.Futures;
-import androidx.concurrent.futures.CallbackToFutureAdapter;
-
-import com.google.common.util.concurrent.ListenableFuture;
-
-import java.util.List;
-import java.util.concurrent.AbstractExecutorService;
-import java.util.concurrent.Callable;
-import java.util.concurrent.Delayed;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.RejectedExecutionException;
-import java.util.concurrent.RunnableScheduledFuture;
-import java.util.concurrent.ScheduledExecutorService;
-import java.util.concurrent.ScheduledFuture;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.TimeoutException;
-import java.util.concurrent.atomic.AtomicReference;
-
-/**
- * An implementation of {@link ScheduledExecutorService} which delegates all scheduled task to
- * the given {@link Handler}.
- *
- * <p>Currently, can only be used to schedule future non-repeating tasks.
- */
-@RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
-final class HandlerScheduledExecutorService extends AbstractExecutorService implements
-        ScheduledExecutorService {
-
-    private static ThreadLocal<ScheduledExecutorService> sThreadLocalInstance =
-            new ThreadLocal<ScheduledExecutorService>() {
-                @Override
-                public ScheduledExecutorService initialValue() {
-                    if (Looper.myLooper() == Looper.getMainLooper()) {
-                        return CameraExecutors.mainThreadExecutor();
-                    } else if (Looper.myLooper() != null) {
-                        Handler handler = new Handler(Looper.myLooper());
-                        return new HandlerScheduledExecutorService(handler);
-                    }
-
-                    return null;
-                }
-            };
-
-    private final Handler mHandler;
-
-    HandlerScheduledExecutorService(@NonNull Handler handler) {
-        mHandler = handler;
-    }
-
-    /**
-     * Retrieves a cached executor derived from the current thread's looper.
-     */
-    static ScheduledExecutorService currentThreadExecutor() {
-        ScheduledExecutorService executor = sThreadLocalInstance.get();
-        if (executor == null) {
-            Looper looper = Looper.myLooper();
-            if (looper == null) {
-                throw new IllegalStateException("Current thread has no looper!");
-            }
-
-            executor = new HandlerScheduledExecutorService(new Handler(looper));
-            sThreadLocalInstance.set(executor);
-        }
-
-        return executor;
-    }
-
-    @Override
-    public ScheduledFuture<?> schedule(
-            @NonNull final Runnable command,
-            long delay,
-            @NonNull TimeUnit unit) {
-        Callable<Void> wrapper = new Callable<Void>() {
-            @Override
-            public Void call() {
-                command.run();
-                return null;
-            }
-        };
-        return schedule(wrapper, delay, unit);
-    }
-
-    @Override
-    @NonNull
-    public <V> ScheduledFuture<V> schedule(
-            @NonNull Callable<V> callable,
-            long delay,
-            @NonNull TimeUnit unit) {
-        long runAtMillis = SystemClock.uptimeMillis() + TimeUnit.MILLISECONDS.convert(delay, unit);
-        HandlerScheduledFuture<V> future = new HandlerScheduledFuture<>(mHandler, runAtMillis,
-                callable);
-        if (mHandler.postAtTime(future, runAtMillis)) {
-            return future;
-        }
-
-        return Futures.immediateFailedScheduledFuture(createPostFailedException());
-    }
-
-    @Override
-    @NonNull
-    public ScheduledFuture<?> scheduleAtFixedRate(
-            @NonNull Runnable command,
-            long initialDelay,
-            long period,
-            @NonNull TimeUnit unit) {
-        throw new UnsupportedOperationException(
-                HandlerScheduledExecutorService.class.getSimpleName()
-                        + " does not yet support fixed-rate scheduling.");
-    }
-
-    @Override
-    @NonNull
-    public ScheduledFuture<?> scheduleWithFixedDelay(@NonNull Runnable command, long initialDelay,
-            long delay, @NonNull TimeUnit unit) {
-        throw new UnsupportedOperationException(
-                HandlerScheduledExecutorService.class.getSimpleName()
-                        + " does not yet support fixed-delay scheduling.");
-    }
-
-    @Override
-    public void shutdown() {
-        throw new UnsupportedOperationException(
-                HandlerScheduledExecutorService.class.getSimpleName()
-                        + " cannot be shut down. Use Looper.quitSafely().");
-    }
-
-    @Override
-    @NonNull
-    public List<Runnable> shutdownNow() {
-        throw new UnsupportedOperationException(
-                HandlerScheduledExecutorService.class.getSimpleName()
-                        + " cannot be shut down. Use Looper.quitSafely().");
-    }
-
-    @Override
-    public boolean isShutdown() {
-        return false;
-    }
-
-    @Override
-    public boolean isTerminated() {
-        return false;
-    }
-
-    @Override
-    public boolean awaitTermination(long timeout, @NonNull TimeUnit unit) {
-        throw new UnsupportedOperationException(
-                HandlerScheduledExecutorService.class.getSimpleName()
-                        + " cannot be shut down. Use Looper.quitSafely().");
-    }
-
-    @Override
-    public void execute(@NonNull Runnable command) {
-        if (!mHandler.post(command)) {
-            throw createPostFailedException();
-        }
-    }
-
-    private RejectedExecutionException createPostFailedException() {
-        return new RejectedExecutionException(mHandler + " is shutting down");
-    }
-
-    private static class HandlerScheduledFuture<V> implements RunnableScheduledFuture<V> {
-
-        final AtomicReference<CallbackToFutureAdapter.Completer<V>>
-                mCompleter = new AtomicReference<>(null);
-        private final long mRunAtMillis;
-        private final Callable<V> mTask;
-        private final ListenableFuture<V> mDelegate;
-
-        HandlerScheduledFuture(final Handler handler, long runAtMillis, final Callable<V> task) {
-            mRunAtMillis = runAtMillis;
-            mTask = task;
-            mDelegate = CallbackToFutureAdapter.getFuture(
-                    new CallbackToFutureAdapter.Resolver<V>() {
-
-                        @Override
-                        public Object attachCompleter(
-                                @NonNull CallbackToFutureAdapter.Completer<V> completer) throws
-                                RejectedExecutionException {
-
-                            completer.addCancellationListener(new Runnable() {
-                                @Override
-                                public void run() {
-                                    // Remove the completer if we're cancelled so the task won't
-                                    // run.
-                                    if (mCompleter.getAndSet(null) != null) {
-                                        handler.removeCallbacks(HandlerScheduledFuture.this);
-                                    }
-                                }
-                            }, CameraExecutors.directExecutor());
-
-                            mCompleter.set(completer);
-                            return "HandlerScheduledFuture-" + task.toString();
-                        }
-                    });
-        }
-
-        @Override
-        public boolean isPeriodic() {
-            return false;
-        }
-
-        @Override
-        public long getDelay(TimeUnit unit) {
-            return unit.convert(mRunAtMillis - System.currentTimeMillis(),
-                    TimeUnit.MILLISECONDS);
-        }
-
-        @Override
-        public int compareTo(Delayed o) {
-            return Long.compare(getDelay(TimeUnit.MILLISECONDS), o.getDelay(TimeUnit.MILLISECONDS));
-        }
-
-        @Override
-        public void run() {
-            // If completer is null, it has already run or is cancelled.
-            CallbackToFutureAdapter.Completer<V> completer = mCompleter.getAndSet(null);
-            if (completer != null) {
-                try {
-                    completer.set(mTask.call());
-                } catch (Exception e) {
-                    completer.setException(e);
-                }
-            }
-        }
-
-        @Override
-        public boolean cancel(boolean mayInterruptIfRunning) {
-            return mDelegate.cancel(mayInterruptIfRunning);
-        }
-
-        @Override
-        public boolean isCancelled() {
-            return mDelegate.isCancelled();
-        }
-
-        @Override
-        public boolean isDone() {
-            return mDelegate.isDone();
-        }
-
-        @Override
-        public V get() throws ExecutionException, InterruptedException {
-            return mDelegate.get();
-        }
-
-        @Override
-        public V get(long timeout, @NonNull TimeUnit unit)
-                throws ExecutionException, InterruptedException, TimeoutException {
-            return mDelegate.get(timeout, unit);
-        }
-    }
-}
diff --git a/camera/camera-viewfinder/src/main/java/androidx/camera/viewfinder/internal/utils/executor/MainThreadExecutor.java b/camera/camera-viewfinder/src/main/java/androidx/camera/viewfinder/internal/utils/executor/MainThreadExecutor.java
deleted file mode 100644
index 1f91ab4..0000000
--- a/camera/camera-viewfinder/src/main/java/androidx/camera/viewfinder/internal/utils/executor/MainThreadExecutor.java
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * Copyright 2022 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.camera.viewfinder.internal.utils.executor;
-
-import android.os.Handler;
-import android.os.Looper;
-
-import androidx.annotation.RequiresApi;
-
-import java.util.concurrent.Executor;
-import java.util.concurrent.ScheduledExecutorService;
-
-/**
- * Helper class for retrieving an {@link ScheduledExecutorService} which will post to the main
- * thread.
- *
- * <p>Since {@link ScheduledExecutorService} implements {@link Executor}, this can also be used
- * as a simple Executor.
- */
-@RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
-final class MainThreadExecutor {
-    private static volatile ScheduledExecutorService sInstance;
-
-    private MainThreadExecutor() {
-    }
-
-    static ScheduledExecutorService getInstance() {
-        if (sInstance != null) {
-            return sInstance;
-        }
-        synchronized (MainThreadExecutor.class) {
-            if (sInstance == null) {
-                sInstance = new HandlerScheduledExecutorService(
-                        new Handler(Looper.getMainLooper()));
-            }
-        }
-
-        return sInstance;
-    }
-}
diff --git a/camera/camera-viewfinder/src/main/java/androidx/camera/viewfinder/internal/utils/futures/AsyncFunction.java b/camera/camera-viewfinder/src/main/java/androidx/camera/viewfinder/internal/utils/futures/AsyncFunction.java
deleted file mode 100644
index 182384d..0000000
--- a/camera/camera-viewfinder/src/main/java/androidx/camera/viewfinder/internal/utils/futures/AsyncFunction.java
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * Copyright 2022 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.camera.viewfinder.internal.utils.futures;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.annotation.RequiresApi;
-
-import com.google.common.util.concurrent.ListenableFuture;
-
-import java.util.concurrent.Executor;
-import java.util.concurrent.Future;
-
-/**
- * Cloned from concurrent-futures package in Guava to AndroidX namespace since we would need
- * ListenableFuture related implementation but not want to include whole Guava library.
- *
- * Transforms a value, possibly asynchronously. For an example usage and more information, see
- * {@link Futures#transformAsync(ListenableFuture, AsyncFunction, Executor)}.
- *
- * @author Chris Povirk
- * @since 11.0
- * @param <I>
- * @param <O>
- *
- */
-@FunctionalInterface
-@RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
-public interface AsyncFunction<I, O> {
-    /**
-     * Returns an output {@code Future} to use in place of the given {@code input}. The output
-     * {@code Future} need not be {@linkplain Future#isDone done}, making {@code AsyncFunction}
-     * suitable for asynchronous derivations.
-     *
-     * <p>Throwing an exception from this method is equivalent to returning a failing {@code
-     * Future}.
-     */
-    @NonNull
-    ListenableFuture<O> apply(@Nullable I input) throws Exception;
-}
diff --git a/camera/camera-viewfinder/src/main/java/androidx/camera/viewfinder/internal/utils/futures/ChainingListenableFuture.java b/camera/camera-viewfinder/src/main/java/androidx/camera/viewfinder/internal/utils/futures/ChainingListenableFuture.java
deleted file mode 100644
index 0e40d98..0000000
--- a/camera/camera-viewfinder/src/main/java/androidx/camera/viewfinder/internal/utils/futures/ChainingListenableFuture.java
+++ /dev/null
@@ -1,297 +0,0 @@
-/*
- * Copyright 2022 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.camera.viewfinder.internal.utils.futures;
-
-import static androidx.camera.viewfinder.internal.utils.futures.Futures.getUninterruptibly;
-import static androidx.core.util.Preconditions.checkNotNull;
-
-import static java.util.concurrent.TimeUnit.NANOSECONDS;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.annotation.RequiresApi;
-import androidx.camera.viewfinder.internal.utils.executor.CameraExecutors;
-
-import com.google.common.util.concurrent.ListenableFuture;
-
-import java.lang.reflect.UndeclaredThrowableException;
-import java.util.concurrent.BlockingQueue;
-import java.util.concurrent.CancellationException;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.Future;
-import java.util.concurrent.LinkedBlockingQueue;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.TimeoutException;
-
-/**
- *  The Class is based on the ChainingListenableFuture in Guava, the constructor of FutureChain
- *  will use the CallbackToFutureAdapter instead of the AbstractFuture.
- *
- * An implementation of {@code ListenableFuture} that also implements
- * {@code Runnable} so that it can be used to nest ListenableFutures.
- * Once the passed-in {@code ListenableFuture} is complete, it calls the
- * passed-in {@code Function} to generate the result.
- *
- * <p>If the Function throws any checked exceptions, they should be wrapped
- * in a {@code UndeclaredThrowableException} so that this class can get access to the cause.
- *
- */
-@RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
-class ChainingListenableFuture<I, O> extends FutureChain<O> implements Runnable {
-    @Nullable
-    private AsyncFunction<? super I, ? extends O> mFunction;
-    private final BlockingQueue<Boolean> mMayInterruptIfRunningChannel =
-            new LinkedBlockingQueue<>(1);
-    private final CountDownLatch mOutputCreated = new CountDownLatch(1);
-    @Nullable
-    private ListenableFuture<? extends I> mInputFuture;
-    @Nullable
-    volatile ListenableFuture<? extends O> mOutputFuture;
-
-    ChainingListenableFuture(
-            @NonNull AsyncFunction<? super I, ? extends O> function,
-            @NonNull ListenableFuture<? extends I> inputFuture) {
-        super();
-        mFunction = checkNotNull(function);
-        mInputFuture = checkNotNull(inputFuture);
-    }
-
-    /**
-     * Delegate the get() to the input and output mFutures, in case
-     * their implementations defer starting computation until their
-     * own get() is invoked.
-     */
-    @Override
-    @Nullable
-    public O get() throws InterruptedException, ExecutionException {
-        if (!isDone()) {
-            // Invoking get on the mInputFuture will ensure our own run()
-            // method below is invoked as a listener when mInputFuture sets
-            // its value.  Therefore when get() returns we should then see
-            // the mOutputFuture be created.
-            ListenableFuture<? extends I> inputFuture = mInputFuture;
-            if (inputFuture != null) {
-                inputFuture.get();
-            }
-
-            // If our listener was scheduled to run on an executor we may
-            // need to wait for our listener to finish running before the
-            // mOutputFuture has been constructed by the mFunction.
-            mOutputCreated.await();
-
-            // Like above with the mInputFuture, we have a listener on
-            // the mOutputFuture that will set our own value when its
-            // value is set.  Invoking get will ensure the output can
-            // complete and invoke our listener, so that we can later
-            // get the mResult.
-            ListenableFuture<? extends O> outputFuture = mOutputFuture;
-            if (outputFuture != null) {
-                outputFuture.get();
-            }
-        }
-        return super.get();
-    }
-
-    /**
-     * Delegate the get() to the input and output mFutures, in case
-     * their implementations defer starting computation until their
-     * own get() is invoked.
-     */
-    @Override
-    @Nullable
-    public O get(long timeout, @NonNull TimeUnit unit) throws TimeoutException,
-            ExecutionException, InterruptedException {
-        if (!isDone()) {
-            // Use a single time unit so we can decrease mRemaining timeout
-            // as we wait for various phases to complete.
-            if (unit != NANOSECONDS) {
-                timeout = NANOSECONDS.convert(timeout, unit);
-                unit = NANOSECONDS;
-            }
-
-            // Invoking get on the mInputFuture will ensure our own run()
-            // method below is invoked as a listener when mInputFuture sets
-            // its value.  Therefore when get() returns we should then see
-            // the mOutputFuture be created.
-            ListenableFuture<? extends I> inputFuture = mInputFuture;
-            if (inputFuture != null) {
-                long start = System.nanoTime();
-                inputFuture.get(timeout, unit);
-                timeout -= Math.max(0, System.nanoTime() - start);
-            }
-
-            // If our listener was scheduled to run on an executor we may
-            // need to wait for our listener to finish running before the
-            // mOutputFuture has been constructed by the mFunction.
-            long start = System.nanoTime();
-            if (!mOutputCreated.await(timeout, unit)) {
-                throw new TimeoutException();
-            }
-            timeout -= Math.max(0, System.nanoTime() - start);
-
-            // Like above with the mInputFuture, we have a listener on
-            // the mOutputFuture that will set our own value when its
-            // value is set.  Invoking get will ensure the output can
-            // complete and invoke our listener, so that we can later
-            // get the mResult.
-            ListenableFuture<? extends O> outputFuture = mOutputFuture;
-            if (outputFuture != null) {
-                outputFuture.get(timeout, unit);
-            }
-        }
-        return super.get(timeout, unit);
-    }
-
-    @Override
-    public boolean cancel(boolean mayInterruptIfRunning) {
-        /*
-         * Our additional cancellation work needs to occur even if
-         * !mayInterruptIfRunning, so we can't move it into interruptTask().
-         */
-        if (super.cancel(mayInterruptIfRunning)) {
-            // This should never block since only one thread is allowed to cancel
-            // this Future.
-            putUninterruptibly(mMayInterruptIfRunningChannel, mayInterruptIfRunning);
-            cancel(mInputFuture, mayInterruptIfRunning);
-            cancel(mOutputFuture, mayInterruptIfRunning);
-            return true;
-        }
-        return false;
-    }
-
-    private void cancel(@Nullable Future<?> future,
-            boolean mayInterruptIfRunning) {
-        if (future != null) {
-            future.cancel(mayInterruptIfRunning);
-        }
-    }
-
-    @Override
-    public void run() {
-        try {
-            I sourceResult;
-            try {
-                sourceResult = getUninterruptibly(mInputFuture);
-            } catch (CancellationException e) {
-                // Cancel this future and return.
-                // At this point, mInputFuture is cancelled and mOutputFuture doesn't
-                // exist, so the value of mayInterruptIfRunning is irrelevant.
-                cancel(false);
-                return;
-            } catch (ExecutionException e) {
-                // Set the cause of the exception as this future's exception
-                setException(e.getCause());
-                return;
-            }
-
-            final ListenableFuture<? extends O> outputFuture = mOutputFuture =
-                    mFunction.apply(sourceResult);
-            if (isCancelled()) {
-                // Handles the case where cancel was called while the mFunction was
-                // being applied.
-                // There is a gap in cancel(boolean) between calling sync.cancel()
-                // and storing the value of mayInterruptIfRunning, so this thread
-                // needs to block, waiting for that value.
-                outputFuture.cancel(takeUninterruptibly(mMayInterruptIfRunningChannel));
-                mOutputFuture = null;
-                return;
-            }
-            outputFuture.addListener(new Runnable() {
-                @Override
-                public void run() {
-                    try {
-                        // Here it would have been nice to have had an
-                        // UninterruptibleListenableFuture, but we don't want to start a
-                        // combinatorial explosion of interfaces, so we have to make do.
-                        set(getUninterruptibly(outputFuture));
-                    } catch (CancellationException e) {
-                        // Cancel this future and return.
-                        // At this point, mInputFuture and mOutputFuture are done, so the
-                        // value of mayInterruptIfRunning is irrelevant.
-                        cancel(false);
-                        return;
-                    } catch (ExecutionException e) {
-                        // Set the cause of the exception as this future's exception
-                        setException(e.getCause());
-                    } finally {
-                        // Don't pin inputs beyond completion
-                        ChainingListenableFuture.this.mOutputFuture = null;
-                    }
-                }
-            }, CameraExecutors.directExecutor());
-        } catch (UndeclaredThrowableException e) {
-            // Set the cause of the exception as this future's exception
-            setException(e.getCause());
-        } catch (Exception e) {
-            // This exception is irrelevant in this thread, but useful for the
-            // client
-            setException(e);
-        } catch (Error e) {
-            // Propagate errors up ASAP - our superclass will rethrow the error
-            setException(e);
-        } finally {
-            // Don't pin inputs beyond completion
-            mFunction = null;
-            mInputFuture = null;
-            // Allow our get routines to examine mOutputFuture now.
-            mOutputCreated.countDown();
-        }
-    }
-
-    /**
-     * Invokes {@code queue.}{@link BlockingQueue#take() take()} uninterruptibly.
-     */
-    private <E> E takeUninterruptibly(@NonNull BlockingQueue<E> queue) {
-        boolean interrupted = false;
-        try {
-            while (true) {
-                try {
-                    return queue.take();
-                } catch (InterruptedException e) {
-                    interrupted = true;
-                }
-            }
-        } finally {
-            if (interrupted) {
-                Thread.currentThread().interrupt();
-            }
-        }
-    }
-
-    /**
-     * Invokes {@code queue.}{@link BlockingQueue#put(Object) put(element)}
-     * uninterruptibly.
-     */
-    private <E> void putUninterruptibly(@NonNull BlockingQueue<E> queue, @NonNull E element) {
-        boolean interrupted = false;
-        try {
-            while (true) {
-                try {
-                    queue.put(element);
-                    return;
-                } catch (InterruptedException e) {
-                    interrupted = true;
-                }
-            }
-        } finally {
-            if (interrupted) {
-                Thread.currentThread().interrupt();
-            }
-        }
-    }
-}
diff --git a/camera/camera-viewfinder/src/main/java/androidx/camera/viewfinder/internal/utils/futures/FutureCallback.java b/camera/camera-viewfinder/src/main/java/androidx/camera/viewfinder/internal/utils/futures/FutureCallback.java
deleted file mode 100644
index acedaa2..0000000
--- a/camera/camera-viewfinder/src/main/java/androidx/camera/viewfinder/internal/utils/futures/FutureCallback.java
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * Copyright 2022 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.camera.viewfinder.internal.utils.futures;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.RequiresApi;
-
-import com.google.common.util.concurrent.ListenableFuture;
-
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.Future;
-
-/**
- * Cloned from concurrent-futures package in Guava to AndroidX namespace since we would need
- * ListenableFuture related implementation but not want to include whole Guava library.
- *
- * A callback for accepting the results of a {@link Future} computation
- * asynchronously.
- *
- * <p>To attach to a {@link ListenableFuture} use {@link Futures#addCallback}.
- *
- * @author Anthony Zana
- * @since 10.0
- * @param <V>
- *
- */
-@RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
-public interface FutureCallback<V> {
-    /** Invoked with the result of the {@code Future} computation when it is successful. */
-    void onSuccess(V result);
-
-    /**
-     * Invoked when a {@code Future} computation fails or is canceled.
-     *
-     * <p>If the future's {@link Future#get() get} method throws an {@link ExecutionException}, then
-     * the cause is passed to this method. Any other thrown object is passed unaltered.
-     */
-    void onFailure(@NonNull Throwable t);
-}
diff --git a/camera/camera-viewfinder/src/main/java/androidx/camera/viewfinder/internal/utils/futures/FutureChain.java b/camera/camera-viewfinder/src/main/java/androidx/camera/viewfinder/internal/utils/futures/FutureChain.java
deleted file mode 100644
index b13e334..0000000
--- a/camera/camera-viewfinder/src/main/java/androidx/camera/viewfinder/internal/utils/futures/FutureChain.java
+++ /dev/null
@@ -1,155 +0,0 @@
-/*
- * Copyright 2022 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.camera.viewfinder.internal.utils.futures;
-
-import static androidx.core.util.Preconditions.checkNotNull;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.annotation.RequiresApi;
-import androidx.arch.core.util.Function;
-import androidx.concurrent.futures.CallbackToFutureAdapter;
-import androidx.core.util.Preconditions;
-
-import com.google.common.util.concurrent.ListenableFuture;
-
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.Executor;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.TimeoutException;
-
-/**
- * A {@link ListenableFuture} that supports chains of operations. For example:
- *
- * <pre>{@code
- * ListenableFuture<Boolean> adminIsLoggedIn =
- *     FutureChain.from(usersDatabase.getAdminUser())
- *         .transform(User::getId, directExecutor())
- *         .transform(ActivityService::isLoggedIn, threadPool);
- * }</pre>
- * @param <V>
- *
- */
-@RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
-public class FutureChain<V> implements ListenableFuture<V> {
-    @NonNull
-    private final ListenableFuture<V> mDelegate;
-    @Nullable
-    CallbackToFutureAdapter.Completer<V> mCompleter;
-
-    /**
-     * Converts the given {@code ListenableFuture} to an equivalent {@code FutureChain}.
-     *
-     * <p>If the given {@code ListenableFuture} is already a {@code FutureChain}, it is returned
-     * directly. If not, it is wrapped in a {@code FutureChain} that delegates all calls to the
-     * original {@code ListenableFuture}.
-     *
-     * @return directly if input a FutureChain or a ListenableFuture wrapped by FutureChain.
-     */
-    @NonNull
-    public static <V> FutureChain<V> from(@NonNull ListenableFuture<V> future) {
-        return future instanceof FutureChain
-                ? (FutureChain<V>) future : new FutureChain<V>(future);
-    }
-
-    /**
-     * Returns a new {@code Future} whose result is derived from the result of this {@code
-     * Future}.
-     * If this input {@code Future} fails, the returned {@code Future} fails with the same
-     * exception (and the function is not invoked).
-     *
-     * @param function A Function to transform the results of this future to the results of the
-     *                 returned future.
-     * @param executor Executor to run the function in.
-     * @return A future that holds result of the transformation.
-     */
-    @NonNull
-    public final <T> FutureChain<T> transform(@NonNull Function<? super V, T> function,
-            @NonNull Executor executor) {
-        return (FutureChain<T>) Futures.transform(this, function, executor);
-    }
-
-    FutureChain(@NonNull ListenableFuture<V> delegate) {
-        mDelegate = checkNotNull(delegate);
-    }
-
-    FutureChain() {
-        mDelegate = CallbackToFutureAdapter.getFuture(
-                new CallbackToFutureAdapter.Resolver<V>() {
-                    @Override
-                    public Object attachCompleter(
-                            @NonNull CallbackToFutureAdapter.Completer<V> completer) {
-                        Preconditions.checkState(mCompleter == null,
-                                "The result can only set once!");
-                        mCompleter = completer;
-                        return "FutureChain[" + FutureChain.this + "]";
-                    }
-                });
-    }
-
-    @Override
-    public void addListener(@NonNull Runnable listener, @NonNull Executor executor) {
-        mDelegate.addListener(listener, executor);
-    }
-
-    @Override
-    public boolean cancel(boolean mayInterruptIfRunning) {
-        return mDelegate.cancel(mayInterruptIfRunning);
-    }
-
-    @Override
-    public boolean isCancelled() {
-        return mDelegate.isCancelled();
-    }
-
-    @Override
-    public boolean isDone() {
-        return mDelegate.isDone();
-    }
-
-
-    @Nullable
-    @Override
-    public V get() throws InterruptedException, ExecutionException {
-        return mDelegate.get();
-    }
-
-    @Nullable
-    @Override
-    public V get(long timeout, @NonNull TimeUnit unit)
-            throws InterruptedException, ExecutionException, TimeoutException {
-        return mDelegate.get(timeout, unit);
-    }
-
-    boolean set(@Nullable V value) {
-        if (mCompleter != null) {
-            return mCompleter.set(value);
-        }
-
-        return false;
-    }
-
-    boolean setException(@NonNull Throwable throwable) {
-        if (mCompleter != null) {
-            return mCompleter.setException(throwable);
-        }
-
-        return false;
-    }
-
-}
-
diff --git a/camera/camera-viewfinder/src/main/java/androidx/camera/viewfinder/internal/utils/futures/Futures.java b/camera/camera-viewfinder/src/main/java/androidx/camera/viewfinder/internal/utils/futures/Futures.java
deleted file mode 100644
index 4a2802f..0000000
--- a/camera/camera-viewfinder/src/main/java/androidx/camera/viewfinder/internal/utils/futures/Futures.java
+++ /dev/null
@@ -1,398 +0,0 @@
-/*
- * Copyright 2022 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.camera.viewfinder.internal.utils.futures;
-
-import static androidx.core.util.Preconditions.checkNotNull;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.annotation.RequiresApi;
-import androidx.arch.core.util.Function;
-import androidx.camera.viewfinder.internal.utils.executor.CameraExecutors;
-import androidx.concurrent.futures.CallbackToFutureAdapter;
-import androidx.core.util.Preconditions;
-
-import com.google.common.util.concurrent.ListenableFuture;
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.List;
-import java.util.concurrent.CancellationException;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.Executor;
-import java.util.concurrent.Future;
-import java.util.concurrent.ScheduledFuture;
-
-/**
- * Utility class for generating specific implementations of {@link ListenableFuture}.
- */
-@RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
-public final class Futures {
-
-    /**
-     * Returns an implementation of {@link ListenableFuture} which immediately contains a result.
-     *
-     * @param value The result that is immediately set on the future.
-     * @param <V>   The type of the result.
-     * @return A future which immediately contains the result.
-     */
-    @NonNull
-    public static <V> ListenableFuture<V> immediateFuture(@Nullable V value) {
-        if (value == null) {
-            return ImmediateFuture.nullFuture();
-        }
-
-        return new ImmediateFuture.ImmediateSuccessfulFuture<>(value);
-    }
-
-    /**
-     * Returns an implementation of {@link ScheduledFuture} which immediately contains an
-     * exception that will be thrown by {@link Future#get()}.
-     *
-     * @param cause The cause of the {@link ExecutionException} that will be thrown by
-     * {@link Future#get()}.
-     * @param <V>   The type of the result.
-     * @return A future which immediately contains an exception.
-     */
-    @NonNull
-    public static <V> ScheduledFuture<V> immediateFailedScheduledFuture(@NonNull Throwable cause) {
-        return new ImmediateFuture.ImmediateFailedScheduledFuture<>(cause);
-    }
-
-    /**
-     * Returns a new {@code Future} whose result is asynchronously derived from the result
-     * of the given {@code Future}. If the given {@code Future} fails, the returned {@code Future}
-     * fails with the same exception (and the function is not invoked).
-     *
-     * @param input    The future to transform
-     * @param function A function to transform the result of the input future to the result of the
-     *                 output future
-     * @param executor Executor to run the function in.
-     * @return A future that holds result of the function (if the input succeeded) or the original
-     * input's failure (if not)
-     */
-    @NonNull
-    public static <I, O> ListenableFuture<O> transformAsync(
-            @NonNull ListenableFuture<I> input,
-            @NonNull AsyncFunction<? super I, ? extends O> function,
-            @NonNull Executor executor) {
-        ChainingListenableFuture<I, O> output = new ChainingListenableFuture<I, O>(function, input);
-        input.addListener(output, executor);
-        return output;
-    }
-
-    /**
-     * Returns a new {@code Future} whose result is derived from the result of the given {@code
-     * Future}. If {@code input} fails, the returned {@code Future} fails with the same
-     * exception (and the function is not invoked)
-     *
-     * @param input    The future to transform
-     * @param function A function to transform the results of the provided future to the results of
-     *                 the returned future.
-     * @param executor Executor to run the function in.
-     * @return A future that holds result of the transformation.
-     */
-    @NonNull
-    public static <I, O> ListenableFuture<O> transform(
-            @NonNull ListenableFuture<I> input,
-            @NonNull Function<? super I, ? extends O> function,
-            @NonNull Executor executor) {
-        checkNotNull(function);
-        return transformAsync(input, new AsyncFunction<I, O>() {
-
-            @Override
-            public ListenableFuture<O> apply(I input) {
-                return immediateFuture(function.apply(input));
-            }
-        }, executor);
-    }
-
-    private static final Function<?, ?> IDENTITY_FUNCTION = new Function<Object, Object>() {
-        @Override
-        public Object apply(Object input) {
-            return input;
-        }
-    };
-
-    /**
-     * Propagates the result of the given {@code ListenableFuture} to the given {@link
-     * CallbackToFutureAdapter.Completer} directly.
-     *
-     * <p>If {@code input} fails, the failure will be propagated to the {@code completer}.
-     *
-     * @param input     The future being propagated.
-     * @param completer The completer which will receive the result of the provided future.
-     */
-    @SuppressWarnings("LambdaLast") // ListenableFuture not needed for SAM conversion
-    public static <V> void propagate(
-            @NonNull ListenableFuture<V> input,
-            @NonNull final CallbackToFutureAdapter.Completer<V> completer) {
-        @SuppressWarnings({"unchecked"}) // Input of function is same as output
-                Function<? super V, ? extends V> identityTransform =
-                (Function<? super V, ? extends V>) IDENTITY_FUNCTION;
-        // Use direct executor here since function is just unpacking the output and should be quick
-        propagateTransform(input, identityTransform, completer, CameraExecutors.directExecutor());
-    }
-
-    /**
-     * Propagates the result of the given {@code ListenableFuture} to the given {@link
-     * CallbackToFutureAdapter.Completer} by applying the provided transformation function.
-     *
-     * <p>If {@code input} fails, the failure will be propagated to the {@code completer} (and the
-     * function is not invoked)
-     *
-     * @param input     The future to transform.
-     * @param function  A function to transform the results of the provided future to the results of
-     *                  the provided completer.
-     * @param completer The completer which will receive the result of the provided future.
-     * @param executor  Executor to run the function in.
-     */
-    public static <I, O> void propagateTransform(
-            @NonNull final ListenableFuture<I> input,
-            @NonNull final Function<? super I, ? extends O> function,
-            @NonNull final CallbackToFutureAdapter.Completer<O> completer,
-            @NonNull Executor executor) {
-        propagateTransform(true, input, function, completer, executor);
-    }
-
-    /**
-     * Propagates the result of the given {@code ListenableFuture} to the given {@link
-     * CallbackToFutureAdapter.Completer} by applying the provided transformation function.
-     *
-     * <p>If {@code input} fails, the failure will be propagated to the {@code completer} (and the
-     * function is not invoked)
-     *
-     * @param propagateCancellation {@code true} to propagate the cancellation from completer to
-     *                              input future.
-     * @param input                 The future to transform.
-     * @param function              A function to transform the results of the provided future to
-     *                              the results of the provided completer.
-     * @param completer             The completer which will receive the result of the provided
-     *                              future.
-     * @param executor              Executor to run the function in.
-     */
-    private static <I, O> void propagateTransform(
-            boolean propagateCancellation,
-            @NonNull final ListenableFuture<I> input,
-            @NonNull final Function<? super I, ? extends O> function,
-            @NonNull final CallbackToFutureAdapter.Completer<O> completer,
-            @NonNull Executor executor) {
-        Preconditions.checkNotNull(input);
-        Preconditions.checkNotNull(function);
-        Preconditions.checkNotNull(completer);
-        Preconditions.checkNotNull(executor);
-
-        addCallback(input, new FutureCallback<I>() {
-            @Override
-            public void onSuccess(@Nullable I result) {
-                try {
-                    completer.set(function.apply(result));
-                } catch (Throwable t) {
-                    completer.setException(t);
-                }
-            }
-
-            @Override
-            public void onFailure(Throwable t) {
-                completer.setException(t);
-            }
-        }, executor);
-
-        if (propagateCancellation) {
-            // Propagate cancellation from completer to input future
-            completer.addCancellationListener(new Runnable() {
-                @Override
-                public void run() {
-                    input.cancel(true);
-                }
-            }, CameraExecutors.directExecutor());
-        }
-    }
-
-    /**
-     * Returns a {@code ListenableFuture} whose result is set from the supplied future when it
-     * completes.
-     *
-     * <p>Cancelling the supplied future will also cancel the returned future, but
-     * cancelling the returned future will have no effect on the supplied future.
-     */
-    @NonNull
-    public static <V> ListenableFuture<V> nonCancellationPropagating(
-            @NonNull ListenableFuture<V> future) {
-        Preconditions.checkNotNull(future);
-
-        if (future.isDone()) {
-            return future;
-        }
-
-        ListenableFuture<V> output = CallbackToFutureAdapter.getFuture(
-                completer -> {
-                    @SuppressWarnings({"unchecked"}) // Input of function is same as output
-                            Function<? super V, ? extends V> identityTransform =
-                            (Function<? super V, ? extends V>) IDENTITY_FUNCTION;
-                    propagateTransform(false, future, identityTransform, completer,
-                            CameraExecutors.directExecutor());
-                    return "nonCancellationPropagating[" + future + "]";
-                });
-        return output;
-    }
-
-    /**
-     * Creates a new {@code ListenableFuture} whose value is a list containing the values of all its
-     * successful input futures. The list of results is in the same order as the input list, and if
-     * any of the provided futures fails or is canceled, its corresponding position will contain
-     * {@code null} (which is indistinguishable from the future having a successful value of {@code
-     * null}).
-     *
-     * <p>Canceling this future will attempt to cancel all the component futures.
-     *
-     * @param futures futures to combine
-     * @return a future that provides a list of the results of the component futures
-     */
-    @NonNull
-    public static <V> ListenableFuture<List<V>> successfulAsList(
-            @NonNull Collection<? extends ListenableFuture<? extends V>> futures) {
-        return new ListFuture<V>(new ArrayList<>(futures), false,
-                CameraExecutors.directExecutor());
-    }
-
-    /**
-     * Creates a new {@code ListenableFuture} whose value is a list containing the values of all its
-     * input futures, if all succeed.
-     *
-     * <p>The list of results is in the same order as the input list.
-     *
-     * <p>Canceling this future will attempt to cancel all the component futures, and if any of the
-     * provided futures fails or is canceled, this one is, too.
-     *
-     * @param futures futures to combine
-     * @return a future that provides a list of the results of the component futures
-     */
-    @NonNull
-    public static <V> ListenableFuture<List<V>> allAsList(
-            @NonNull Collection<? extends ListenableFuture<? extends V>> futures) {
-        return new ListFuture<V>(new ArrayList<>(futures), true, CameraExecutors.directExecutor());
-    }
-
-    /**
-     * Registers separate success and failure callbacks to be run when the {@code Future}'s
-     * computation is {@linkplain Future#isDone() complete} or, if the
-     * computation is already complete, immediately.
-     *
-     * @param future   The future attach the callback to.
-     * @param callback The callback to invoke when {@code future} is completed.
-     * @param executor The executor to run {@code callback} when the future completes.
-     */
-    public static <V> void addCallback(
-            @NonNull final ListenableFuture<V> future,
-            @NonNull final FutureCallback<? super V> callback,
-            @NonNull Executor executor) {
-        Preconditions.checkNotNull(callback);
-        future.addListener(new CallbackListener<V>(future, callback), executor);
-    }
-
-    /**
-     * See {@link #addCallback(ListenableFuture, FutureCallback, Executor)} for behavioral notes.
-     */
-    private static final class CallbackListener<V> implements Runnable {
-        final Future<V> mFuture;
-        final FutureCallback<? super V> mCallback;
-
-        CallbackListener(Future<V> future, FutureCallback<? super V> callback) {
-            mFuture = future;
-            mCallback = callback;
-        }
-
-        @Override
-        public void run() {
-            final V value;
-            try {
-                value = getDone(mFuture);
-            } catch (ExecutionException e) {
-                mCallback.onFailure(e.getCause());
-                return;
-            } catch (RuntimeException | Error e) {
-                mCallback.onFailure(e);
-                return;
-            }
-            mCallback.onSuccess(value);
-        }
-
-        @NonNull
-        @Override
-        public String toString() {
-            return getClass().getSimpleName() + "," + mCallback;
-        }
-    }
-
-    /**
-     * Returns the result of the input {@code Future}, which must have already completed.
-     *
-     * <p>The benefits of this method are twofold. First, the name "getDone" suggests to readers
-     * that the {@code Future} is already done. Second, if buggy code calls {@code getDone} on a
-     * {@code Future} that is still pending, the program will throw instead of block.
-     *
-     * @throws ExecutionException    if the {@code Future} failed with an exception
-     * @throws CancellationException if the {@code Future} was cancelled
-     * @throws IllegalStateException if the {@code Future} is not done
-     */
-    @Nullable
-    public static <V> V getDone(@NonNull Future<V> future) throws ExecutionException {
-        /*
-         * We throw IllegalStateException, since the call could succeed later. Perhaps we
-         * "should" throw IllegalArgumentException, since the call could succeed with a different
-         * argument. Those exceptions' docs suggest that either is acceptable. Google's Java
-         * Practices page recommends IllegalArgumentException here, in part to keep its
-         * recommendation simple: Static methods should throw IllegalStateException only when
-         * they use static state.
-         *
-         * Why do we deviate here? The answer: We want for fluentFuture.getDone() to throw the same
-         * exception as Futures.getDone(fluentFuture).
-         */
-        Preconditions.checkState(future.isDone(), "Future was expected to be done, " + future);
-        return getUninterruptibly(future);
-    }
-
-    /**
-     * Invokes {@code Future.}{@link Future#get() get()} uninterruptibly.
-     *
-     * @throws ExecutionException    if the computation threw an exception
-     * @throws CancellationException if the computation was cancelled
-     */
-    @Nullable
-    public static <V> V getUninterruptibly(@NonNull Future<V> future) throws ExecutionException {
-        boolean interrupted = false;
-        try {
-            while (true) {
-                try {
-                    return future.get();
-                } catch (InterruptedException e) {
-                    interrupted = true;
-                }
-            }
-        } finally {
-            if (interrupted) {
-                Thread.currentThread().interrupt();
-            }
-        }
-    }
-
-    /**
-     * Should not be instantiated.
-     */
-    private Futures() {}
-}
diff --git a/camera/camera-viewfinder/src/main/java/androidx/camera/viewfinder/internal/utils/futures/ImmediateFuture.java b/camera/camera-viewfinder/src/main/java/androidx/camera/viewfinder/internal/utils/futures/ImmediateFuture.java
deleted file mode 100644
index 40c12fd..0000000
--- a/camera/camera-viewfinder/src/main/java/androidx/camera/viewfinder/internal/utils/futures/ImmediateFuture.java
+++ /dev/null
@@ -1,166 +0,0 @@
-/*
- * Copyright 2022 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.camera.viewfinder.internal.utils.futures;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.annotation.RequiresApi;
-import androidx.camera.viewfinder.internal.utils.Logger;
-import androidx.core.util.Preconditions;
-
-import com.google.common.util.concurrent.ListenableFuture;
-
-import java.util.concurrent.Delayed;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.Executor;
-import java.util.concurrent.ScheduledFuture;
-import java.util.concurrent.TimeUnit;
-
-/**
- * An implementation of {@link ListenableFuture} which immediately contains a result.
- *
- * <p>This implementation is based off of the Guava ImmediateSuccessfulFuture class.
- * @param <V> The type of the value stored in the future.
- *
- */
-@RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
-abstract class ImmediateFuture<V> implements ListenableFuture<V> {
-
-    private static final String TAG = "ImmediateFuture";
-
-    /**
-     * Returns a future that contains a null value.
-     *
-     * <p>This should be used any time a null value is needed as it uses a static ListenableFuture
-     * that contains null, and thus will not allocate.
-     */
-    public static <V> ListenableFuture<V> nullFuture() {
-        @SuppressWarnings({"unchecked", "rawtypes"}) // Safe since null can be cast to any type
-        ListenableFuture<V> typedNull = (ListenableFuture) ImmediateSuccessfulFuture.NULL_FUTURE;
-        return typedNull;
-    }
-
-    @Override
-    public void addListener(@NonNull Runnable listener, @NonNull Executor executor) {
-        Preconditions.checkNotNull(listener);
-        Preconditions.checkNotNull(executor);
-
-        try {
-            executor.execute(listener);
-        } catch (RuntimeException e) {
-            // ListenableFuture does not throw runtime exceptions, so swallow the exception and
-            // log it here.
-            Logger.e(TAG, "Experienced RuntimeException while attempting to notify " + listener
-                    + " on Executor " + executor, e);
-        }
-
-    }
-
-    @Override
-    public boolean cancel(boolean mayInterruptIfRunning) {
-        return false;
-    }
-
-    @Override
-    public boolean isCancelled() {
-        return false;
-    }
-
-    @Override
-    public boolean isDone() {
-        return true;
-    }
-
-    @Override
-    @Nullable
-    public abstract V get() throws ExecutionException;
-
-    @Override
-    @Nullable
-    public V get(long timeout, @NonNull TimeUnit unit) throws ExecutionException {
-        Preconditions.checkNotNull(unit);
-        return get();
-    }
-
-    static final class ImmediateSuccessfulFuture<V> extends ImmediateFuture<V> {
-
-        static final ImmediateFuture<Object> NULL_FUTURE =
-                new ImmediateSuccessfulFuture<>(null);
-
-        @Nullable
-        private final V mValue;
-
-        ImmediateSuccessfulFuture(@Nullable V value) {
-            mValue = value;
-        }
-
-
-        @Nullable
-        @Override
-        public V get() {
-            return mValue;
-        }
-
-        @NonNull
-        @Override
-        public String toString() {
-            // Behaviour analogous to AbstractResolvableFuture#toString().
-            return super.toString() + "[status=SUCCESS, result=[" + mValue + "]]";
-        }
-    }
-
-    static class ImmediateFailedFuture<V> extends ImmediateFuture<V> {
-
-        @NonNull
-        private final Throwable mCause;
-
-        ImmediateFailedFuture(@NonNull Throwable cause) {
-            mCause = cause;
-        }
-
-        @Nullable
-        @Override
-        public V get() throws ExecutionException {
-            throw new ExecutionException(mCause);
-        }
-
-        @Override
-        @NonNull
-        public String toString() {
-            // Behaviour analogous to AbstractResolvableFuture#toString().
-            return super.toString() + "[status=FAILURE, cause=[" + mCause + "]]";
-        }
-    }
-
-    static final class ImmediateFailedScheduledFuture<V> extends ImmediateFailedFuture<V> implements
-            ScheduledFuture<V> {
-
-        ImmediateFailedScheduledFuture(@NonNull Throwable cause) {
-            super(cause);
-        }
-
-        @Override
-        public long getDelay(@NonNull TimeUnit timeUnit) {
-            return 0;
-        }
-
-        @Override
-        public int compareTo(@NonNull Delayed delayed) {
-            return -1;
-        }
-    }
-}
diff --git a/camera/camera-viewfinder/src/main/java/androidx/camera/viewfinder/internal/utils/futures/ListFuture.java b/camera/camera-viewfinder/src/main/java/androidx/camera/viewfinder/internal/utils/futures/ListFuture.java
deleted file mode 100644
index 1308b89..0000000
--- a/camera/camera-viewfinder/src/main/java/androidx/camera/viewfinder/internal/utils/futures/ListFuture.java
+++ /dev/null
@@ -1,270 +0,0 @@
-/*
- * Copyright 2022 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.camera.viewfinder.internal.utils.futures;
-
-import static androidx.camera.viewfinder.internal.utils.futures.Futures.getUninterruptibly;
-import static androidx.core.util.Preconditions.checkNotNull;
-import static androidx.core.util.Preconditions.checkState;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.annotation.RequiresApi;
-import androidx.camera.viewfinder.internal.utils.executor.CameraExecutors;
-import androidx.concurrent.futures.CallbackToFutureAdapter;
-import androidx.core.util.Preconditions;
-
-import com.google.common.util.concurrent.ListenableFuture;
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.List;
-import java.util.concurrent.CancellationException;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.Executor;
-import java.util.concurrent.Future;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.TimeoutException;
-import java.util.concurrent.atomic.AtomicInteger;
-
-/**
- * The Class is based on the ListFuture in Guava and to use the CallbackToFutureAdapter instead
- * of the AbstractFuture.
- *
- * Class that implements {@link Futures#allAsList(Collection)} and
- * {@link Futures#successfulAsList(Collection)}.
- * The idea is to create a (null-filled) List and register a listener with
- * each component future to fill out the value in the List when that future
- * completes.
- *
- */
-@RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
-class ListFuture<V> implements ListenableFuture<List<V>> {
-    @Nullable
-    List<? extends ListenableFuture<? extends V>> mFutures;
-    @Nullable
-    List<V> mValues;
-    private final boolean mAllMustSucceed;
-    @NonNull
-    private final AtomicInteger mRemaining;
-    @NonNull
-    private final ListenableFuture<List<V>> mResult;
-    CallbackToFutureAdapter.Completer<List<V>> mResultNotifier;
-
-    /**
-     * Constructor.
-     *
-     * @param futures          all the futures to build the list from
-     * @param allMustSucceed   whether a single failure or cancellation should
-     *                         propagate to this future
-     * @param listenerExecutor used to run listeners on all the passed in futures.
-     */
-    ListFuture(
-            @NonNull List<? extends ListenableFuture<? extends V>> futures,
-            boolean allMustSucceed, @NonNull Executor listenerExecutor) {
-        mFutures = checkNotNull(futures);
-        mValues = new ArrayList<>(futures.size());
-        mAllMustSucceed = allMustSucceed;
-        mRemaining = new AtomicInteger(futures.size());
-        mResult = CallbackToFutureAdapter.getFuture(
-                new CallbackToFutureAdapter.Resolver<List<V>>() {
-                    @Override
-                    public Object attachCompleter(
-                            @NonNull CallbackToFutureAdapter.Completer<List<V>> completer) {
-                        Preconditions.checkState(mResultNotifier == null,
-                                "The result can only set once!");
-                        mResultNotifier = completer;
-                        return "ListFuture[" + this + "]";
-                    }
-                });
-
-        init(listenerExecutor);
-    }
-
-    private void init(@NonNull Executor listenerExecutor) {
-        // First, schedule cleanup to execute when the Future is done.
-        addListener(new Runnable() {
-            @Override
-            public void run() {
-                // By now the mValues array has either been set as the Future's value,
-                // or (in case of failure) is no longer useful.
-                ListFuture.this.mValues = null;
-
-                // Let go of the memory held by other mFutures
-                ListFuture.this.mFutures = null;
-            }
-        }, CameraExecutors.directExecutor());
-
-        // Now begin the "real" initialization.
-
-        // Corner case: List is empty.
-        if (mFutures.isEmpty()) {
-            mResultNotifier.set(new ArrayList<>(mValues));
-            return;
-        }
-
-        // Populate the results list with null initially.
-        for (int i = 0; i < mFutures.size(); ++i) {
-            mValues.add(null);
-        }
-
-        // Register a listener on each Future in the list to update
-        // the state of this future.
-        // Note that if all the mFutures on the list are done prior to completing
-        // this loop, the last call to addListener() will callback to
-        // setOneValue(), transitively call our cleanup listener, and set
-        // mFutures to null.
-        // We store a reference to mFutures to avoid the NPE.
-        List<? extends ListenableFuture<? extends V>> localFutures = mFutures;
-        for (int i = 0; i < localFutures.size(); i++) {
-            final ListenableFuture<? extends V> listenable = localFutures.get(i);
-            final int index = i;
-            listenable.addListener(new Runnable() {
-                @Override
-                public void run() {
-                    setOneValue(index, listenable);
-                }
-            }, listenerExecutor);
-        }
-    }
-
-    /**
-     * Sets the value at the given index to that of the given future.
-     */
-    void setOneValue(int index, @NonNull Future<? extends V> future) {
-        List<V> localValues = mValues;
-        if (isDone() || localValues == null) {
-            // Some other future failed or has been cancelled, causing this one to
-            // also be cancelled or have an exception set. This should only happen
-            // if mAllMustSucceed is true.
-            checkState(mAllMustSucceed,
-                    "Future was done before all dependencies completed");
-            return;
-        }
-
-        try {
-            checkState(future.isDone(),
-                    "Tried to set value from future which is not done");
-            localValues.set(index, getUninterruptibly(future));
-        } catch (CancellationException e) {
-            if (mAllMustSucceed) {
-                // Set ourselves as cancelled. Let the input futures keep running
-                // as some of them may be used elsewhere.
-                // (Currently we don't override interruptTask, so
-                // mayInterruptIfRunning==false isn't technically necessary.)
-                cancel(false);
-            }
-        } catch (ExecutionException e) {
-            if (mAllMustSucceed) {
-                // As soon as the first one fails, throw the exception up.
-                // The mResult of all other inputs is then ignored.
-                mResultNotifier.setException(e.getCause());
-            }
-        } catch (RuntimeException e) {
-            if (mAllMustSucceed) {
-                mResultNotifier.setException(e);
-            }
-        } catch (Error e) {
-            // Propagate errors up ASAP - our superclass will rethrow the error
-            mResultNotifier.setException(e);
-        } finally {
-            int newRemaining = mRemaining.decrementAndGet();
-            checkState(newRemaining >= 0, "Less than 0 remaining futures");
-            if (newRemaining == 0) {
-                localValues = mValues;
-                if (localValues != null) {
-                    mResultNotifier.set(new ArrayList<>(localValues));
-                } else {
-                    checkState(isDone());
-                }
-            }
-        }
-    }
-
-    @Override
-    public void addListener(@NonNull Runnable listener, @NonNull Executor executor) {
-        mResult.addListener(listener, executor);
-    }
-
-    @Override
-    public boolean cancel(boolean mayInterruptIfRunning) {
-        if (mFutures != null) {
-            for (ListenableFuture<? extends V> f : mFutures) {
-                f.cancel(mayInterruptIfRunning);
-            }
-        }
-
-        return mResult.cancel(mayInterruptIfRunning);
-    }
-
-    @Override
-    public boolean isCancelled() {
-        return mResult.isCancelled();
-    }
-
-    @Override
-    public boolean isDone() {
-        return mResult.isDone();
-    }
-
-    @Override
-    @Nullable
-    public List<V> get() throws InterruptedException, ExecutionException {
-        callAllGets();
-
-        // This may still block in spite of the calls above, as the listeners may
-        // be scheduled for execution in other threads.
-        return mResult.get();
-    }
-
-    @Override
-    public List<V> get(long timeout, @NonNull TimeUnit unit)
-            throws InterruptedException, ExecutionException, TimeoutException {
-        return mResult.get(timeout, unit);
-    }
-
-    /**
-     * Calls the get method of all dependency futures to work around a bug in
-     * some ListenableFutures where the listeners aren't called until get() is
-     * called.
-     */
-    private void callAllGets() throws InterruptedException {
-        List<? extends ListenableFuture<? extends V>> oldFutures = mFutures;
-        if (oldFutures != null && !isDone()) {
-            for (ListenableFuture<? extends V> future : oldFutures) {
-                // We wait for a little while for the future, but if it's not done,
-                // we check that no other futures caused a cancellation or failure.
-                // This can introduce a delay of up to 10ms in reporting an exception.
-                while (!future.isDone()) {
-                    try {
-                        future.get();
-                    } catch (Error e) {
-                        throw e;
-                    } catch (InterruptedException e) {
-                        throw e;
-                    } catch (Throwable e) {
-                        // ExecutionException / CancellationException / RuntimeException
-                        if (mAllMustSucceed) {
-                            return;
-                        } else {
-                            continue;
-                        }
-                    }
-                }
-            }
-        }
-    }
-}
diff --git a/camera/camera-viewfinder/src/main/java/androidx/camera/viewfinder/internal/utils/futures/package-info.java b/camera/camera-viewfinder/src/main/java/androidx/camera/viewfinder/internal/utils/futures/package-info.java
deleted file mode 100644
index cf0c731..0000000
--- a/camera/camera-viewfinder/src/main/java/androidx/camera/viewfinder/internal/utils/futures/package-info.java
+++ /dev/null
@@ -1,22 +0,0 @@
-/*
- * Copyright 2022 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.
- */
-
-/**
- */
-@RestrictTo(RestrictTo.Scope.LIBRARY)
-package androidx.camera.viewfinder.internal.utils.futures;
-
-import androidx.annotation.RestrictTo;
diff --git a/collection/collection-benchmark/build.gradle b/collection/collection-benchmark/build.gradle
index 062d54e..3e41373 100644
--- a/collection/collection-benchmark/build.gradle
+++ b/collection/collection-benchmark/build.gradle
@@ -116,8 +116,8 @@
     )
     xcodeProjectName = "collection-benchmark-ios"
     scheme = "testapp-ios"
-    // ios 13, 17.0
-    destination = "platform=iOS Simulator,name=iPhone 13,OS=17.0"
+    // To run locally switch to iOS 17.0 simulators
+    destination = "platform=iOS Simulator,name=iPhone 13,OS=15.2"
     referenceSha.set(androidx.getReferenceSha())
 }
 
diff --git a/compose/compiler/compiler-hosted/integration-tests/src/jvmTest/kotlin/androidx/compose/compiler/plugins/kotlin/ClassStabilityTransformTests.kt b/compose/compiler/compiler-hosted/integration-tests/src/jvmTest/kotlin/androidx/compose/compiler/plugins/kotlin/ClassStabilityTransformTests.kt
index 07fc5cb..479d52f 100644
--- a/compose/compiler/compiler-hosted/integration-tests/src/jvmTest/kotlin/androidx/compose/compiler/plugins/kotlin/ClassStabilityTransformTests.kt
+++ b/compose/compiler/compiler-hosted/integration-tests/src/jvmTest/kotlin/androidx/compose/compiler/plugins/kotlin/ClassStabilityTransformTests.kt
@@ -1465,6 +1465,9 @@
                 A(X(listOf(StableClass())))
                 A(StableDelegateProp())
                 A(UnstableDelegateProp())
+                A(SingleParamProp(0))
+                A(SingleParamNonProp(0))
+                A(SingleParamProp(Any()))
             }
         """
         )
diff --git a/compose/compiler/compiler-hosted/integration-tests/src/jvmTest/kotlin/androidx/compose/compiler/plugins/kotlin/FunctionBodySkippingTransformTests.kt b/compose/compiler/compiler-hosted/integration-tests/src/jvmTest/kotlin/androidx/compose/compiler/plugins/kotlin/FunctionBodySkippingTransformTests.kt
index 1704013..1a46556 100644
--- a/compose/compiler/compiler-hosted/integration-tests/src/jvmTest/kotlin/androidx/compose/compiler/plugins/kotlin/FunctionBodySkippingTransformTests.kt
+++ b/compose/compiler/compiler-hosted/integration-tests/src/jvmTest/kotlin/androidx/compose/compiler/plugins/kotlin/FunctionBodySkippingTransformTests.kt
@@ -1368,4 +1368,20 @@
             }
         """.trimIndent()
     )
+
+    @Test
+    fun testComposable() = verifyGoldenComposeIrTransform(
+        source = """
+            interface NewProfileOBViewModel {
+                fun overrideMe(): @Type () -> Unit
+            }
+
+            class ReturningProfileObViewModel : NewProfileOBViewModel {
+                override fun overrideMe(): @Type () -> Unit = {}
+            }
+
+            @Target(AnnotationTarget.TYPE)
+            annotation class Type
+        """
+    )
 }
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ClassStabilityTransformTests/testStabilityPropagationOfVariousTypesInSameModule\133useFir = false\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ClassStabilityTransformTests/testStabilityPropagationOfVariousTypesInSameModule\133useFir = false\135.txt"
index 325ec57..290e146 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ClassStabilityTransformTests/testStabilityPropagationOfVariousTypesInSameModule\133useFir = false\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ClassStabilityTransformTests/testStabilityPropagationOfVariousTypesInSameModule\133useFir = false\135.txt"
@@ -17,6 +17,9 @@
     A(X(listOf(StableClass())))
     A(StableDelegateProp())
     A(UnstableDelegateProp())
+    A(SingleParamProp(0))
+    A(SingleParamNonProp(0))
+    A(SingleParamProp(Any()))
 }
 
 //
@@ -52,7 +55,7 @@
 @Composable
 fun A(y: Any, %composer: Composer?, %changed: Int) {
   %composer = %composer.startRestartGroup(<>)
-  sourceInformation(%composer, "C(A)<A(X(li...>,<A(Stab...>,<A(Unst...>:Test.kt")
+  sourceInformation(%composer, "C(A)<A(X(li...>,<A(Stab...>,<A(Unst...>,<A(Sing...>,<A(Sing...>,<A(Sing...>:Test.kt")
   if (isTraceInProgress()) {
     traceEventStart(<>, %changed, -1, <>)
   }
@@ -60,6 +63,9 @@
   A(X(listOf(StableClass())), %composer, 0b1000)
   A(StableDelegateProp(), %composer, 0)
   A(UnstableDelegateProp(), %composer, UnstableDelegate.%stable)
+  A(SingleParamProp(0), %composer, SingleParamProp.%stable or 0)
+  A(SingleParamNonProp(0), %composer, SingleParamNonProp.%stable)
+  A(SingleParamProp(Any()), %composer, 0b1000)
   if (isTraceInProgress()) {
     traceEventEnd()
   }
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ClassStabilityTransformTests/testStabilityPropagationOfVariousTypesInSameModule\133useFir = true\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ClassStabilityTransformTests/testStabilityPropagationOfVariousTypesInSameModule\133useFir = true\135.txt"
index 325ec57..290e146 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ClassStabilityTransformTests/testStabilityPropagationOfVariousTypesInSameModule\133useFir = true\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ClassStabilityTransformTests/testStabilityPropagationOfVariousTypesInSameModule\133useFir = true\135.txt"
@@ -17,6 +17,9 @@
     A(X(listOf(StableClass())))
     A(StableDelegateProp())
     A(UnstableDelegateProp())
+    A(SingleParamProp(0))
+    A(SingleParamNonProp(0))
+    A(SingleParamProp(Any()))
 }
 
 //
@@ -52,7 +55,7 @@
 @Composable
 fun A(y: Any, %composer: Composer?, %changed: Int) {
   %composer = %composer.startRestartGroup(<>)
-  sourceInformation(%composer, "C(A)<A(X(li...>,<A(Stab...>,<A(Unst...>:Test.kt")
+  sourceInformation(%composer, "C(A)<A(X(li...>,<A(Stab...>,<A(Unst...>,<A(Sing...>,<A(Sing...>,<A(Sing...>:Test.kt")
   if (isTraceInProgress()) {
     traceEventStart(<>, %changed, -1, <>)
   }
@@ -60,6 +63,9 @@
   A(X(listOf(StableClass())), %composer, 0b1000)
   A(StableDelegateProp(), %composer, 0)
   A(UnstableDelegateProp(), %composer, UnstableDelegate.%stable)
+  A(SingleParamProp(0), %composer, SingleParamProp.%stable or 0)
+  A(SingleParamNonProp(0), %composer, SingleParamNonProp.%stable)
+  A(SingleParamProp(Any()), %composer, 0b1000)
   if (isTraceInProgress()) {
     traceEventEnd()
   }
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.FunctionBodySkippingTransformTestsNoSource/testComposable\133useFir = false\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.FunctionBodySkippingTransformTestsNoSource/testComposable\133useFir = false\135.txt"
new file mode 100644
index 0000000..60c3ae7
--- /dev/null
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.FunctionBodySkippingTransformTestsNoSource/testComposable\133useFir = false\135.txt"
@@ -0,0 +1,32 @@
+//
+// Source
+// ------------------------------------------
+
+interface NewProfileOBViewModel {
+    fun overrideMe(): @Type () -> Unit
+}
+
+class ReturningProfileObViewModel : NewProfileOBViewModel {
+    override fun overrideMe(): @Type () -> Unit = {}
+}
+
+@Target(AnnotationTarget.TYPE)
+annotation class Type
+
+//
+// Transformed IR
+// ------------------------------------------
+
+interface NewProfileOBViewModel {
+  abstract fun overrideMe(): @[Type] Function0<Unit>
+}
+@StabilityInferred(parameters = 1)
+class ReturningProfileObViewModel : NewProfileOBViewModel {
+  override fun overrideMe(): @[Type] Function0<Unit> {
+    return {
+    }
+  }
+  static val %stable: Int = 0
+}
+@Target(allowedTargets = AnnotationTarget.TYPE)
+open annotation class Type : Annotation
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.FunctionBodySkippingTransformTestsNoSource/testComposable\133useFir = true\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.FunctionBodySkippingTransformTestsNoSource/testComposable\133useFir = true\135.txt"
new file mode 100644
index 0000000..60c3ae7
--- /dev/null
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.FunctionBodySkippingTransformTestsNoSource/testComposable\133useFir = true\135.txt"
@@ -0,0 +1,32 @@
+//
+// Source
+// ------------------------------------------
+
+interface NewProfileOBViewModel {
+    fun overrideMe(): @Type () -> Unit
+}
+
+class ReturningProfileObViewModel : NewProfileOBViewModel {
+    override fun overrideMe(): @Type () -> Unit = {}
+}
+
+@Target(AnnotationTarget.TYPE)
+annotation class Type
+
+//
+// Transformed IR
+// ------------------------------------------
+
+interface NewProfileOBViewModel {
+  abstract fun overrideMe(): @[Type] Function0<Unit>
+}
+@StabilityInferred(parameters = 1)
+class ReturningProfileObViewModel : NewProfileOBViewModel {
+  override fun overrideMe(): @[Type] Function0<Unit> {
+    return {
+    }
+  }
+  static val %stable: Int = 0
+}
+@Target(allowedTargets = AnnotationTarget.TYPE)
+open annotation class Type : Annotation
diff --git a/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/ComposeFqNames.kt b/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/ComposeFqNames.kt
index 4221be5..001ab7e 100644
--- a/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/ComposeFqNames.kt
+++ b/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/ComposeFqNames.kt
@@ -17,8 +17,11 @@
 package androidx.compose.compiler.plugins.kotlin
 
 import org.jetbrains.kotlin.ir.declarations.IrAnnotationContainer
+import org.jetbrains.kotlin.ir.expressions.IrConstructorCall
 import org.jetbrains.kotlin.ir.types.IrType
+import org.jetbrains.kotlin.ir.util.constructedClass
 import org.jetbrains.kotlin.ir.util.hasAnnotation
+import org.jetbrains.kotlin.ir.util.hasEqualFqName
 import org.jetbrains.kotlin.name.CallableId
 import org.jetbrains.kotlin.name.ClassId
 import org.jetbrains.kotlin.name.FqName
@@ -126,3 +129,6 @@
 
 fun IrAnnotationContainer.hasComposableAnnotation(): Boolean =
     hasAnnotation(ComposeFqNames.Composable)
+
+fun IrConstructorCall.isComposableAnnotation() =
+    symbol.owner.constructedClass.hasEqualFqName(ComposeFqNames.Composable)
diff --git a/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/ComposeIrGenerationExtension.kt b/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/ComposeIrGenerationExtension.kt
index e62c899..7391051 100644
--- a/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/ComposeIrGenerationExtension.kt
+++ b/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/ComposeIrGenerationExtension.kt
@@ -100,6 +100,7 @@
         }
 
         ClassStabilityTransformer(
+            useK2,
             pluginContext,
             symbolRemapper,
             metrics,
diff --git a/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/VersionChecker.kt b/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/VersionChecker.kt
index 5b14b17..ba475e4 100644
--- a/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/VersionChecker.kt
+++ b/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/VersionChecker.kt
@@ -129,6 +129,7 @@
             11600 to "1.6.0-alpha07",
             11700 to "1.6.0-alpha08",
             11800 to "1.6.0-beta01",
+            12000 to "1.7.0-alpha01",
         )
 
         /**
diff --git a/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/ClassStabilityTransformer.kt b/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/ClassStabilityTransformer.kt
index 3ee594f..8d6397e 100644
--- a/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/ClassStabilityTransformer.kt
+++ b/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/ClassStabilityTransformer.kt
@@ -60,6 +60,7 @@
  * annotation on it, as well as putting a static final int of the stability to be used at runtime.
  */
 class ClassStabilityTransformer(
+    private val useK2: Boolean,
     context: IrPluginContext,
     symbolRemapper: DeepCopySymbolRemapper,
     metrics: ModuleMetrics,
@@ -163,20 +164,24 @@
             marked = false,
             stability = stability
         )
+        val annotation = IrConstructorCallImpl(
+            UNDEFINED_OFFSET,
+            UNDEFINED_OFFSET,
+            StabilityInferredClass.defaultType,
+            StabilityInferredClass.constructors.first(),
+            0,
+            0,
+            1,
+            null
+        ).also {
+            it.putValueArgument(0, irConst(parameterMask))
+        }
 
-        cls.annotations +=
-            IrConstructorCallImpl(
-                startOffset = UNDEFINED_OFFSET,
-                endOffset = UNDEFINED_OFFSET,
-                type = StabilityInferredClass.defaultType,
-                symbol = StabilityInferredClass.constructors.first(),
-                typeArgumentsCount = 0,
-                constructorTypeArgumentsCount = 0,
-                valueArgumentsCount = 1,
-                origin = null
-            ).also {
-                it.putValueArgument(0, irConst(parameterMask))
-            }
+        if (useK2) {
+            context.annotationsRegistrar.addMetadataVisibleAnnotationsToElement(cls, annotation)
+        } else {
+            cls.annotations += annotation
+        }
 
         cls.addStabilityMarkerField(stableExpr)
         return result
diff --git a/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/ComposableTypeRemapper.kt b/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/ComposableTypeRemapper.kt
index 7d2ce82..f792619 100644
--- a/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/ComposableTypeRemapper.kt
+++ b/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/ComposableTypeRemapper.kt
@@ -16,8 +16,8 @@
 
 package androidx.compose.compiler.plugins.kotlin.lower
 
-import androidx.compose.compiler.plugins.kotlin.ComposeFqNames
 import androidx.compose.compiler.plugins.kotlin.hasComposableAnnotation
+import androidx.compose.compiler.plugins.kotlin.isComposableAnnotation
 import androidx.compose.compiler.plugins.kotlin.lower.decoys.isDecoy
 import org.jetbrains.kotlin.backend.common.extensions.IrPluginContext
 import org.jetbrains.kotlin.backend.common.extensions.IrPluginContextImpl
@@ -58,9 +58,7 @@
 import org.jetbrains.kotlin.ir.util.SymbolRenamer
 import org.jetbrains.kotlin.ir.util.TypeRemapper
 import org.jetbrains.kotlin.ir.util.defaultType
-import org.jetbrains.kotlin.ir.util.fqNameForIrSerialization
 import org.jetbrains.kotlin.ir.util.functions
-import org.jetbrains.kotlin.ir.util.hasAnnotation
 import org.jetbrains.kotlin.ir.util.isFunction
 import org.jetbrains.kotlin.ir.util.packageFqName
 import org.jetbrains.kotlin.ir.util.parentClassOrNull
@@ -86,13 +84,20 @@
         }
 
         return super.visitSimpleFunction(declaration).also {
-            it.overriddenSymbols.forEach {
-                if (!it.isBound) {
+            it.overriddenSymbols.forEach { symbol ->
+                if (!symbol.isBound) {
                     // symbol will be rebound by deep copy on later iteration
                     return@forEach
                 }
-                if (it.owner.needsComposableRemapping() && !it.owner.isDecoy()) {
-                    it.owner.remapTypes(typeRemapper)
+                val overriddenFn = symbol.owner
+                if (overriddenFn.origin == IrDeclarationOrigin.IR_EXTERNAL_DECLARATION_STUB) {
+                    // this is external function that is in a different compilation unit,
+                    // so we potentially need to update composable types for it.
+                    // if the function is in the current module, it should be updated eventually
+                    // by this deep copy pass.
+                    if (overriddenFn.needsComposableRemapping() && !overriddenFn.isDecoy()) {
+                        overriddenFn.remapTypes(typeRemapper)
+                    }
                 }
             }
             it.correspondingPropertySymbol = declaration.correspondingPropertySymbol
@@ -219,7 +224,7 @@
     private fun needsComposableRemapping(type: IrType?): Boolean {
         if (type == null) return false
         if (type !is IrSimpleType) return false
-        if (type.isComposable()) return true
+        if (type.hasComposableAnnotation()) return true
         if (type.arguments.any { needsComposableRemapping(it.typeOrNull) }) return true
         return false
     }
@@ -240,7 +245,7 @@
                 // or function type is composable (K1)
                 containingClass.defaultType.isSyntheticComposableFunction() || (
                     containingClass.defaultType.isFunction() &&
-                        expression.dispatchReceiver?.type?.isComposable() == true
+                        expression.dispatchReceiver?.type?.hasComposableAnnotation() == true
                 )
             )
         ) {
@@ -402,10 +407,6 @@
             dispatchReceiver = original.dispatchReceiver?.transform()
             extensionReceiver = original.extensionReceiver?.transform()
         }
-
-    private fun IrType.isComposable(): Boolean {
-        return annotations.hasAnnotation(ComposeFqNames.Composable)
-    }
 }
 
 class ComposerTypeRemapper(
@@ -506,9 +507,6 @@
         )
 }
 
-private fun IrConstructorCall.isComposableAnnotation() =
-    this.symbol.owner.parent.fqNameForIrSerialization == ComposeFqNames.Composable
-
 private val KotlinFunctionsBuiltInsPackageFqName = StandardNames.BUILT_INS_PACKAGE_FQ_NAME
     .child(Name.identifier("jvm"))
     .child(Name.identifier("functions"))
diff --git a/compose/foundation/foundation/api/1.6.0-beta01.txt b/compose/foundation/foundation/api/1.6.0-beta01.txt
index 7635bd1..b8a71d6 100644
--- a/compose/foundation/foundation/api/1.6.0-beta01.txt
+++ b/compose/foundation/foundation/api/1.6.0-beta01.txt
@@ -300,7 +300,7 @@
   }
 
   public final class DragAndDropTargetKt {
-    method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public static androidx.compose.ui.Modifier dragAndDropTarget(androidx.compose.ui.Modifier, kotlin.jvm.functions.Function1<? super androidx.compose.ui.draganddrop.DragAndDropEvent,? extends androidx.compose.ui.draganddrop.DragAndDropTarget> acceptDragAndDropTransfer);
+    method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public static androidx.compose.ui.Modifier dragAndDropTarget(androidx.compose.ui.Modifier, kotlin.jvm.functions.Function1<? super androidx.compose.ui.draganddrop.DragAndDropEvent,java.lang.Boolean> shouldStartDragAndDrop, androidx.compose.ui.draganddrop.DragAndDropTarget target);
   }
 
 }
diff --git a/compose/foundation/foundation/api/current.ignore b/compose/foundation/foundation/api/current.ignore
deleted file mode 100644
index e195065..0000000
--- a/compose/foundation/foundation/api/current.ignore
+++ /dev/null
@@ -1,41 +0,0 @@
-// Baseline format: 1.0
-DefaultValueChange: androidx.compose.foundation.ScrollState#animateScrollTo(int, androidx.compose.animation.core.AnimationSpec<java.lang.Float>, kotlin.coroutines.Continuation<? super kotlin.Unit>) parameter #2:
-    Attempted to remove default value from parameter arg3 in androidx.compose.foundation.ScrollState.animateScrollTo
-DefaultValueChange: androidx.compose.foundation.gestures.ScrollExtensionsKt#animateScrollBy(androidx.compose.foundation.gestures.ScrollableState, float, androidx.compose.animation.core.AnimationSpec<java.lang.Float>, kotlin.coroutines.Continuation<? super java.lang.Float>) parameter #3:
-    Attempted to remove default value from parameter arg4 in androidx.compose.foundation.gestures.ScrollExtensionsKt.animateScrollBy
-DefaultValueChange: androidx.compose.foundation.gestures.ScrollExtensionsKt#stopScroll(androidx.compose.foundation.gestures.ScrollableState, androidx.compose.foundation.MutatePriority, kotlin.coroutines.Continuation<? super kotlin.Unit>) parameter #2:
-    Attempted to remove default value from parameter arg3 in androidx.compose.foundation.gestures.ScrollExtensionsKt.stopScroll
-DefaultValueChange: androidx.compose.foundation.gestures.TapGestureDetectorKt#awaitFirstDown(androidx.compose.ui.input.pointer.AwaitPointerEventScope, boolean, androidx.compose.ui.input.pointer.PointerEventPass, kotlin.coroutines.Continuation<? super androidx.compose.ui.input.pointer.PointerInputChange>) parameter #3:
-    Attempted to remove default value from parameter arg4 in androidx.compose.foundation.gestures.TapGestureDetectorKt.awaitFirstDown
-DefaultValueChange: androidx.compose.foundation.gestures.TapGestureDetectorKt#detectTapGestures(androidx.compose.ui.input.pointer.PointerInputScope, kotlin.jvm.functions.Function1<? super androidx.compose.ui.geometry.Offset,kotlin.Unit>, kotlin.jvm.functions.Function1<? super androidx.compose.ui.geometry.Offset,kotlin.Unit>, kotlin.jvm.functions.Function3<? super androidx.compose.foundation.gestures.PressGestureScope,? super androidx.compose.ui.geometry.Offset,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,?>, kotlin.jvm.functions.Function1<? super androidx.compose.ui.geometry.Offset,kotlin.Unit>, kotlin.coroutines.Continuation<? super kotlin.Unit>) parameter #5:
-    Attempted to remove default value from parameter arg6 in androidx.compose.foundation.gestures.TapGestureDetectorKt.detectTapGestures
-DefaultValueChange: androidx.compose.foundation.gestures.TapGestureDetectorKt#waitForUpOrCancellation(androidx.compose.ui.input.pointer.AwaitPointerEventScope, androidx.compose.ui.input.pointer.PointerEventPass, kotlin.coroutines.Continuation<? super androidx.compose.ui.input.pointer.PointerInputChange>) parameter #2:
-    Attempted to remove default value from parameter arg3 in androidx.compose.foundation.gestures.TapGestureDetectorKt.waitForUpOrCancellation
-DefaultValueChange: androidx.compose.foundation.gestures.TransformableStateKt#animatePanBy(androidx.compose.foundation.gestures.TransformableState, long, androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.geometry.Offset>, kotlin.coroutines.Continuation<? super kotlin.Unit>) parameter #3:
-    Attempted to remove default value from parameter arg4 in androidx.compose.foundation.gestures.TransformableStateKt.animatePanBy
-DefaultValueChange: androidx.compose.foundation.gestures.TransformableStateKt#animateRotateBy(androidx.compose.foundation.gestures.TransformableState, float, androidx.compose.animation.core.AnimationSpec<java.lang.Float>, kotlin.coroutines.Continuation<? super kotlin.Unit>) parameter #3:
-    Attempted to remove default value from parameter arg4 in androidx.compose.foundation.gestures.TransformableStateKt.animateRotateBy
-DefaultValueChange: androidx.compose.foundation.gestures.TransformableStateKt#animateZoomBy(androidx.compose.foundation.gestures.TransformableState, float, androidx.compose.animation.core.AnimationSpec<java.lang.Float>, kotlin.coroutines.Continuation<? super kotlin.Unit>) parameter #3:
-    Attempted to remove default value from parameter arg4 in androidx.compose.foundation.gestures.TransformableStateKt.animateZoomBy
-DefaultValueChange: androidx.compose.foundation.gestures.TransformableStateKt#stopTransformation(androidx.compose.foundation.gestures.TransformableState, androidx.compose.foundation.MutatePriority, kotlin.coroutines.Continuation<? super kotlin.Unit>) parameter #2:
-    Attempted to remove default value from parameter arg3 in androidx.compose.foundation.gestures.TransformableStateKt.stopTransformation
-DefaultValueChange: androidx.compose.foundation.lazy.LazyListState#animateScrollToItem(int, int, kotlin.coroutines.Continuation<? super kotlin.Unit>) parameter #2:
-    Attempted to remove default value from parameter arg3 in androidx.compose.foundation.lazy.LazyListState.animateScrollToItem
-DefaultValueChange: androidx.compose.foundation.lazy.LazyListState#scrollToItem(int, int, kotlin.coroutines.Continuation<? super kotlin.Unit>) parameter #2:
-    Attempted to remove default value from parameter arg3 in androidx.compose.foundation.lazy.LazyListState.scrollToItem
-DefaultValueChange: androidx.compose.foundation.lazy.grid.LazyGridState#animateScrollToItem(int, int, kotlin.coroutines.Continuation<? super kotlin.Unit>) parameter #2:
-    Attempted to remove default value from parameter arg3 in androidx.compose.foundation.lazy.grid.LazyGridState.animateScrollToItem
-DefaultValueChange: androidx.compose.foundation.lazy.grid.LazyGridState#scrollToItem(int, int, kotlin.coroutines.Continuation<? super kotlin.Unit>) parameter #2:
-    Attempted to remove default value from parameter arg3 in androidx.compose.foundation.lazy.grid.LazyGridState.scrollToItem
-DefaultValueChange: androidx.compose.foundation.lazy.staggeredgrid.LazyStaggeredGridState#animateScrollToItem(int, int, kotlin.coroutines.Continuation<? super kotlin.Unit>) parameter #2:
-    Attempted to remove default value from parameter arg3 in androidx.compose.foundation.lazy.staggeredgrid.LazyStaggeredGridState.animateScrollToItem
-DefaultValueChange: androidx.compose.foundation.lazy.staggeredgrid.LazyStaggeredGridState#scrollToItem(int, int, kotlin.coroutines.Continuation<? super kotlin.Unit>) parameter #2:
-    Attempted to remove default value from parameter arg3 in androidx.compose.foundation.lazy.staggeredgrid.LazyStaggeredGridState.scrollToItem
-
-
-RemovedClass: androidx.compose.foundation.ExcludeFromSystemGesture_androidKt:
-    Removed class androidx.compose.foundation.ExcludeFromSystemGesture_androidKt
-RemovedClass: androidx.compose.foundation.MagnifierKt:
-    Removed class androidx.compose.foundation.MagnifierKt
-RemovedClass: androidx.compose.foundation.OverscrollConfigurationKt:
-    Removed class androidx.compose.foundation.OverscrollConfigurationKt
diff --git a/compose/foundation/foundation/api/current.txt b/compose/foundation/foundation/api/current.txt
index 7635bd1..b8a71d6 100644
--- a/compose/foundation/foundation/api/current.txt
+++ b/compose/foundation/foundation/api/current.txt
@@ -300,7 +300,7 @@
   }
 
   public final class DragAndDropTargetKt {
-    method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public static androidx.compose.ui.Modifier dragAndDropTarget(androidx.compose.ui.Modifier, kotlin.jvm.functions.Function1<? super androidx.compose.ui.draganddrop.DragAndDropEvent,? extends androidx.compose.ui.draganddrop.DragAndDropTarget> acceptDragAndDropTransfer);
+    method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public static androidx.compose.ui.Modifier dragAndDropTarget(androidx.compose.ui.Modifier, kotlin.jvm.functions.Function1<? super androidx.compose.ui.draganddrop.DragAndDropEvent,java.lang.Boolean> shouldStartDragAndDrop, androidx.compose.ui.draganddrop.DragAndDropTarget target);
   }
 
 }
diff --git a/compose/foundation/foundation/api/restricted_1.6.0-beta01.txt b/compose/foundation/foundation/api/restricted_1.6.0-beta01.txt
index 1b7db92..de3615d 100644
--- a/compose/foundation/foundation/api/restricted_1.6.0-beta01.txt
+++ b/compose/foundation/foundation/api/restricted_1.6.0-beta01.txt
@@ -302,7 +302,7 @@
   }
 
   public final class DragAndDropTargetKt {
-    method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public static androidx.compose.ui.Modifier dragAndDropTarget(androidx.compose.ui.Modifier, kotlin.jvm.functions.Function1<? super androidx.compose.ui.draganddrop.DragAndDropEvent,? extends androidx.compose.ui.draganddrop.DragAndDropTarget> acceptDragAndDropTransfer);
+    method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public static androidx.compose.ui.Modifier dragAndDropTarget(androidx.compose.ui.Modifier, kotlin.jvm.functions.Function1<? super androidx.compose.ui.draganddrop.DragAndDropEvent,java.lang.Boolean> shouldStartDragAndDrop, androidx.compose.ui.draganddrop.DragAndDropTarget target);
   }
 
 }
diff --git a/compose/foundation/foundation/api/restricted_current.ignore b/compose/foundation/foundation/api/restricted_current.ignore
deleted file mode 100644
index 9acda9a..0000000
--- a/compose/foundation/foundation/api/restricted_current.ignore
+++ /dev/null
@@ -1,37 +0,0 @@
-// Baseline format: 1.0
-DefaultValueChange: androidx.compose.foundation.ScrollState#animateScrollTo(int, androidx.compose.animation.core.AnimationSpec<java.lang.Float>, kotlin.coroutines.Continuation<? super kotlin.Unit>) parameter #2:
-    Attempted to remove default value from parameter arg3 in androidx.compose.foundation.ScrollState.animateScrollTo
-DefaultValueChange: androidx.compose.foundation.gestures.ScrollExtensionsKt#animateScrollBy(androidx.compose.foundation.gestures.ScrollableState, float, androidx.compose.animation.core.AnimationSpec<java.lang.Float>, kotlin.coroutines.Continuation<? super java.lang.Float>) parameter #3:
-    Attempted to remove default value from parameter arg4 in androidx.compose.foundation.gestures.ScrollExtensionsKt.animateScrollBy
-DefaultValueChange: androidx.compose.foundation.gestures.ScrollExtensionsKt#stopScroll(androidx.compose.foundation.gestures.ScrollableState, androidx.compose.foundation.MutatePriority, kotlin.coroutines.Continuation<? super kotlin.Unit>) parameter #2:
-    Attempted to remove default value from parameter arg3 in androidx.compose.foundation.gestures.ScrollExtensionsKt.stopScroll
-DefaultValueChange: androidx.compose.foundation.gestures.TapGestureDetectorKt#awaitFirstDown(androidx.compose.ui.input.pointer.AwaitPointerEventScope, boolean, androidx.compose.ui.input.pointer.PointerEventPass, kotlin.coroutines.Continuation<? super androidx.compose.ui.input.pointer.PointerInputChange>) parameter #3:
-    Attempted to remove default value from parameter arg4 in androidx.compose.foundation.gestures.TapGestureDetectorKt.awaitFirstDown
-DefaultValueChange: androidx.compose.foundation.gestures.TapGestureDetectorKt#detectTapGestures(androidx.compose.ui.input.pointer.PointerInputScope, kotlin.jvm.functions.Function1<? super androidx.compose.ui.geometry.Offset,kotlin.Unit>, kotlin.jvm.functions.Function1<? super androidx.compose.ui.geometry.Offset,kotlin.Unit>, kotlin.jvm.functions.Function3<? super androidx.compose.foundation.gestures.PressGestureScope,? super androidx.compose.ui.geometry.Offset,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,?>, kotlin.jvm.functions.Function1<? super androidx.compose.ui.geometry.Offset,kotlin.Unit>, kotlin.coroutines.Continuation<? super kotlin.Unit>) parameter #5:
-    Attempted to remove default value from parameter arg6 in androidx.compose.foundation.gestures.TapGestureDetectorKt.detectTapGestures
-DefaultValueChange: androidx.compose.foundation.gestures.TapGestureDetectorKt#waitForUpOrCancellation(androidx.compose.ui.input.pointer.AwaitPointerEventScope, androidx.compose.ui.input.pointer.PointerEventPass, kotlin.coroutines.Continuation<? super androidx.compose.ui.input.pointer.PointerInputChange>) parameter #2:
-    Attempted to remove default value from parameter arg3 in androidx.compose.foundation.gestures.TapGestureDetectorKt.waitForUpOrCancellation
-DefaultValueChange: androidx.compose.foundation.gestures.TransformableStateKt#animatePanBy(androidx.compose.foundation.gestures.TransformableState, long, androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.geometry.Offset>, kotlin.coroutines.Continuation<? super kotlin.Unit>) parameter #3:
-    Attempted to remove default value from parameter arg4 in androidx.compose.foundation.gestures.TransformableStateKt.animatePanBy
-DefaultValueChange: androidx.compose.foundation.gestures.TransformableStateKt#animateRotateBy(androidx.compose.foundation.gestures.TransformableState, float, androidx.compose.animation.core.AnimationSpec<java.lang.Float>, kotlin.coroutines.Continuation<? super kotlin.Unit>) parameter #3:
-    Attempted to remove default value from parameter arg4 in androidx.compose.foundation.gestures.TransformableStateKt.animateRotateBy
-DefaultValueChange: androidx.compose.foundation.gestures.TransformableStateKt#animateZoomBy(androidx.compose.foundation.gestures.TransformableState, float, androidx.compose.animation.core.AnimationSpec<java.lang.Float>, kotlin.coroutines.Continuation<? super kotlin.Unit>) parameter #3:
-    Attempted to remove default value from parameter arg4 in androidx.compose.foundation.gestures.TransformableStateKt.animateZoomBy
-DefaultValueChange: androidx.compose.foundation.gestures.TransformableStateKt#stopTransformation(androidx.compose.foundation.gestures.TransformableState, androidx.compose.foundation.MutatePriority, kotlin.coroutines.Continuation<? super kotlin.Unit>) parameter #2:
-    Attempted to remove default value from parameter arg3 in androidx.compose.foundation.gestures.TransformableStateKt.stopTransformation
-DefaultValueChange: androidx.compose.foundation.lazy.LazyListState#animateScrollToItem(int, int, kotlin.coroutines.Continuation<? super kotlin.Unit>) parameter #2:
-    Attempted to remove default value from parameter arg3 in androidx.compose.foundation.lazy.LazyListState.animateScrollToItem
-DefaultValueChange: androidx.compose.foundation.lazy.LazyListState#scrollToItem(int, int, kotlin.coroutines.Continuation<? super kotlin.Unit>) parameter #2:
-    Attempted to remove default value from parameter arg3 in androidx.compose.foundation.lazy.LazyListState.scrollToItem
-DefaultValueChange: androidx.compose.foundation.lazy.grid.LazyGridState#animateScrollToItem(int, int, kotlin.coroutines.Continuation<? super kotlin.Unit>) parameter #2:
-    Attempted to remove default value from parameter arg3 in androidx.compose.foundation.lazy.grid.LazyGridState.animateScrollToItem
-DefaultValueChange: androidx.compose.foundation.lazy.grid.LazyGridState#scrollToItem(int, int, kotlin.coroutines.Continuation<? super kotlin.Unit>) parameter #2:
-    Attempted to remove default value from parameter arg3 in androidx.compose.foundation.lazy.grid.LazyGridState.scrollToItem
-DefaultValueChange: androidx.compose.foundation.lazy.staggeredgrid.LazyStaggeredGridState#animateScrollToItem(int, int, kotlin.coroutines.Continuation<? super kotlin.Unit>) parameter #2:
-    Attempted to remove default value from parameter arg3 in androidx.compose.foundation.lazy.staggeredgrid.LazyStaggeredGridState.animateScrollToItem
-DefaultValueChange: androidx.compose.foundation.lazy.staggeredgrid.LazyStaggeredGridState#scrollToItem(int, int, kotlin.coroutines.Continuation<? super kotlin.Unit>) parameter #2:
-    Attempted to remove default value from parameter arg3 in androidx.compose.foundation.lazy.staggeredgrid.LazyStaggeredGridState.scrollToItem
-
-
-RemovedClass: androidx.compose.foundation.ExcludeFromSystemGesture_androidKt:
-    Removed class androidx.compose.foundation.ExcludeFromSystemGesture_androidKt
diff --git a/compose/foundation/foundation/api/restricted_current.txt b/compose/foundation/foundation/api/restricted_current.txt
index 1b7db92..de3615d 100644
--- a/compose/foundation/foundation/api/restricted_current.txt
+++ b/compose/foundation/foundation/api/restricted_current.txt
@@ -302,7 +302,7 @@
   }
 
   public final class DragAndDropTargetKt {
-    method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public static androidx.compose.ui.Modifier dragAndDropTarget(androidx.compose.ui.Modifier, kotlin.jvm.functions.Function1<? super androidx.compose.ui.draganddrop.DragAndDropEvent,? extends androidx.compose.ui.draganddrop.DragAndDropTarget> acceptDragAndDropTransfer);
+    method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public static androidx.compose.ui.Modifier dragAndDropTarget(androidx.compose.ui.Modifier, kotlin.jvm.functions.Function1<? super androidx.compose.ui.draganddrop.DragAndDropEvent,java.lang.Boolean> shouldStartDragAndDrop, androidx.compose.ui.draganddrop.DragAndDropTarget target);
   }
 
 }
diff --git a/compose/foundation/foundation/samples/src/main/java/androidx/compose/foundation/samples/DragAndDropSamples.kt b/compose/foundation/foundation/samples/src/main/java/androidx/compose/foundation/samples/DragAndDropSamples.kt
index d8e7867..d3aef9d 100644
--- a/compose/foundation/foundation/samples/src/main/java/androidx/compose/foundation/samples/DragAndDropSamples.kt
+++ b/compose/foundation/foundation/samples/src/main/java/androidx/compose/foundation/samples/DragAndDropSamples.kt
@@ -189,25 +189,24 @@
         modifier = Modifier
             .fillMaxSize()
             .dragAndDropTarget(
-                acceptDragAndDropTransfer = accept@{ startEvent ->
+                shouldStartDragAndDrop = accept@{ startEvent ->
                     val hasValidMimeType = startEvent.mimeTypes().any { eventMimeType ->
                         validMimeTypePrefixes.any(eventMimeType::startsWith)
                     }
-                    if (!hasValidMimeType) return@accept null
-
-                    DragAndDropTarget(
-                        onStarted = {
-                            backgroundColor = Color.DarkGray.copy(alpha = 0.2f)
-                        },
-                        onDropped = { event ->
-                            onDragAndDropEventDropped(event)
-                            true
-                        },
-                        onEnded = {
-                            backgroundColor = Color.Transparent
-                        }
-                    )
+                    hasValidMimeType
                 },
+                target = DragAndDropTarget(
+                    onStarted = {
+                        backgroundColor = Color.DarkGray.copy(alpha = 0.2f)
+                    },
+                    onDrop = { event ->
+                        onDragAndDropEventDropped(event)
+                        true
+                    },
+                    onEnded = {
+                        backgroundColor = Color.Transparent
+                    }
+                ),
             )
             .background(backgroundColor)
             .border(
@@ -414,41 +413,39 @@
 private fun Modifier.stateDropTarget(
     state: State
 ) = dragAndDropTarget(
-    acceptDragAndDropTransfer = { startEvent ->
-        if (!startEvent.mimeTypes().contains(ClipDescription.MIMETYPE_TEXT_INTENT))
-            return@dragAndDropTarget null
-
-        DragAndDropTarget(
-            onStarted = {
-                state.onStarted()
-            },
-            onEntered = {
-                println("Entered ${state.name}")
-                state.onEntered()
-            },
-            onMoved = {
-                println("Moved in ${state.name}")
-            },
-            onExited = {
-                println("Exited ${state.name}")
-                state.onExited()
-            },
-            onEnded = {
-                println("Ended in ${state.name}")
-                state.onEnded()
-            },
-            onDropped = { event ->
-                println("Dropped items in ${state.name}")
-                when (val transferredColor = event.toAndroidDragEvent().clipData.color()) {
-                    null -> false
-                    else -> {
-                        state.onDropped(transferredColor)
-                        true
-                    }
+    shouldStartDragAndDrop = { startEvent ->
+        startEvent.mimeTypes().contains(ClipDescription.MIMETYPE_TEXT_INTENT)
+    },
+    target = DragAndDropTarget(
+        onStarted = {
+            state.onStarted()
+        },
+        onEntered = {
+            println("Entered ${state.name}")
+            state.onEntered()
+        },
+        onMoved = {
+            println("Moved in ${state.name}")
+        },
+        onExited = {
+            println("Exited ${state.name}")
+            state.onExited()
+        },
+        onEnded = {
+            println("Ended in ${state.name}")
+            state.onEnded()
+        },
+        onDrop = { event ->
+            println("Dropped items in ${state.name}")
+            when (val transferredColor = event.toAndroidDragEvent().clipData.color()) {
+                null -> false
+                else -> {
+                    state.onDropped(transferredColor)
+                    true
                 }
             }
-        )
-    },
+        }
+    ),
 )
 
 @Stable
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/Draggable2DTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/Draggable2DTest.kt
index f0f36bd..b87f6d5 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/Draggable2DTest.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/Draggable2DTest.kt
@@ -695,8 +695,7 @@
         rule.setContent {
             val viewConfig = LocalViewConfiguration.current
             val newConfig = object : ViewConfiguration by viewConfig {
-                override val maximumFlingVelocity: Int
-                    get() = maxVelocity.toInt()
+                override val maximumFlingVelocity: Float get() = maxVelocity
             }
             CompositionLocalProvider(LocalViewConfiguration provides newConfig) {
                 Box {
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/DraggableTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/DraggableTest.kt
index 3a20285..4efffb4 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/DraggableTest.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/DraggableTest.kt
@@ -809,8 +809,7 @@
         rule.setContent {
             val viewConfig = LocalViewConfiguration.current
             val newConfig = object : ViewConfiguration by viewConfig {
-                override val maximumFlingVelocity: Int
-                    get() = maxVelocity.toInt()
+                override val maximumFlingVelocity: Float get() = maxVelocity
             }
             CompositionLocalProvider(LocalViewConfiguration provides newConfig) {
                 Box {
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/DetectDownAndDragGesturesWithObserverInitializationTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/DetectDownAndDragGesturesWithObserverInitializationTest.kt
index 163c7fb9f..b3f45a0 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/DetectDownAndDragGesturesWithObserverInitializationTest.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/DetectDownAndDragGesturesWithObserverInitializationTest.kt
@@ -30,7 +30,6 @@
 import androidx.compose.ui.platform.LocalViewConfiguration
 import androidx.compose.ui.platform.testTag
 import androidx.compose.ui.test.ExperimentalTestApi
-import androidx.compose.ui.test.TouchInjectionScope
 import androidx.compose.ui.test.junit4.createComposeRule
 import androidx.compose.ui.test.onNodeWithTag
 import androidx.compose.ui.test.performTouchInput
@@ -39,7 +38,6 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.MediumTest
 import com.google.common.truth.Truth.assertThat
-import kotlin.math.sign
 import kotlinx.coroutines.Dispatchers
 import org.junit.Before
 import org.junit.Rule
@@ -69,7 +67,8 @@
         rule.setContent {
             CompositionLocalProvider(
                 LocalViewConfiguration provides TestViewConfiguration(
-                    minimumTouchTargetSize = DpSize.Zero
+                    minimumTouchTargetSize = DpSize.Zero,
+                    touchSlop = Float.MIN_VALUE,
                 )
             ) {
                 Box(modifier = Modifier.fillMaxSize()) {
@@ -92,8 +91,8 @@
     fun whenPressingAndMoving_expectedInteractionsRecorded() {
         rule.onNodeWithTag(testTag).performTouchInput {
             down(center)
-            movePastSlopBy(Offset(1f, 1f))
-            movePastSlopBy(Offset(1f, 1f))
+            moveBy(Offset(1f, 1f))
+            moveBy(Offset(1f, 1f))
             up()
         }
 
@@ -104,14 +103,6 @@
             .inOrder()
     }
 
-    private fun TouchInjectionScope.movePastSlopBy(delta: Offset) {
-        val slop = Offset(
-            x = viewConfiguration.touchSlop * delta.x.sign,
-            y = viewConfiguration.touchSlop * delta.y.sign
-        )
-        moveBy(delta + slop)
-    }
-
     private inner class RecordingTextDragObserver : TextDragObserver {
         override fun onDown(point: Offset) = add("down")
         override fun onUp() = add("up")
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/selection/SelectionHandlePopupPositionTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/selection/SelectionHandlePopupPositionTest.kt
index f4f7884..7fcc2a7 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/selection/SelectionHandlePopupPositionTest.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/selection/SelectionHandlePopupPositionTest.kt
@@ -244,14 +244,13 @@
                     SimpleLayout {
                         Spacer(Modifier.size(parentSizeWidth, parentSizeHeight))
                         SelectionHandle(
-                            position = offset,
+                            offsetProvider = { offset },
                             isStartHandle = isStartHandle,
                             direction = ResolvedTextDirection.Ltr,
                             handlesCrossed = false,
                             modifier = Modifier.onGloballyPositioned {
                                 measureLatch.countDown()
                             },
-                            content = null
                         )
                     }
                 }
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/selection/SelectionHandleTestUtils.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/selection/SelectionHandleTestUtils.kt
index 1c512ca..291a74c 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/selection/SelectionHandleTestUtils.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/selection/SelectionHandleTestUtils.kt
@@ -17,9 +17,16 @@
 package androidx.compose.foundation.text.selection
 
 import androidx.compose.foundation.text.Handle
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.geometry.isSpecified
+import androidx.compose.ui.geometry.isUnspecified
+import androidx.compose.ui.semantics.SemanticsNode
 import androidx.compose.ui.semantics.getOrNull
 import androidx.compose.ui.test.SemanticsMatcher
 import androidx.compose.ui.test.SemanticsNodeInteraction
+import androidx.compose.ui.test.TouchInjectionScope
+import androidx.compose.ui.test.junit4.ComposeTestRule
+import androidx.compose.ui.test.performTouchInput
 import androidx.compose.ui.unit.Dp
 import com.google.common.truth.Truth.assertWithMessage
 
@@ -48,7 +55,7 @@
 ) {
     val node = fetchSemanticsNode()
     with(node.layoutInfo.density) {
-        val positionFound = node.config[SelectionHandleInfoKey].position
+        val positionFound = node.getSelectionHandleInfo().position
         val positionFoundX = positionFound.x.toDp()
         val positionFoundY = positionFound.y.toDp()
         val message = "Expected position ($expectedX, $expectedY), " +
@@ -63,9 +70,102 @@
 internal fun SemanticsNodeInteraction.assertHandleAnchorMatches(
     anchor: SelectionHandleAnchor
 ) {
-    val node = fetchSemanticsNode()
-    val actualAnchor = node.config[SelectionHandleInfoKey].anchor
-    val message = "Expected anchor ($anchor), " +
-        "but found ($actualAnchor)"
+    val actualAnchor = fetchSemanticsNode().getSelectionHandleInfo().anchor
+    val message = "Expected anchor ($anchor), but found ($actualAnchor)"
     assertWithMessage(message).that(actualAnchor).isEqualTo(anchor)
 }
+
+internal fun SemanticsNode.getSelectionHandleInfo(): SelectionHandleInfo {
+    val message = "Expected node to have SelectionHandleInfo."
+    assertWithMessage(message).that(SelectionHandleInfoKey in config).isTrue()
+    return config[SelectionHandleInfoKey]
+}
+
+internal fun ComposeTestRule.withHandlePressed(
+    handle: Handle,
+    block: HandlePressedScope.() -> Unit
+) {
+    onNode(isSelectionHandle(handle)).run {
+        assertExists()
+        performTouchInput { down(center) }
+        HandlePressedScope(this@withHandlePressed, this@run).block()
+        performTouchInput { up() }
+    }
+}
+
+// TODO(b/308691180) The necessity of this class is due to the test input mechanism using root
+//  coordinates as its base rather than screen coordinates. As the handle moves, so does the
+//  root, so we have to take into account the root changing positions into our gestures.
+//  Once the test input mechanism supports screen coordinates, this class should be able to be
+//  refactored to contain less logic or removed completely.
+internal class HandlePressedScope(
+    private val rule: ComposeTestRule,
+    private val interaction: SemanticsNodeInteraction,
+) {
+    // position of the popup before the previous move, relative to the container.
+    private var previousPopupPosition: Offset = Offset.Unspecified
+
+    // position of the gesture in progress, relative to the container.
+    private var gesturePosition: Offset = fetchHandleInfo().position
+
+    fun setInitialGesturePosition(initialGesturePosition: Offset) {
+        check(gesturePosition.isUnspecified) {
+            "Can't set the gesture position if it is already specified. " +
+                "You should only set the position if the handle is invisible " +
+                "and only after instantiation."
+        }
+        check(initialGesturePosition.isSpecified) {
+            "Can't initialize the gesture position to unspecified."
+        }
+        gesturePosition = initialGesturePosition
+    }
+
+    fun fetchHandleInfo(): SelectionHandleInfo =
+        interaction.fetchSemanticsNode().getSelectionHandleInfo()
+
+    fun moveHandleTo(containerOffset: Offset) {
+        checkGesturePositionSpecified()
+        val delta = containerOffset - gesturePosition
+        moveHandleBy(delta)
+    }
+
+    private fun moveHandleBy(delta: Offset) {
+        checkGesturePositionSpecified()
+        val currentPopupPosition = fetchHandleInfo().position
+        val popupDelta =
+            if (previousPopupPosition.isSpecified && currentPopupPosition.isSpecified) {
+                currentPopupPosition - previousPopupPosition
+            } else {
+                Offset.Zero
+            }
+
+        // Avoid updating previous position from specified to unspecified.
+        // In these cases, the handle disappears, but does not move positions,
+        // so we should maintain the previous position.
+        if (currentPopupPosition.isSpecified || previousPopupPosition.isUnspecified) {
+            previousPopupPosition = currentPopupPosition
+        }
+
+        // Since the underlying input framework uses the local coordinates of the gesture,
+        // and the window may have moved under it, we have to apply the window movement to our
+        // gesture as well to get the gesture to truly match our intention at the screen level.
+        val adjustedDelta = delta - popupDelta
+        performTouchInput {
+            moveBy(adjustedDelta)
+            gesturePosition += delta
+        }
+        rule.waitForIdle()
+    }
+
+    private fun checkGesturePositionSpecified() {
+        check(gesturePosition.isSpecified) {
+            "gesturePosition must be specified. If you try to move an invisible handle, " +
+                "you must specify the initialGesturePosition yourself (The last spot it was" +
+                "at before it turned invisible)."
+        }
+    }
+
+    private fun performTouchInput(block: TouchInjectionScope.() -> Unit) {
+        interaction.performTouchInput(block)
+    }
+}
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/selection/SelectionHandlesTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/selection/SelectionHandlesTest.kt
index 94e6daa6..fc5468b 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/selection/SelectionHandlesTest.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/selection/SelectionHandlesTest.kt
@@ -16,13 +16,24 @@
 
 package androidx.compose.foundation.text.selection
 
+import android.graphics.Bitmap
 import android.os.Build
+import androidx.annotation.RequiresApi
+import androidx.compose.foundation.background
+import androidx.compose.foundation.text.Handle
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.setValue
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.graphics.asAndroidBitmap
 import androidx.compose.ui.graphics.toArgb
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.test.SemanticsNodeInteraction
 import androidx.compose.ui.test.captureToImage
 import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithTag
 import androidx.compose.ui.test.onRoot
 import androidx.compose.ui.text.style.ResolvedTextDirection
 import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -41,44 +52,16 @@
     val rule = createComposeRule()
 
     private val handleColor = Color(0xFF4286F4)
-
-    private val selectionLtrHandleDirection = Selection(
-        start = Selection.AnchorInfo(
-            direction = ResolvedTextDirection.Ltr,
-            offset = 0,
-            selectableId = 0
-        ),
-        end = Selection.AnchorInfo(
-            direction = ResolvedTextDirection.Ltr,
-            offset = 0,
-            selectableId = 0
-        ),
-        handlesCrossed = false
-    )
-
-    private val selectionRtlHandleDirection = Selection(
-        start = Selection.AnchorInfo(
-            direction = ResolvedTextDirection.Ltr,
-            offset = 0,
-            selectableId = 0
-        ),
-        end = Selection.AnchorInfo(
-            direction = ResolvedTextDirection.Ltr,
-            offset = 0,
-            selectableId = 0
-        ),
-        handlesCrossed = true
-    )
+    private val backgroundColor = Color.White
 
     @Test
     @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
-    fun StartSelectionHandle_left_pointing() {
+    fun SelectionHandleIcon_left_pointsTopRight() {
         rule.setContent {
-            DefaultSelectionHandle(
-                modifier = Modifier,
-                isStartHandle = true,
-                direction = selectionLtrHandleDirection.start.direction,
-                handlesCrossed = selectionLtrHandleDirection.handlesCrossed
+            SelectionHandleIcon(
+                modifier = Modifier.background(backgroundColor),
+                iconVisible = { true },
+                isLeft = true,
             )
         }
 
@@ -86,19 +69,18 @@
         val bitmap = rule.onRoot().captureToImage().asAndroidBitmap()
         val pixelLeftTop = bitmap.getPixel(0, 0)
         val pixelRightTop = bitmap.getPixel(bitmap.width - 1, 0)
-        assertThat(pixelLeftTop).isNotEqualTo(handleColor.toArgb())
+        assertThat(pixelLeftTop).isEqualTo(backgroundColor.toArgb())
         assertThat(pixelRightTop).isEqualTo(handleColor.toArgb())
     }
 
     @Test
     @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
-    fun StartSelectionHandle_right_pointing() {
+    fun SelectionHandleIcon_right_pointsTopLeft() {
         rule.setContent {
-            DefaultSelectionHandle(
-                modifier = Modifier,
-                isStartHandle = true,
-                direction = selectionRtlHandleDirection.start.direction,
-                handlesCrossed = selectionRtlHandleDirection.handlesCrossed
+            SelectionHandleIcon(
+                modifier = Modifier.background(backgroundColor),
+                iconVisible = { true },
+                isLeft = false,
             )
         }
 
@@ -107,47 +89,90 @@
         val pixelLeftTop = bitmap.getPixel(0, 0)
         val pixelRightTop = bitmap.getPixel(bitmap.width - 1, 0)
         assertThat(pixelLeftTop).isEqualTo(handleColor.toArgb())
-        assertThat(pixelRightTop).isNotEqualTo(handleColor.toArgb())
+        assertThat(pixelRightTop).isEqualTo(backgroundColor.toArgb())
     }
 
     @Test
     @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
-    fun EndSelectionHandle_right_pointing() {
+    fun SelectionHandleIcon_left_notVisible() {
         rule.setContent {
-            DefaultSelectionHandle(
-                modifier = Modifier,
-                isStartHandle = false,
-                direction = selectionLtrHandleDirection.end.direction,
-                handlesCrossed = selectionLtrHandleDirection.handlesCrossed
+            SelectionHandleIcon(
+                modifier = Modifier.background(backgroundColor),
+                iconVisible = { false },
+                isLeft = true,
             )
         }
-
-        rule.waitForIdle()
-        val bitmap = rule.onRoot().captureToImage().asAndroidBitmap()
-        val pixelLeftTop = bitmap.getPixel(0, 0)
-        val pixelRightTop = bitmap.getPixel(bitmap.width - 1, 0)
-        assertThat(pixelLeftTop).isEqualTo(handleColor.toArgb())
-        assertThat(pixelRightTop).isNotEqualTo(handleColor.toArgb())
+        assertThat(rule.onRoot().uniquePixels()).containsExactly(backgroundColor.toArgb())
     }
 
     @Test
     @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
-    fun EndSelectionHandle_left_pointing() {
+    fun SelectionHandleIcon_right_notVisible() {
         rule.setContent {
-            DefaultSelectionHandle(
-                modifier = Modifier,
+            SelectionHandleIcon(
+                modifier = Modifier.background(backgroundColor),
+                iconVisible = { false },
+                isLeft = false,
+            )
+        }
+        assertThat(rule.onRoot().uniquePixels()).containsExactly(backgroundColor.toArgb())
+    }
+
+    /**
+     * When the offset changes to and from [Offset.Unspecified],
+     * we want to ensure that the semantics and visibility change as expected. If the
+     * semantics here aren't correct, many other tests will likely fail as well.
+     *
+     * If [Offset.Unspecified]: no SelectionHandleInfo semantics, not visible.
+     *
+     * else: SelectionHandleInfo semantics exists, is visible.
+     *
+     * The test tag should always be found because the layout will exist either way.
+     */
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+    @Test
+    fun DefaultSelectionHandle_visibilityAndSemantics_changesFromOffsetState() {
+        val tag = "testTag"
+        val specifiedOffset = Offset(5f, 5f)
+        var offsetState by mutableStateOf(specifiedOffset)
+        rule.setContent {
+            SelectionHandle(
+                modifier = Modifier
+                    .testTag(tag)
+                    .background(backgroundColor),
+                offsetProvider = { offsetState },
                 isStartHandle = false,
-                direction = selectionRtlHandleDirection.end.direction,
-                handlesCrossed = selectionRtlHandleDirection.handlesCrossed
+                direction = ResolvedTextDirection.Ltr,
+                handlesCrossed = false,
             )
         }
 
         rule.waitForIdle()
-        val bitmap = rule.onRoot().captureToImage().asAndroidBitmap()
-        val pixelLeftTop = bitmap.getPixel(0, 0)
-        val pixelRightTop = bitmap.getPixel(bitmap.width - 1, 0)
-        assertThat(pixelLeftTop).isNotEqualTo(handleColor.toArgb())
-        assertThat(pixelRightTop).isEqualTo(handleColor.toArgb())
+        val testNode = rule.onNodeWithTag(tag)
+        val startHandleNode = rule.onNode(isSelectionHandle(Handle.SelectionStart))
+        val endHandleNode = rule.onNode(isSelectionHandle(Handle.SelectionEnd))
+
+        testNode.assertExists()
+        assertThat(testNode.uniquePixels()).contains(handleColor.toArgb())
+        startHandleNode.assertDoesNotExist()
+        endHandleNode.assertExists()
+        endHandleNode.assertVisible(visible = true)
+
+        offsetState = Offset.Unspecified
+        rule.waitForIdle()
+        testNode.assertExists()
+        assertThat(testNode.uniquePixels()).containsExactly(backgroundColor.toArgb())
+        startHandleNode.assertDoesNotExist()
+        endHandleNode.assertExists()
+        endHandleNode.assertVisible(visible = false)
+
+        offsetState = specifiedOffset
+        rule.waitForIdle()
+        assertThat(testNode.uniquePixels()).contains(handleColor.toArgb())
+        testNode.assertExists()
+        startHandleNode.assertDoesNotExist()
+        endHandleNode.assertExists()
+        endHandleNode.assertVisible(visible = true)
     }
 
     @Test
@@ -182,3 +207,20 @@
         ).isTrue()
     }
 }
+
+private fun SemanticsNodeInteraction.assertVisible(visible: Boolean) {
+    val isVisible = fetchSemanticsNode().getSelectionHandleInfo().visible
+    assertThat(isVisible).let { if (visible) it.isTrue() else it.isFalse() }
+}
+
+@RequiresApi(Build.VERSION_CODES.O)
+private fun SemanticsNodeInteraction.uniquePixels(): Set<Int> =
+    captureToImage().asAndroidBitmap().uniquePixels()
+
+private fun Bitmap.uniquePixels(): Set<Int> = buildSet {
+    for (x in 0 until width) {
+        for (y in 0 until height) {
+            add(getPixel(x, y))
+        }
+    }
+}
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/selection/TextSelectionColorsScreenshotTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/selection/TextSelectionColorsScreenshotTest.kt
index 96980dac..31afacb 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/selection/TextSelectionColorsScreenshotTest.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/selection/TextSelectionColorsScreenshotTest.kt
@@ -38,7 +38,6 @@
 import androidx.compose.ui.test.performTouchInput
 import androidx.compose.ui.text.TextRange
 import androidx.compose.ui.text.input.TextFieldValue
-import androidx.compose.ui.text.style.ResolvedTextDirection
 import androidx.compose.ui.unit.dp
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.LargeTest
@@ -157,22 +156,20 @@
     CompositionLocalProvider(LocalTextSelectionColors provides textSelectionColors) {
         Row(Modifier.testTag(Tag), horizontalArrangement = Arrangement.spacedBy(20.dp)) {
             // Manually draw selection handles as we cannot screenshot the ones drawn in the popup
-            DefaultSelectionHandle(
+            SelectionHandleIcon(
                 modifier = Modifier,
-                isStartHandle = true,
-                direction = ResolvedTextDirection.Ltr,
-                handlesCrossed = false
+                iconVisible = { true },
+                isLeft = true,
             )
 
             SelectionContainer {
                 BasicText(Text)
             }
 
-            DefaultSelectionHandle(
+            SelectionHandleIcon(
                 modifier = Modifier,
-                isStartHandle = false,
-                direction = ResolvedTextDirection.Ltr,
-                handlesCrossed = false
+                iconVisible = { true },
+                isLeft = false,
             )
         }
     }
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/selection/gestures/AbstractSelectionGesturesTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/selection/gestures/AbstractSelectionGesturesTest.kt
index 5537b83..f2f16d3 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/selection/gestures/AbstractSelectionGesturesTest.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/selection/gestures/AbstractSelectionGesturesTest.kt
@@ -85,7 +85,8 @@
             CompositionLocalProvider(
                 LocalDensity provides density,
                 LocalViewConfiguration provides TestViewConfiguration(
-                    minimumTouchTargetSize = DpSize.Zero
+                    minimumTouchTargetSize = DpSize.Zero,
+                    touchSlop = Float.MIN_VALUE,
                 ),
                 LocalHapticFeedback provides hapticFeedback,
             ) {
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/selection/gestures/ClippedTextSelectionGesturesTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/selection/gestures/ClippedTextSelectionGesturesTest.kt
index f5403fb..911e0c1 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/selection/gestures/ClippedTextSelectionGesturesTest.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/selection/gestures/ClippedTextSelectionGesturesTest.kt
@@ -21,16 +21,18 @@
 import androidx.compose.foundation.layout.width
 import androidx.compose.foundation.text.BasicText
 import androidx.compose.foundation.text.Handle
+import androidx.compose.foundation.text.selection.HandlePressedScope
 import androidx.compose.foundation.text.selection.Selection
 import androidx.compose.foundation.text.selection.SelectionContainer
-import androidx.compose.foundation.text.selection.SelectionHandleInfoKey
 import androidx.compose.foundation.text.selection.fetchTextLayoutResult
 import androidx.compose.foundation.text.selection.gestures.util.SelectionSubject
 import androidx.compose.foundation.text.selection.gestures.util.TextSelectionAsserter
 import androidx.compose.foundation.text.selection.gestures.util.applyAndAssert
 import androidx.compose.foundation.text.selection.gestures.util.longPress
 import androidx.compose.foundation.text.selection.gestures.util.to
+import androidx.compose.foundation.text.selection.getSelectionHandleInfo
 import androidx.compose.foundation.text.selection.isSelectionHandle
+import androidx.compose.foundation.text.selection.withHandlePressed
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.ui.Alignment
@@ -38,11 +40,8 @@
 import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.geometry.Rect
 import androidx.compose.ui.platform.testTag
-import androidx.compose.ui.test.SemanticsNodeInteraction
-import androidx.compose.ui.test.TouchInjectionScope
 import androidx.compose.ui.test.longClick
 import androidx.compose.ui.test.onNodeWithTag
-import androidx.compose.ui.test.performTouchInput
 import androidx.compose.ui.text.TextStyle
 import androidx.compose.ui.text.style.TextOverflow
 import androidx.compose.ui.unit.dp
@@ -50,7 +49,6 @@
 import androidx.test.filters.MediumTest
 import androidx.test.filters.SdkSuppress
 import com.google.common.truth.Truth
-import kotlin.math.sign
 import kotlin.test.fail
 import org.junit.Before
 import org.junit.Test
@@ -176,13 +174,85 @@
             hapticsCount++
         }
 
+        // TODO(grantapher) Need a horizontal move for the selection to shrink.
+        //  Remove when this behavior changes or it is determined to be okay to keep.
+        touchDragTo(position = characterPosition(4))
+        // drag back to ensure the gesture continues on
+        touchDragTo(position = characterPosition(2))
+        asserter.applyAndAssert {
+            selection = 0 to 2
+            endSelectionHandleShown = true
+            magnifierShown = true
+            hapticsCount++
+        }
+
         performTouchGesture { up() }
         asserter.applyAndAssert {
+            magnifierShown = false
             textToolbarShown = true
         }
     }
 
-    // Test is irrelevant if the end handle is not hidden, which is the case below api 26.
+    @SdkSuppress(minSdkVersion = 26)
+    @Test
+    fun whenDragEndHandleOutOfBounds_selectionAndHandleUpdates() {
+        maxLinesState.value = 1
+        overflowState.value = TextOverflow.Clip
+        rule.waitForIdle()
+        asserter.assert()
+
+        performTouchGesture { longPress(characterPosition(offset = 4)) }
+        asserter.applyAndAssert {
+            selection = 0 to text.length
+            startSelectionHandleShown = true
+            hapticsCount++
+        }
+
+        touchDragTo(characterPosition(offset = 2))
+        asserter.applyAndAssert {
+            selection = 0 to 2
+            magnifierShown = true
+            endSelectionHandleShown = true
+        }
+
+        performTouchGesture { up() }
+        asserter.applyAndAssert {
+            magnifierShown = false
+            textToolbarShown = true
+        }
+
+        rule.withHandlePressed(Handle.SelectionEnd) {
+            asserter.applyAndAssert {
+                magnifierShown = true
+                textToolbarShown = false
+            }
+
+            moveHandleTo(bottomEnd)
+            asserter.applyAndAssert {
+                selection = 0 to text.length
+                magnifierShown = false
+                endSelectionHandleShown = false
+                hapticsCount++
+            }
+
+            // TODO(grantapher) Need a horizontal move for the selection to shrink.
+            //  Remove when this behavior changes or it is determined to be okay to keep.
+            moveHandleToCharacter(characterOffset = 4)
+            moveHandleToCharacter(characterOffset = 2)
+            asserter.applyAndAssert {
+                selection = 0 to 2
+                magnifierShown = true
+                endSelectionHandleShown = true
+                hapticsCount++
+            }
+        }
+
+        asserter.applyAndAssert {
+            magnifierShown = false
+            textToolbarShown = true
+        }
+    }
+
     @SdkSuppress(minSdkVersion = 26)
     @Test
     fun whenDragStartHandle_withNoEndHandle_selectionAndHandleUpdates() {
@@ -199,43 +269,107 @@
             hapticsCount++
         }
 
-        withHandlePressed(Handle.SelectionStart) {
+        rule.withHandlePressed(Handle.SelectionStart) {
+            asserter.applyAndAssert {
+                magnifierShown = true
+                textToolbarShown = false
+            }
+
             moveHandleToCharacter(characterOffset = 2)
+            asserter.applyAndAssert {
+                selection = 2 to text.length
+                magnifierShown = true
+                hapticsCount++
+            }
+
+            moveHandleTo(bottomEnd)
+            asserter.applyAndAssert {
+                selection = (text.length - 1) to text.length
+                magnifierShown = false
+                startSelectionHandleShown = false
+                hapticsCount++
+            }
+
+            // TODO(grantapher) Need a horizontal move for the selection to shrink.
+            //  Remove when this behavior changes or it is determined to be okay to keep.
+            moveHandleToCharacter(characterOffset = 0)
+            moveHandleToCharacter(characterOffset = 2)
+            asserter.applyAndAssert {
+                selection = 2 to text.length
+                magnifierShown = true
+                startSelectionHandleShown = true
+                hapticsCount++
+            }
         }
+
         asserter.applyAndAssert {
-            selection = 2 to text.length
+            magnifierShown = false
+            textToolbarShown = true
+        }
+    }
+
+    @SdkSuppress(minSdkVersion = 26)
+    @Test
+    fun whenDragInvisibleEndHandle_noSelectionChanges() {
+        maxLinesState.value = 1
+        overflowState.value = TextOverflow.Clip
+        rule.waitForIdle()
+        asserter.assert()
+
+        performTouchGesture { longPress(characterPosition(offset = 4)) }
+        asserter.applyAndAssert {
+            selection = 0 to text.length
+            startSelectionHandleShown = true
             hapticsCount++
         }
+
+        val offsetTwoPosition = characterPosition(offset = 2)
+        touchDragTo(offsetTwoPosition)
+        asserter.applyAndAssert {
+            selection = 0 to 2
+            magnifierShown = true
+            endSelectionHandleShown = true
+        }
+
+        // last position where the handle is shown
+        val initialPosition = rule.onNode(isSelectionHandle(Handle.SelectionEnd))
+            .fetchSemanticsNode()
+            .getSelectionHandleInfo()
+            .position
+
+        // drag straight down, out of text bounds
+        touchDragTo(offsetTwoPosition.copy(y = bottomEnd.y))
+        asserter.applyAndAssert {
+            selection = 0 to text.length
+            magnifierShown = false
+            endSelectionHandleShown = false
+        }
+
+        performTouchGesture { up() }
+        asserter.applyAndAssert {
+            textToolbarShown = true
+        }
+
+        rule.withHandlePressed(Handle.SelectionEnd) {
+            setInitialGesturePosition(initialPosition)
+            asserter.assert()
+            moveHandleToCharacter(4)
+            asserter.assert()
+            moveHandleToCharacter(2)
+            asserter.assert()
+        }
+        asserter.assert()
     }
 
-    private fun withHandlePressed(
-        handle: Handle,
-        block: SemanticsNodeInteraction.() -> Unit
-    ): SemanticsNodeInteraction = rule.onNode(isSelectionHandle(handle)).run {
-        performTouchInput { down(center) }
-        block()
-        performTouchInput { up() }
-    }
-
-    private fun SemanticsNodeInteraction.moveHandleToCharacter(characterOffset: Int) {
-        val selectionHandleInfo = fetchSemanticsNode().config[SelectionHandleInfoKey]
+    private fun HandlePressedScope.moveHandleToCharacter(characterOffset: Int) {
         val destinationPosition = characterBox(characterOffset).run {
-            when (selectionHandleInfo.handle) {
-                Handle.SelectionStart -> bottomLeft
-                Handle.SelectionEnd -> bottomRight
+            when (fetchHandleInfo().handle) {
+                Handle.SelectionStart -> bottomLeft.nudge(HorizontalDirection.END)
+                Handle.SelectionEnd -> bottomLeft.nudge(HorizontalDirection.START)
                 Handle.Cursor -> fail("Unexpected handle ${Handle.Cursor}")
             }
         }
-        val delta = destinationPosition - selectionHandleInfo.position
-        performTouchInput { movePastSlopBy(delta) }
-    }
-
-    private fun TouchInjectionScope.movePastSlopBy(delta: Offset) {
-        val slop = Offset(
-            x = viewConfiguration.touchSlop * delta.x.sign,
-            y = viewConfiguration.touchSlop * delta.y.sign
-        )
-        moveBy(delta + slop)
+        moveHandleTo(destinationPosition)
     }
 
     private fun characterPosition(offset: Int): Offset =
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/selection/gestures/LazyColumnMultiTextRegressionTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/selection/gestures/LazyColumnMultiTextRegressionTest.kt
index 4aa35e8..6c4fadc 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/selection/gestures/LazyColumnMultiTextRegressionTest.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/selection/gestures/LazyColumnMultiTextRegressionTest.kt
@@ -27,6 +27,7 @@
 import androidx.compose.foundation.text.selection.SelectionContainer
 import androidx.compose.foundation.text.selection.SelectionHandleInfoKey
 import androidx.compose.foundation.text.selection.fetchTextLayoutResult
+import androidx.compose.foundation.text.selection.gestures.util.assertSelectionHandlesShown
 import androidx.compose.foundation.text.selection.gestures.util.longPress
 import androidx.compose.foundation.text.selection.isSelectionHandle
 import androidx.compose.runtime.CompositionLocalProvider
@@ -118,45 +119,37 @@
             prevEnd = endHandlePosition
         }
 
-        assertHandleNotShown(Handle.SelectionStart)
-        assertHandleNotShown(Handle.SelectionEnd)
+        rule.assertSelectionHandlesShown(startShown = false, endShown = false)
 
         createSelection(startLine = 1, endLine = 3)
-        assertHandleShown(Handle.SelectionStart)
-        assertHandleShown(Handle.SelectionEnd)
+        rule.assertSelectionHandlesShown(startShown = true, endShown = true)
         updateHandlePositions()
 
         scrollLines(fromLine = 3, toLine = 2)
-        assertHandleShown(Handle.SelectionStart)
-        assertHandleShown(Handle.SelectionEnd)
+        rule.assertSelectionHandlesShown(startShown = true, endShown = true)
         assertPositionMovedUp(prevStart, startHandlePosition)
         assertPositionMovedUp(prevEnd, endHandlePosition)
         updateHandlePositions()
 
         scrollLines(fromLine = 4, toLine = 2)
-        assertHandleNotShown(Handle.SelectionStart)
-        assertHandleShown(Handle.SelectionEnd)
+        rule.assertSelectionHandlesShown(startShown = false, endShown = true)
         assertPositionMovedUp(prevEnd, endHandlePosition)
 
         scrollLines(fromLine = 6, toLine = 4)
-        assertHandleNotShown(Handle.SelectionStart)
-        assertHandleNotShown(Handle.SelectionEnd)
+        rule.assertSelectionHandlesShown(startShown = false, endShown = false)
         updateHandlePositions()
 
         scrollLines(fromLine = 6, toLine = 8)
-        assertHandleNotShown(Handle.SelectionStart)
-        assertHandleShown(Handle.SelectionEnd)
+        rule.assertSelectionHandlesShown(startShown = false, endShown = true)
         updateHandlePositions()
 
         scrollLines(fromLine = 4, toLine = 6)
-        assertHandleShown(Handle.SelectionStart)
-        assertHandleShown(Handle.SelectionEnd)
+        rule.assertSelectionHandlesShown(startShown = true, endShown = true)
         assertHandleMovedDown(prevEnd, endHandlePosition)
         updateHandlePositions()
 
         scrollLines(fromLine = 2, toLine = 5)
-        assertHandleShown(Handle.SelectionStart)
-        assertHandleShown(Handle.SelectionEnd)
+        rule.assertSelectionHandlesShown(startShown = true, endShown = true)
         assertHandleMovedDown(prevStart, startHandlePosition)
         assertHandleMovedDown(prevEnd, endHandlePosition)
         updateHandlePositions()
@@ -349,14 +342,6 @@
                 ?.get(SelectionHandleInfoKey)
                 ?.position
 
-        fun assertHandleShown(handle: Handle) {
-            rule.onNode(isSelectionHandle(handle)).assertExists()
-        }
-
-        fun assertHandleNotShown(handle: Handle) {
-            rule.onNode(isSelectionHandle(handle)).assertDoesNotExist()
-        }
-
         fun assertTextToolbarTopAt(y: Float) {
             assertThat(textToolbarRect?.top)
                 .isWithin(0.1f)
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/selection/gestures/util/TextSelectionTestUtils.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/selection/gestures/util/TextSelectionTestUtils.kt
index 67b4db6..f30870d 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/selection/gestures/util/TextSelectionTestUtils.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/selection/gestures/util/TextSelectionTestUtils.kt
@@ -20,6 +20,7 @@
 import androidx.compose.foundation.isPlatformMagnifierSupported
 import androidx.compose.foundation.text.Handle
 import androidx.compose.foundation.text.selection.Selection
+import androidx.compose.foundation.text.selection.getSelectionHandleInfo
 import androidx.compose.foundation.text.selection.isSelectionHandle
 import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.geometry.isSpecified
@@ -32,16 +33,49 @@
 import androidx.compose.ui.test.junit4.ComposeTestRule
 import androidx.compose.ui.text.TextRange
 import androidx.compose.ui.text.style.ResolvedTextDirection
-import com.google.common.truth.Truth
+import com.google.common.truth.Truth.assertThat
+import com.google.common.truth.Truth.assertWithMessage
+import kotlin.test.fail
 
-private fun ComposeTestRule.assertSelectionHandlesShown(startShown: Boolean, endShown: Boolean) {
+internal fun ComposeTestRule.assertSelectionHandlesShown(startShown: Boolean, endShown: Boolean) {
     listOf(Handle.SelectionStart to startShown, Handle.SelectionEnd to endShown)
         .forEach { (handle, shown) ->
-            val node = onNode(isSelectionHandle(handle))
-            if (shown) node.assertExists() else node.assertDoesNotExist()
+            if (shown) {
+                assertHandleShown(handle)
+            } else {
+                assertHandleNotShown(handle)
+            }
         }
 }
 
+private fun ComposeTestRule.assertHandleShown(handle: Handle) {
+    val node = onNode(isSelectionHandle(handle))
+    node.assertExists()
+    val info = node.fetchSemanticsNode().getSelectionHandleInfo()
+    val message = "Handle exists but is not visible."
+    assertWithMessage(message).that(info.visible).isTrue()
+}
+
+private fun ComposeTestRule.assertHandleNotShown(handle: Handle) {
+    val nodes = onAllNodes(isSelectionHandle(handle)).fetchSemanticsNodes()
+    when (nodes.size) {
+        0 -> {
+            // There are no handles, so this passes
+        }
+
+        1 -> {
+            // verify the single handle is not visible
+            val info = nodes.single().getSelectionHandleInfo()
+            val message = "Handle exists and is visible."
+            assertWithMessage(message).that(info.visible).isFalse()
+        }
+
+        else -> {
+            fail("Found multiple $handle handles.")
+        }
+    }
+}
+
 private fun ComposeTestRule.assertMagnifierShown(shown: Boolean) {
     if (!isPlatformMagnifierSupported()) return
 
@@ -54,19 +88,19 @@
                 .isSpecified
         }
 
-    Truth.assertWithMessage("Magnifier should${if (shown) " " else " not "}be shown.")
+    assertWithMessage("Magnifier should${if (shown) " " else " not "}be shown.")
         .that(magShown)
         .isEqualTo(shown)
 }
 
 private fun TextToolbar.assertShown(shown: Boolean = true) {
-    Truth.assertWithMessage("Text toolbar status was not as expected.")
+    assertWithMessage("Text toolbar status was not as expected.")
         .that(status)
         .isEqualTo(if (shown) TextToolbarStatus.Shown else TextToolbarStatus.Hidden)
 }
 
 private fun FakeHapticFeedback.assertPerformedAtLeastThenClear(times: Int) {
-    Truth.assertThat(invocationCountMap[HapticFeedbackType.TextHandleMove] ?: 0).isAtLeast(times)
+    assertThat(invocationCountMap[HapticFeedbackType.TextHandleMove] ?: 0).isAtLeast(times)
     invocationCountMap.clear()
 }
 
@@ -90,12 +124,14 @@
      * Overridden by [startSelectionHandleShown] and [endSelectionHandleShown].
      */
     var selectionHandlesShown = false
+
     /**
      * Set to true if the start handle is expected to be shown.
      *
      * Overrides [selectionHandlesShown] unless this is `null`.
      */
     var startSelectionHandleShown: Boolean? = null
+
     /**
      * Set to true if the end handle is expected to be shown.
      *
diff --git a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/AndroidCursorHandle.android.kt b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/AndroidCursorHandle.android.kt
index c5dacb6..3cd2d0f 100644
--- a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/AndroidCursorHandle.android.kt
+++ b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/AndroidCursorHandle.android.kt
@@ -42,7 +42,7 @@
     content: @Composable (() -> Unit)?
 ) {
     HandlePopup(
-        position = handlePosition,
+        positionProvider = { handlePosition },
         handleReferencePoint = HandleReferencePoint.TopMiddle
     ) {
         if (content == null) {
diff --git a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/selection/AndroidSelectionHandles.android.kt b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/selection/AndroidSelectionHandles.android.kt
index f9f10a3..4487429dc 100644
--- a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/selection/AndroidSelectionHandles.android.kt
+++ b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/selection/AndroidSelectionHandles.android.kt
@@ -18,11 +18,15 @@
 
 import androidx.compose.foundation.layout.Spacer
 import androidx.compose.foundation.layout.size
-import androidx.compose.foundation.text.Handle
+import androidx.compose.foundation.text.Handle.SelectionEnd
+import androidx.compose.foundation.text.Handle.SelectionStart
 import androidx.compose.foundation.text.selection.HandleReferencePoint.TopLeft
 import androidx.compose.foundation.text.selection.HandleReferencePoint.TopMiddle
 import androidx.compose.foundation.text.selection.HandleReferencePoint.TopRight
+import androidx.compose.foundation.text.selection.SelectionHandleAnchor.Left
+import androidx.compose.foundation.text.selection.SelectionHandleAnchor.Right
 import androidx.compose.runtime.Composable
+import androidx.compose.runtime.CompositionLocalProvider
 import androidx.compose.runtime.remember
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.composed
@@ -30,6 +34,8 @@
 import androidx.compose.ui.draw.drawWithCache
 import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.geometry.Size
+import androidx.compose.ui.geometry.isSpecified
+import androidx.compose.ui.geometry.takeOrElse
 import androidx.compose.ui.graphics.BlendMode
 import androidx.compose.ui.graphics.Canvas
 import androidx.compose.ui.graphics.Color
@@ -38,109 +44,94 @@
 import androidx.compose.ui.graphics.ImageBitmapConfig
 import androidx.compose.ui.graphics.drawscope.CanvasDrawScope
 import androidx.compose.ui.graphics.drawscope.scale
+import androidx.compose.ui.platform.LocalViewConfiguration
 import androidx.compose.ui.semantics.semantics
 import androidx.compose.ui.text.style.ResolvedTextDirection
 import androidx.compose.ui.unit.IntOffset
 import androidx.compose.ui.unit.IntRect
 import androidx.compose.ui.unit.IntSize
 import androidx.compose.ui.unit.LayoutDirection
+import androidx.compose.ui.unit.round
 import androidx.compose.ui.window.Popup
 import androidx.compose.ui.window.PopupPositionProvider
 import androidx.compose.ui.window.PopupProperties
 import kotlin.math.ceil
-import kotlin.math.roundToInt
 
 @Composable
 internal actual fun SelectionHandle(
-    position: Offset,
+    offsetProvider: OffsetProvider,
     isStartHandle: Boolean,
     direction: ResolvedTextDirection,
     handlesCrossed: Boolean,
     modifier: Modifier,
-    content: @Composable (() -> Unit)?
 ) {
     val isLeft = isLeft(isStartHandle, direction, handlesCrossed)
     // The left selection handle's top right is placed at the given position, and vice versa.
-    val handleReferencePoint = if (isLeft) {
-        HandleReferencePoint.TopRight
-    } else {
-        HandleReferencePoint.TopLeft
-    }
+    val handleReferencePoint = if (isLeft) TopRight else TopLeft
 
-    HandlePopup(position = position, handleReferencePoint = handleReferencePoint) {
-        if (content == null) {
-            DefaultSelectionHandle(
-                modifier = modifier
-                    .semantics {
-                        this[SelectionHandleInfoKey] = SelectionHandleInfo(
-                            handle = if (isStartHandle) {
-                                Handle.SelectionStart
-                            } else {
-                                Handle.SelectionEnd
-                            },
-                            position = position,
-                            anchor = if (isLeft) {
-                                SelectionHandleAnchor.Left
-                            } else {
-                                SelectionHandleAnchor.Right
-                            }
-                        )
-                    },
-                isStartHandle = isStartHandle,
-                direction = direction,
-                handlesCrossed = handlesCrossed
+    // Propagate the view configuration to the popup.
+    val viewConfiguration = LocalViewConfiguration.current
+    HandlePopup(positionProvider = offsetProvider, handleReferencePoint = handleReferencePoint) {
+        CompositionLocalProvider(LocalViewConfiguration provides viewConfiguration) {
+            SelectionHandleIcon(
+                modifier = modifier.semantics {
+                    val position = offsetProvider.provide()
+                    this[SelectionHandleInfoKey] = SelectionHandleInfo(
+                        handle = if (isStartHandle) SelectionStart else SelectionEnd,
+                        position = position,
+                        anchor = if (isLeft) Left else Right,
+                        visible = position.isSpecified,
+                    )
+                },
+                iconVisible = { offsetProvider.provide().isSpecified },
+                isLeft = isLeft,
             )
-        } else {
-            content()
         }
     }
 }
 
 @Composable
 /*@VisibleForTesting*/
-internal fun DefaultSelectionHandle(
+internal fun SelectionHandleIcon(
     modifier: Modifier,
-    isStartHandle: Boolean,
-    direction: ResolvedTextDirection,
-    handlesCrossed: Boolean
+    iconVisible: () -> Boolean,
+    isLeft: Boolean,
 ) {
     Spacer(
-        modifier.size(HandleWidth, HandleHeight)
-            .drawSelectionHandle(isStartHandle, direction, handlesCrossed)
+        modifier
+            .size(HandleWidth, HandleHeight)
+            .drawSelectionHandle(iconVisible, isLeft)
     )
 }
 
 internal fun Modifier.drawSelectionHandle(
-    isStartHandle: Boolean,
-    direction: ResolvedTextDirection,
-    handlesCrossed: Boolean
-) = composed {
+    iconVisible: () -> Boolean,
+    isLeft: Boolean
+): Modifier = composed {
     val handleColor = LocalTextSelectionColors.current.handleColor
-    this.then(
-        Modifier.drawWithCache {
-            val radius = size.width / 2f
-            val handleImage = createHandleImage(radius)
-            val colorFilter = ColorFilter.tint(handleColor)
-            onDrawWithContent {
-                drawContent()
-                val isLeft = isLeft(isStartHandle, direction, handlesCrossed)
-                if (isLeft) {
-                    // Flip the selection handle horizontally.
-                    scale(scaleX = -1f, scaleY = 1f) {
-                        drawImage(
-                            image = handleImage,
-                            colorFilter = colorFilter
-                        )
-                    }
-                } else {
+    this.drawWithCache {
+        val radius = size.width / 2f
+        val handleImage = createHandleImage(radius)
+        val colorFilter = ColorFilter.tint(handleColor)
+        onDrawWithContent {
+            drawContent()
+            if (!iconVisible()) return@onDrawWithContent
+            if (isLeft) {
+                // Flip the selection handle horizontally.
+                scale(scaleX = -1f, scaleY = 1f) {
                     drawImage(
                         image = handleImage,
                         colorFilter = colorFilter
                     )
                 }
+            } else {
+                drawImage(
+                    image = handleImage,
+                    colorFilter = colorFilter
+                )
             }
         }
-    )
+    }
 }
 
 /**
@@ -227,23 +218,17 @@
 
 @Composable
 internal fun HandlePopup(
-    position: Offset,
+    positionProvider: OffsetProvider,
     handleReferencePoint: HandleReferencePoint,
     content: @Composable () -> Unit
 ) {
-    val intOffset = IntOffset(position.x.roundToInt(), position.y.roundToInt())
-
-    val popupPositioner = remember(handleReferencePoint, intOffset) {
-        HandlePositionProvider(handleReferencePoint, intOffset)
+    val popupPositionProvider = remember(handleReferencePoint, positionProvider) {
+        HandlePositionProvider(handleReferencePoint, positionProvider)
     }
-
     Popup(
-        popupPositionProvider = popupPositioner,
-        properties = PopupProperties(
-            excludeFromSystemGesture = true,
-            clippingEnabled = false
-        ),
-        content = content
+        popupPositionProvider = popupPositionProvider,
+        properties = PopupProperties(excludeFromSystemGesture = true, clippingEnabled = false),
+        content = content,
     )
 }
 
@@ -264,38 +249,45 @@
 
 /**
  * This [PopupPositionProvider] for [HandlePopup]. It will position the selection handle
- * to the [offset] in its anchor layout.
+ * to the result of [positionProvider] in its anchor layout.
  *
  * @see HandleReferencePoint
  */
-/*@VisibleForTesting*/
 internal class HandlePositionProvider(
     private val handleReferencePoint: HandleReferencePoint,
-    private val offset: IntOffset
+    private val positionProvider: OffsetProvider
 ) : PopupPositionProvider {
+
+    /**
+     * When Handle disappears, it starts reporting its position as [Offset.Unspecified]. Normally,
+     * Popup is dismissed immediately when its position becomes unspecified, but for one frame a
+     * position update might be requested by soon-to-be-destroyed Popup. In this case, report the
+     * last known position as there are no more updates. If the first ever position is provided as
+     * unspecified, start with [Offset.Zero] default.
+     */
+    private var prevPosition: Offset = Offset.Zero
+
     override fun calculatePosition(
         anchorBounds: IntRect,
         windowSize: IntSize,
         layoutDirection: LayoutDirection,
         popupContentSize: IntSize
     ): IntOffset {
-        return when (handleReferencePoint) {
-            HandleReferencePoint.TopLeft ->
-                IntOffset(
-                    x = anchorBounds.left + offset.x,
-                    y = anchorBounds.top + offset.y
-                )
-            HandleReferencePoint.TopRight ->
-                IntOffset(
-                    x = anchorBounds.left + offset.x - popupContentSize.width,
-                    y = anchorBounds.top + offset.y
-                )
-            HandleReferencePoint.TopMiddle ->
-                IntOffset(
-                    x = anchorBounds.left + offset.x - popupContentSize.width / 2,
-                    y = anchorBounds.top + offset.y
-                )
+        val position = positionProvider.provide().takeOrElse { prevPosition }
+        prevPosition = position
+
+        // We want the cursor to point to the position,
+        // so adjust the x-axis based on where the handle is pointing.
+        val xAdjustment = when (handleReferencePoint) {
+            TopLeft -> 0
+            TopMiddle -> popupContentSize.width / 2
+            TopRight -> popupContentSize.width
         }
+
+        val offset = position.round()
+        val x = anchorBounds.left + offset.x - xAdjustment
+        val y = anchorBounds.top + offset.y
+        return IntOffset(x, y)
     }
 }
 
diff --git a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text2/input/internal/selection/TextFieldSelectionHandles.android.kt b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text2/input/internal/selection/TextFieldSelectionHandles.android.kt
deleted file mode 100644
index 8ee5391..0000000
--- a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text2/input/internal/selection/TextFieldSelectionHandles.android.kt
+++ /dev/null
@@ -1,152 +0,0 @@
-/*
- * Copyright 2023 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.foundation.text2.input.internal.selection
-
-import androidx.compose.foundation.text.Handle
-import androidx.compose.foundation.text.selection.DefaultSelectionHandle
-import androidx.compose.foundation.text.selection.HandleReferencePoint
-import androidx.compose.foundation.text.selection.SelectionHandleAnchor
-import androidx.compose.foundation.text.selection.SelectionHandleInfo
-import androidx.compose.foundation.text.selection.SelectionHandleInfoKey
-import androidx.compose.foundation.text.selection.isLeft
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.remember
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.geometry.Offset
-import androidx.compose.ui.geometry.takeOrElse
-import androidx.compose.ui.semantics.semantics
-import androidx.compose.ui.text.style.ResolvedTextDirection
-import androidx.compose.ui.unit.IntOffset
-import androidx.compose.ui.unit.IntRect
-import androidx.compose.ui.unit.IntSize
-import androidx.compose.ui.unit.LayoutDirection
-import androidx.compose.ui.unit.round
-import androidx.compose.ui.window.Popup
-import androidx.compose.ui.window.PopupPositionProvider
-import androidx.compose.ui.window.PopupProperties
-
-@Composable
-internal actual fun TextFieldSelectionHandle2(
-    positionProvider: OffsetProvider,
-    isStartHandle: Boolean,
-    direction: ResolvedTextDirection,
-    handlesCrossed: Boolean,
-    modifier: Modifier
-) {
-    val isLeft = isLeft(isStartHandle, direction, handlesCrossed)
-    // The left selection handle's top right is placed at the given position, and vice versa.
-    val handleReferencePoint = if (isLeft) {
-        HandleReferencePoint.TopRight
-    } else {
-        HandleReferencePoint.TopLeft
-    }
-
-    HandlePopup2(
-        positionProvider = positionProvider,
-        handleReferencePoint = handleReferencePoint
-    ) {
-        DefaultSelectionHandle(
-            modifier = modifier
-                .semantics {
-                    this[SelectionHandleInfoKey] = SelectionHandleInfo(
-                        handle = if (isStartHandle) {
-                            Handle.SelectionStart
-                        } else {
-                            Handle.SelectionEnd
-                        },
-                        position = positionProvider.provide(),
-                        anchor = if (isLeft) {
-                            SelectionHandleAnchor.Left
-                        } else {
-                            SelectionHandleAnchor.Right
-                        }
-                    )
-                },
-            isStartHandle = isStartHandle,
-            direction = direction,
-            handlesCrossed = handlesCrossed
-        )
-    }
-}
-
-/**
- * An alternative HandlePopup API that uses dynamic positioning. This enables us to update the
- * handle position when onGloballyPositioned is called.
- */
-@Composable
-internal fun HandlePopup2(
-    positionProvider: OffsetProvider,
-    handleReferencePoint: HandleReferencePoint,
-    content: @Composable () -> Unit
-) {
-    val popupPositioner = remember(handleReferencePoint, positionProvider) {
-        HandlePositionProvider2(handleReferencePoint, positionProvider)
-    }
-
-    Popup(
-        popupPositionProvider = popupPositioner,
-        properties = PopupProperties(
-            excludeFromSystemGesture = true,
-            clippingEnabled = false
-        ),
-        content = content
-    )
-}
-
-internal class HandlePositionProvider2(
-    private val handleReferencePoint: HandleReferencePoint,
-    private val positionProvider: OffsetProvider
-) : PopupPositionProvider {
-
-    /**
-     * When Handle disappears, it starts reporting its position as [Offset.Unspecified]. Normally,
-     * Popup is dismissed immediately when its position becomes unspecified, but for one frame a
-     * position update might be requested by soon-to-be-destroyed Popup. In this case, report the
-     * last known position as there are no more updates. If the first ever position is provided as
-     * unspecified, start with [Offset.Zero] default.
-     */
-    private var prevPosition: Offset = Offset.Zero
-
-    override fun calculatePosition(
-        anchorBounds: IntRect,
-        windowSize: IntSize,
-        layoutDirection: LayoutDirection,
-        popupContentSize: IntSize
-    ): IntOffset {
-        val position = positionProvider.provide().takeOrElse { prevPosition }
-        prevPosition = position
-        val intOffset = position.round()
-
-        return when (handleReferencePoint) {
-            HandleReferencePoint.TopLeft ->
-                IntOffset(
-                    x = anchorBounds.left + intOffset.x,
-                    y = anchorBounds.top + intOffset.y
-                )
-            HandleReferencePoint.TopRight ->
-                IntOffset(
-                    x = anchorBounds.left + intOffset.x - popupContentSize.width,
-                    y = anchorBounds.top + intOffset.y
-                )
-            HandleReferencePoint.TopMiddle ->
-                IntOffset(
-                    x = anchorBounds.left + intOffset.x - popupContentSize.width / 2,
-                    y = anchorBounds.top + intOffset.y
-                )
-        }
-    }
-}
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/draganddrop/DragAndDropTarget.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/draganddrop/DragAndDropTarget.kt
index 3052410..37b5001 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/draganddrop/DragAndDropTarget.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/draganddrop/DragAndDropTarget.kt
@@ -33,60 +33,73 @@
  *
  * @sample androidx.compose.foundation.samples.TextDragAndDropTargetSample
  *
- * @param acceptDragAndDropTransfer Allows the Composable to decide if it wants to receive from
- * a given drag and drop session by returning a viable [DragAndDropTarget].
+ * @param shouldStartDragAndDrop Allows the Composable to decide if it wants to receive from
+ * a given drag and drop session by inspecting the [DragAndDropEvent] that started the session.
  *
- * returning a [DragAndDropTarget] instance indicates interest in a drag and drop session,
- * null indicates no interest.
+ * @param target The [DragAndDropTarget] that will receive events for a given drag and drop
+ * session.
  *
  * All drag and drop target modifiers in the hierarchy will be given an opportunity to participate
- * in a given drag and drop session.
+ * in a given drag and drop session via [shouldStartDragAndDrop].
  *
  * @see [DragAndDropModifierNode.acceptDragAndDropTransfer]
  */
 @ExperimentalFoundationApi
 fun Modifier.dragAndDropTarget(
-    acceptDragAndDropTransfer: (event: DragAndDropEvent) -> DragAndDropTarget?,
+    shouldStartDragAndDrop: (startEvent: DragAndDropEvent) -> Boolean,
+    target: DragAndDropTarget,
 ): Modifier = this then DropTargetElement(
-    acceptsDragAndDropTransaction = acceptDragAndDropTransfer,
+    target = target,
+    shouldStartDragAndDrop = shouldStartDragAndDrop,
 )
 
 @ExperimentalFoundationApi
 private class DropTargetElement(
-    val acceptsDragAndDropTransaction: (event: DragAndDropEvent) -> DragAndDropTarget?,
+    val shouldStartDragAndDrop: (event: DragAndDropEvent) -> Boolean,
+    val target: DragAndDropTarget,
 ) : ModifierNodeElement<DragAndDropTargetNode>() {
     override fun create() = DragAndDropTargetNode(
-        acceptsDragAndDropTransaction = acceptsDragAndDropTransaction,
+        target = target,
+        shouldStartDragAndDrop = shouldStartDragAndDrop,
     )
 
     override fun update(node: DragAndDropTargetNode) = with(node) {
-        acceptsDragAndDropTransaction = [email protected]
+        target = [email protected]
+        shouldStartDragAndDrop = [email protected]
     }
 
     override fun InspectorInfo.inspectableProperties() {
         name = "dropTarget"
-        properties["acceptsDragAndDropTransaction"] = acceptsDragAndDropTransaction
+        properties["target"] = target
+        properties["shouldStartDragAndDrop"] = shouldStartDragAndDrop
     }
 
     override fun equals(other: Any?): Boolean {
         if (this === other) return true
         if (other !is DropTargetElement) return false
+        if (target != other.target) return false
 
-        return acceptsDragAndDropTransaction == other.acceptsDragAndDropTransaction
+        return shouldStartDragAndDrop == other.shouldStartDragAndDrop
     }
 
     override fun hashCode(): Int {
-        return acceptsDragAndDropTransaction.hashCode()
+        var result = target.hashCode()
+        result = 31 * result + shouldStartDragAndDrop.hashCode()
+        return result
     }
 }
 
 @ExperimentalFoundationApi
 private class DragAndDropTargetNode(
-    var acceptsDragAndDropTransaction: (event: DragAndDropEvent) -> DragAndDropTarget?
+    var shouldStartDragAndDrop: (event: DragAndDropEvent) -> Boolean,
+    var target: DragAndDropTarget
 ) : DelegatingNode() {
     init {
         delegate(
-            DragAndDropModifierNode(acceptsDragAndDropTransaction)
+            DragAndDropModifierNode(
+                shouldStartDragAndDrop = shouldStartDragAndDrop,
+                target = target
+            )
         )
     }
 }
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/Pager.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/Pager.kt
index 971cdda..7145a86 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/Pager.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/Pager.kt
@@ -260,6 +260,16 @@
         override fun Density.calculateMainAxisPageSize(availableSpace: Int, pageSpacing: Int): Int {
             return pageSize.roundToPx()
         }
+
+        override fun equals(other: Any?): Boolean {
+            if (this === other) return true
+            if (other !is Fixed) return false
+            return pageSize == other.pageSize
+        }
+
+        override fun hashCode(): Int {
+            return pageSize.hashCode()
+        }
     }
 }
 
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/PagerState.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/PagerState.kt
index 0f995d9..89ccc0e 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/PagerState.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/PagerState.kt
@@ -676,7 +676,7 @@
      */
     fun getOffsetFractionForPage(page: Int): Float {
         require(page in 0..pageCount) {
-            "page $page is not within the range 0 to pageCount"
+            "page $page is not within the range 0 to $pageCount"
         }
         return (currentPage - page) + currentPageOffsetFraction
     }
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/CoreTextField.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/CoreTextField.kt
index b56c733..e87358b 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/CoreTextField.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/CoreTextField.kt
@@ -1146,7 +1146,8 @@
                     this[SelectionHandleInfoKey] = SelectionHandleInfo(
                         handle = Handle.Cursor,
                         position = position,
-                        anchor = SelectionHandleAnchor.Middle
+                        anchor = SelectionHandleAnchor.Middle,
+                        visible = true,
                     )
                 },
             content = null
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/SelectionContainer.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/SelectionContainer.kt
index 5a570fc..8b8b02b 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/SelectionContainer.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/SelectionContainer.kt
@@ -27,6 +27,7 @@
 import androidx.compose.runtime.saveable.rememberSaveable
 import androidx.compose.runtime.setValue
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.input.pointer.pointerInput
 import androidx.compose.ui.platform.LocalClipboardManager
 import androidx.compose.ui.platform.LocalHapticFeedback
@@ -109,10 +110,13 @@
                             val observer = remember(isStartHandle) {
                                 manager.handleDragObserver(isStartHandle)
                             }
-                            val position = if (isStartHandle) {
-                                manager.startHandlePosition
-                            } else {
-                                manager.endHandlePosition
+
+                            val positionProvider: () -> Offset = remember(isStartHandle) {
+                                if (isStartHandle) {
+                                    { manager.startHandlePosition ?: Offset.Unspecified }
+                                } else {
+                                    { manager.endHandlePosition ?: Offset.Unspecified }
+                                }
                             }
 
                             val direction = if (isStartHandle) {
@@ -121,18 +125,15 @@
                                 it.end.direction
                             }
 
-                            if (position != null) {
-                                SelectionHandle(
-                                    position = position,
-                                    isStartHandle = isStartHandle,
-                                    direction = direction,
-                                    handlesCrossed = it.handlesCrossed,
-                                    modifier = Modifier.pointerInput(observer) {
-                                        detectDownAndDragGesturesWithObserver(observer)
-                                    },
-                                    content = null
-                                )
-                            }
+                            SelectionHandle(
+                                offsetProvider = positionProvider,
+                                isStartHandle = isStartHandle,
+                                direction = direction,
+                                handlesCrossed = it.handlesCrossed,
+                                modifier = Modifier.pointerInput(observer) {
+                                    detectDownAndDragGesturesWithObserver(observer)
+                                },
+                            )
                         }
                     }
                 }
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/SelectionHandles.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/SelectionHandles.kt
index 348f37e..cf0f5b6 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/SelectionHandles.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/SelectionHandles.kt
@@ -41,11 +41,14 @@
  * @param position The position that the handle is anchored to relative to the selectable content.
  * This position is not necessarily the position of the popup itself, it's the position that the
  * handle "points" to (so e.g. top-middle for [Handle.Cursor]).
+ * @param anchor How the selection handle is anchored to its position
+ * @param visible Whether the icon of the handle is actually shown
  */
 internal data class SelectionHandleInfo(
     val handle: Handle,
     val position: Offset,
-    val anchor: SelectionHandleAnchor
+    val anchor: SelectionHandleAnchor,
+    val visible: Boolean,
 )
 
 /**
@@ -63,15 +66,21 @@
 
 @Composable
 internal expect fun SelectionHandle(
-    position: Offset,
+    offsetProvider: OffsetProvider,
     isStartHandle: Boolean,
     direction: ResolvedTextDirection,
     handlesCrossed: Boolean,
     modifier: Modifier,
-    content: @Composable (() -> Unit)?
 )
 
 /**
+ * Avoids boxing of [Offset] which is an inline value class.
+ */
+internal fun interface OffsetProvider {
+    fun provide(): Offset
+}
+
+/**
  * Adjust coordinates for given text offset.
  *
  * Currently [android.text.Layout.getLineBottom] returns y coordinates of the next
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/SelectionManager.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/SelectionManager.kt
index 7f3589b..30184a0 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/SelectionManager.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/SelectionManager.kt
@@ -612,6 +612,9 @@
 
     fun handleDragObserver(isStartHandle: Boolean): TextDragObserver = object : TextDragObserver {
         override fun onDown(point: Offset) {
+            // if the handle position is null, then it is invisible, so ignore the gesture
+            (if (isStartHandle) startHandlePosition else endHandlePosition) ?: return
+
             val selection = selection ?: return
             val anchor = if (isStartHandle) selection.start else selection.end
             val selectable = getAnchorSelectable(anchor) ?: return
@@ -622,11 +625,9 @@
 
             // The position of the character where the drag gesture should begin. This is in
             // the composable coordinates.
-            val beginCoordinates = getAdjustedCoordinates(
-                selectable.getHandlePosition(
-                    selection = selection, isStartHandle = isStartHandle
-                )
-            )
+            val handlePosition = selectable.getHandlePosition(selection, isStartHandle)
+            if (handlePosition.isUnspecified) return
+            val beginCoordinates = getAdjustedCoordinates(handlePosition)
 
             // Convert the position where drag gesture begins from composable coordinates to
             // selection container coordinates.
@@ -639,33 +640,26 @@
         }
 
         override fun onStart(startPoint: Offset) {
+            draggingHandle ?: return
+
             val selection = selection!!
-            val startSelectable =
-                selectionRegistrar.selectableMap[selection.start.selectableId]
-            val endSelectable =
-                selectionRegistrar.selectableMap[selection.end.selectableId]
+            val anchor = if (isStartHandle) selection.start else selection.end
+            val selectable = checkNotNull(selectionRegistrar.selectableMap[anchor.selectableId]) {
+                "SelectionRegistrar should contain the current selection's selectableIds"
+            }
+
             // The LayoutCoordinates of the composable where the drag gesture should begin. This
             // is used to convert the position of the beginning of the drag gesture from the
             // composable coordinates to selection container coordinates.
-            val beginLayoutCoordinates = if (isStartHandle) {
-                startSelectable?.getLayoutCoordinates()!!
-            } else {
-                endSelectable?.getLayoutCoordinates()!!
+            val beginLayoutCoordinates = checkNotNull(selectable.getLayoutCoordinates()) {
+                "Current selectable should have layout coordinates."
             }
 
             // The position of the character where the drag gesture should begin. This is in
             // the composable coordinates.
-            val beginCoordinates = getAdjustedCoordinates(
-                if (isStartHandle) {
-                    startSelectable!!.getHandlePosition(
-                        selection = selection, isStartHandle = true
-                    )
-                } else {
-                    endSelectable!!.getHandlePosition(
-                        selection = selection, isStartHandle = false
-                    )
-                }
-            )
+            val handlePosition = selectable.getHandlePosition(selection, isStartHandle)
+            if (handlePosition.isUnspecified) return
+            val beginCoordinates = getAdjustedCoordinates(handlePosition)
 
             // Convert the position where drag gesture begins from composable coordinates to
             // selection container coordinates.
@@ -679,6 +673,8 @@
         }
 
         override fun onDrag(delta: Offset) {
+            draggingHandle ?: return
+
             dragTotalDistance += delta
             val endPosition = dragBeginPosition + dragTotalDistance
             val consumed = updateSelection(
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/TextFieldSelectionManager.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/TextFieldSelectionManager.kt
index 29e29d1..52672f5 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/TextFieldSelectionManager.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/TextFieldSelectionManager.kt
@@ -932,17 +932,15 @@
     val observer = remember(isStartHandle, manager) {
         manager.handleDragObserver(isStartHandle)
     }
-    val position = manager.getHandlePosition(isStartHandle)
 
     SelectionHandle(
-        position = position,
+        offsetProvider = { manager.getHandlePosition(isStartHandle) },
         isStartHandle = isStartHandle,
         direction = direction,
         handlesCrossed = manager.value.selection.reversed,
         modifier = Modifier.pointerInput(observer) {
             detectDownAndDragGesturesWithObserver(observer)
         },
-        content = null
     )
 }
 
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/BasicTextField2.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/BasicTextField2.kt
index 43cf174..d9a6a36 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/BasicTextField2.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/BasicTextField2.kt
@@ -33,6 +33,7 @@
 import androidx.compose.foundation.text.KeyboardActions
 import androidx.compose.foundation.text.KeyboardOptions
 import androidx.compose.foundation.text.heightInLines
+import androidx.compose.foundation.text.selection.SelectionHandle
 import androidx.compose.foundation.text.selection.SelectionHandleAnchor
 import androidx.compose.foundation.text.selection.SelectionHandleInfo
 import androidx.compose.foundation.text.selection.SelectionHandleInfoKey
@@ -49,7 +50,6 @@
 import androidx.compose.foundation.text2.input.internal.TextFieldTextLayoutModifier
 import androidx.compose.foundation.text2.input.internal.TextLayoutState
 import androidx.compose.foundation.text2.input.internal.TransformedTextFieldState
-import androidx.compose.foundation.text2.input.internal.selection.TextFieldSelectionHandle2
 import androidx.compose.foundation.text2.input.internal.selection.TextFieldSelectionState
 import androidx.compose.foundation.text2.input.internal.syncTextFieldState
 import androidx.compose.runtime.Composable
@@ -495,7 +495,8 @@
                     this[SelectionHandleInfoKey] = SelectionHandleInfo(
                         handle = Handle.Cursor,
                         position = cursorHandleState.position,
-                        anchor = SelectionHandleAnchor.Middle
+                        anchor = SelectionHandleAnchor.Middle,
+                        visible = true,
                     )
                 }
                 .pointerInput(selectionState) {
@@ -512,8 +513,8 @@
 ) {
     val startHandleState = selectionState.startSelectionHandle
     if (startHandleState.visible) {
-        TextFieldSelectionHandle2(
-            positionProvider = { selectionState.startSelectionHandle.position },
+        SelectionHandle(
+            offsetProvider = { selectionState.startSelectionHandle.position },
             isStartHandle = true,
             direction = startHandleState.direction,
             handlesCrossed = startHandleState.handlesCrossed,
@@ -525,8 +526,8 @@
 
     val endHandleState = selectionState.endSelectionHandle
     if (endHandleState.visible) {
-        TextFieldSelectionHandle2(
-            positionProvider = { selectionState.endSelectionHandle.position },
+        SelectionHandle(
+            offsetProvider = { selectionState.endSelectionHandle.position },
             isStartHandle = false,
             direction = endHandleState.direction,
             handlesCrossed = endHandleState.handlesCrossed,
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/internal/selection/TextFieldSelectionHandles.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/internal/selection/TextFieldSelectionHandles.kt
deleted file mode 100644
index ae62db7..0000000
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/internal/selection/TextFieldSelectionHandles.kt
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * Copyright 2023 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.foundation.text2.input.internal.selection
-
-import androidx.compose.runtime.Composable
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.geometry.Offset
-import androidx.compose.ui.text.style.ResolvedTextDirection
-
-@Composable
-internal expect fun TextFieldSelectionHandle2(
-    positionProvider: OffsetProvider,
-    isStartHandle: Boolean,
-    direction: ResolvedTextDirection,
-    handlesCrossed: Boolean,
-    modifier: Modifier
-)
-
-/**
- * Avoids boxing of [Offset] which is an inline value class.
- */
-internal fun interface OffsetProvider {
-    fun provide(): Offset
-}
diff --git a/compose/foundation/foundation/src/desktopMain/kotlin/androidx/compose/foundation/text/selection/DesktopSelectionHandles.desktop.kt b/compose/foundation/foundation/src/desktopMain/kotlin/androidx/compose/foundation/text/selection/DesktopSelectionHandles.desktop.kt
index 31eacb6..c3f1196 100644
--- a/compose/foundation/foundation/src/desktopMain/kotlin/androidx/compose/foundation/text/selection/DesktopSelectionHandles.desktop.kt
+++ b/compose/foundation/foundation/src/desktopMain/kotlin/androidx/compose/foundation/text/selection/DesktopSelectionHandles.desktop.kt
@@ -18,17 +18,15 @@
 
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Modifier
-import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.text.style.ResolvedTextDirection
 
 @Composable
 internal actual fun SelectionHandle(
-    position: Offset,
+    offsetProvider: OffsetProvider,
     isStartHandle: Boolean,
     direction: ResolvedTextDirection,
     handlesCrossed: Boolean,
     modifier: Modifier,
-    content: (@Composable () -> Unit)?
 ) {
     // TODO
 }
diff --git a/compose/foundation/foundation/src/desktopMain/kotlin/androidx/compose/foundation/text2/input/internal/selection/TextFieldSelectionHandles.desktop.kt b/compose/foundation/foundation/src/desktopMain/kotlin/androidx/compose/foundation/text2/input/internal/selection/TextFieldSelectionHandles.desktop.kt
deleted file mode 100644
index 3fed192..0000000
--- a/compose/foundation/foundation/src/desktopMain/kotlin/androidx/compose/foundation/text2/input/internal/selection/TextFieldSelectionHandles.desktop.kt
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * Copyright 2023 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.foundation.text2.input.internal.selection
-
-import androidx.compose.runtime.Composable
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.text.style.ResolvedTextDirection
-
-/**
- * Handles are not supported on Desktop.
- */
-@Composable
-internal actual fun TextFieldSelectionHandle2(
-    positionProvider: OffsetProvider,
-    isStartHandle: Boolean,
-    direction: ResolvedTextDirection,
-    handlesCrossed: Boolean,
-    modifier: Modifier
-) {}
diff --git a/compose/runtime/runtime-tracing/api/1.0.0-beta01.txt b/compose/runtime/runtime-tracing/api/1.0.0-beta01.txt
new file mode 100644
index 0000000..ef233b1
--- /dev/null
+++ b/compose/runtime/runtime-tracing/api/1.0.0-beta01.txt
@@ -0,0 +1,11 @@
+// Signature format: 4.0
+package androidx.compose.runtime.tracing {
+
+  public final class ComposeTracingInitializer implements androidx.startup.Initializer<kotlin.Unit> {
+    ctor public ComposeTracingInitializer();
+    method public void create(android.content.Context context);
+    method public java.util.List<java.lang.Class<? extends androidx.startup.Initializer<?>>> dependencies();
+  }
+
+}
+
diff --git a/compose/runtime/runtime-tracing/api/res-1.0.0-beta01.txt b/compose/runtime/runtime-tracing/api/res-1.0.0-beta01.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/compose/runtime/runtime-tracing/api/res-1.0.0-beta01.txt
diff --git a/compose/runtime/runtime-tracing/api/restricted_1.0.0-beta01.txt b/compose/runtime/runtime-tracing/api/restricted_1.0.0-beta01.txt
new file mode 100644
index 0000000..ef233b1
--- /dev/null
+++ b/compose/runtime/runtime-tracing/api/restricted_1.0.0-beta01.txt
@@ -0,0 +1,11 @@
+// Signature format: 4.0
+package androidx.compose.runtime.tracing {
+
+  public final class ComposeTracingInitializer implements androidx.startup.Initializer<kotlin.Unit> {
+    ctor public ComposeTracingInitializer();
+    method public void create(android.content.Context context);
+    method public java.util.List<java.lang.Class<? extends androidx.startup.Initializer<?>>> dependencies();
+  }
+
+}
+
diff --git a/compose/runtime/runtime-tracing/build.gradle b/compose/runtime/runtime-tracing/build.gradle
index 9c73f30..00b177b 100644
--- a/compose/runtime/runtime-tracing/build.gradle
+++ b/compose/runtime/runtime-tracing/build.gradle
@@ -45,7 +45,6 @@
 androidx {
     name = "Compose Runtime: Tracing"
     publish = Publish.SNAPSHOT_AND_RELEASE
-    mavenVersion = LibraryVersions.COMPOSE_RUNTIME_TRACING
     inceptionYear = "2022"
     description = "Additional tracing in Compose"
     metalavaK2UastEnabled = true
diff --git a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/ComposeVersion.kt b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/ComposeVersion.kt
index 5779c20..283b6e3 100644
--- a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/ComposeVersion.kt
+++ b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/ComposeVersion.kt
@@ -28,5 +28,5 @@
      * IMPORTANT: Whenever updating this value, please make sure to also update `versionTable` and
      * `minimumRuntimeVersionInt` in `VersionChecker.kt` of the compiler.
      */
-    const val version: Int = 11800
+    const val version: Int = 12000
 }
diff --git a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/RememberObserver.kt b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/RememberObserver.kt
index 3969761..cfa9727 100644
--- a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/RememberObserver.kt
+++ b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/RememberObserver.kt
@@ -28,7 +28,7 @@
  * the same composition, its [onRemembered] and [onForgotten] will be called for each location in
  * the composition.
  *
- * When objects implementing this interface is remmembered and forgotten together,
+ * When objects implementing this interface is remembered and forgotten together,
  * the order of [onForgotten] is guaranteed to be called in the opposite order of [onRemembered].
  * For example, if two objects, A and B are [remember]ed together, A followed by B,
  * [onRemembered] will be called first on A then on B. If they forgotten together then
diff --git a/compose/ui/ui/api/1.6.0-beta01.txt b/compose/ui/ui/api/1.6.0-beta01.txt
index bccc765..6532b0d 100644
--- a/compose/ui/ui/api/1.6.0-beta01.txt
+++ b/compose/ui/ui/api/1.6.0-beta01.txt
@@ -258,7 +258,7 @@
   }
 
   public final class DragAndDropKt {
-    method public static androidx.compose.ui.draganddrop.DragAndDropTarget DragAndDropTarget(kotlin.jvm.functions.Function1<? super androidx.compose.ui.draganddrop.DragAndDropEvent,java.lang.Boolean> onDropped, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.draganddrop.DragAndDropEvent,kotlin.Unit> onStarted, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.draganddrop.DragAndDropEvent,kotlin.Unit> onEntered, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.draganddrop.DragAndDropEvent,kotlin.Unit> onMoved, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.draganddrop.DragAndDropEvent,kotlin.Unit> onChanged, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.draganddrop.DragAndDropEvent,kotlin.Unit> onExited, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.draganddrop.DragAndDropEvent,kotlin.Unit> onEnded);
+    method public static androidx.compose.ui.draganddrop.DragAndDropTarget DragAndDropTarget(kotlin.jvm.functions.Function1<? super androidx.compose.ui.draganddrop.DragAndDropEvent,java.lang.Boolean> onDrop, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.draganddrop.DragAndDropEvent,kotlin.Unit>? onStarted, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.draganddrop.DragAndDropEvent,kotlin.Unit>? onEntered, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.draganddrop.DragAndDropEvent,kotlin.Unit>? onMoved, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.draganddrop.DragAndDropEvent,kotlin.Unit>? onChanged, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.draganddrop.DragAndDropEvent,kotlin.Unit>? onExited, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.draganddrop.DragAndDropEvent,kotlin.Unit>? onEnded);
   }
 
   public interface DragAndDropModifierNode extends androidx.compose.ui.node.DelegatableNode androidx.compose.ui.draganddrop.DragAndDropTarget {
@@ -267,17 +267,18 @@
   }
 
   public final class DragAndDropNodeKt {
-    method public static androidx.compose.ui.draganddrop.DragAndDropModifierNode DragAndDropModifierNode(optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.draganddrop.DragAndDropEvent,? extends androidx.compose.ui.draganddrop.DragAndDropTarget> acceptDragAndDropTransfer);
+    method public static androidx.compose.ui.draganddrop.DragAndDropModifierNode DragAndDropModifierNode();
+    method public static androidx.compose.ui.draganddrop.DragAndDropModifierNode DragAndDropModifierNode(kotlin.jvm.functions.Function1<? super androidx.compose.ui.draganddrop.DragAndDropEvent,java.lang.Boolean> shouldStartDragAndDrop, androidx.compose.ui.draganddrop.DragAndDropTarget target);
   }
 
   public interface DragAndDropTarget {
-    method public void onChanged(androidx.compose.ui.draganddrop.DragAndDropEvent event);
-    method public boolean onDropped(androidx.compose.ui.draganddrop.DragAndDropEvent event);
-    method public void onEnded(androidx.compose.ui.draganddrop.DragAndDropEvent event);
-    method public void onEntered(androidx.compose.ui.draganddrop.DragAndDropEvent event);
-    method public void onExited(androidx.compose.ui.draganddrop.DragAndDropEvent event);
-    method public void onMoved(androidx.compose.ui.draganddrop.DragAndDropEvent event);
-    method public void onStarted(androidx.compose.ui.draganddrop.DragAndDropEvent event);
+    method public default void onChanged(androidx.compose.ui.draganddrop.DragAndDropEvent event);
+    method public boolean onDrop(androidx.compose.ui.draganddrop.DragAndDropEvent event);
+    method public default void onEnded(androidx.compose.ui.draganddrop.DragAndDropEvent event);
+    method public default void onEntered(androidx.compose.ui.draganddrop.DragAndDropEvent event);
+    method public default void onExited(androidx.compose.ui.draganddrop.DragAndDropEvent event);
+    method public default void onMoved(androidx.compose.ui.draganddrop.DragAndDropEvent event);
+    method public default void onStarted(androidx.compose.ui.draganddrop.DragAndDropEvent event);
   }
 
   public final class DragAndDropTransferData {
@@ -2745,7 +2746,7 @@
     property public long doubleTapMinTimeMillis;
     property public long doubleTapTimeoutMillis;
     property public long longPressTimeoutMillis;
-    property public int maximumFlingVelocity;
+    property public float maximumFlingVelocity;
     property public float touchSlop;
   }
 
@@ -2965,13 +2966,13 @@
     method public long getDoubleTapMinTimeMillis();
     method public long getDoubleTapTimeoutMillis();
     method public long getLongPressTimeoutMillis();
-    method public default int getMaximumFlingVelocity();
+    method public default float getMaximumFlingVelocity();
     method public default long getMinimumTouchTargetSize();
     method public float getTouchSlop();
     property public abstract long doubleTapMinTimeMillis;
     property public abstract long doubleTapTimeoutMillis;
     property public abstract long longPressTimeoutMillis;
-    property public default int maximumFlingVelocity;
+    property public default float maximumFlingVelocity;
     property public default long minimumTouchTargetSize;
     property public abstract float touchSlop;
   }
diff --git a/compose/ui/ui/api/current.ignore b/compose/ui/ui/api/current.ignore
deleted file mode 100644
index 4fed4d3..0000000
--- a/compose/ui/ui/api/current.ignore
+++ /dev/null
@@ -1,17 +0,0 @@
-// Baseline format: 1.0
-AddedAbstractMethod: androidx.compose.ui.draganddrop.DragAndDropModifierNode#acceptDragAndDropTransfer(androidx.compose.ui.draganddrop.DragAndDropEvent):
-    Added method androidx.compose.ui.draganddrop.DragAndDropModifierNode.acceptDragAndDropTransfer(androidx.compose.ui.draganddrop.DragAndDropEvent)
-
-
-AddedClass: androidx.compose.ui.draganddrop.DragAndDropKt:
-    Added class androidx.compose.ui.draganddrop.DragAndDropKt
-
-
-ChangedType: androidx.compose.ui.draganddrop.DragAndDropTarget#onStarted(androidx.compose.ui.draganddrop.DragAndDropEvent):
-    Method androidx.compose.ui.draganddrop.DragAndDropTarget.onStarted has changed return type from boolean to void
-
-
-ParameterNameChange: androidx.compose.ui.draganddrop.DragAndDropModifierNode#drag(androidx.compose.ui.draganddrop.DragAndDropTransferData, long, kotlin.jvm.functions.Function1<? super androidx.compose.ui.graphics.drawscope.DrawScope,kotlin.Unit>) parameter #1:
-    Attempted to change parameter name from dragDecorationSize to decorationSize in method androidx.compose.ui.draganddrop.DragAndDropModifierNode.drag
-ParameterNameChange: androidx.compose.ui.draganddrop.DragAndDropNodeKt#DragAndDropModifierNode(kotlin.jvm.functions.Function1<? super androidx.compose.ui.draganddrop.DragAndDropEvent,? extends androidx.compose.ui.draganddrop.DragAndDropTarget>) parameter #0:
-    Attempted to change parameter name from onDragAndDropStart to acceptDragAndDropTransfer in method androidx.compose.ui.draganddrop.DragAndDropNodeKt.DragAndDropModifierNode
diff --git a/compose/ui/ui/api/current.txt b/compose/ui/ui/api/current.txt
index bccc765..6532b0d 100644
--- a/compose/ui/ui/api/current.txt
+++ b/compose/ui/ui/api/current.txt
@@ -258,7 +258,7 @@
   }
 
   public final class DragAndDropKt {
-    method public static androidx.compose.ui.draganddrop.DragAndDropTarget DragAndDropTarget(kotlin.jvm.functions.Function1<? super androidx.compose.ui.draganddrop.DragAndDropEvent,java.lang.Boolean> onDropped, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.draganddrop.DragAndDropEvent,kotlin.Unit> onStarted, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.draganddrop.DragAndDropEvent,kotlin.Unit> onEntered, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.draganddrop.DragAndDropEvent,kotlin.Unit> onMoved, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.draganddrop.DragAndDropEvent,kotlin.Unit> onChanged, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.draganddrop.DragAndDropEvent,kotlin.Unit> onExited, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.draganddrop.DragAndDropEvent,kotlin.Unit> onEnded);
+    method public static androidx.compose.ui.draganddrop.DragAndDropTarget DragAndDropTarget(kotlin.jvm.functions.Function1<? super androidx.compose.ui.draganddrop.DragAndDropEvent,java.lang.Boolean> onDrop, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.draganddrop.DragAndDropEvent,kotlin.Unit>? onStarted, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.draganddrop.DragAndDropEvent,kotlin.Unit>? onEntered, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.draganddrop.DragAndDropEvent,kotlin.Unit>? onMoved, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.draganddrop.DragAndDropEvent,kotlin.Unit>? onChanged, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.draganddrop.DragAndDropEvent,kotlin.Unit>? onExited, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.draganddrop.DragAndDropEvent,kotlin.Unit>? onEnded);
   }
 
   public interface DragAndDropModifierNode extends androidx.compose.ui.node.DelegatableNode androidx.compose.ui.draganddrop.DragAndDropTarget {
@@ -267,17 +267,18 @@
   }
 
   public final class DragAndDropNodeKt {
-    method public static androidx.compose.ui.draganddrop.DragAndDropModifierNode DragAndDropModifierNode(optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.draganddrop.DragAndDropEvent,? extends androidx.compose.ui.draganddrop.DragAndDropTarget> acceptDragAndDropTransfer);
+    method public static androidx.compose.ui.draganddrop.DragAndDropModifierNode DragAndDropModifierNode();
+    method public static androidx.compose.ui.draganddrop.DragAndDropModifierNode DragAndDropModifierNode(kotlin.jvm.functions.Function1<? super androidx.compose.ui.draganddrop.DragAndDropEvent,java.lang.Boolean> shouldStartDragAndDrop, androidx.compose.ui.draganddrop.DragAndDropTarget target);
   }
 
   public interface DragAndDropTarget {
-    method public void onChanged(androidx.compose.ui.draganddrop.DragAndDropEvent event);
-    method public boolean onDropped(androidx.compose.ui.draganddrop.DragAndDropEvent event);
-    method public void onEnded(androidx.compose.ui.draganddrop.DragAndDropEvent event);
-    method public void onEntered(androidx.compose.ui.draganddrop.DragAndDropEvent event);
-    method public void onExited(androidx.compose.ui.draganddrop.DragAndDropEvent event);
-    method public void onMoved(androidx.compose.ui.draganddrop.DragAndDropEvent event);
-    method public void onStarted(androidx.compose.ui.draganddrop.DragAndDropEvent event);
+    method public default void onChanged(androidx.compose.ui.draganddrop.DragAndDropEvent event);
+    method public boolean onDrop(androidx.compose.ui.draganddrop.DragAndDropEvent event);
+    method public default void onEnded(androidx.compose.ui.draganddrop.DragAndDropEvent event);
+    method public default void onEntered(androidx.compose.ui.draganddrop.DragAndDropEvent event);
+    method public default void onExited(androidx.compose.ui.draganddrop.DragAndDropEvent event);
+    method public default void onMoved(androidx.compose.ui.draganddrop.DragAndDropEvent event);
+    method public default void onStarted(androidx.compose.ui.draganddrop.DragAndDropEvent event);
   }
 
   public final class DragAndDropTransferData {
@@ -2745,7 +2746,7 @@
     property public long doubleTapMinTimeMillis;
     property public long doubleTapTimeoutMillis;
     property public long longPressTimeoutMillis;
-    property public int maximumFlingVelocity;
+    property public float maximumFlingVelocity;
     property public float touchSlop;
   }
 
@@ -2965,13 +2966,13 @@
     method public long getDoubleTapMinTimeMillis();
     method public long getDoubleTapTimeoutMillis();
     method public long getLongPressTimeoutMillis();
-    method public default int getMaximumFlingVelocity();
+    method public default float getMaximumFlingVelocity();
     method public default long getMinimumTouchTargetSize();
     method public float getTouchSlop();
     property public abstract long doubleTapMinTimeMillis;
     property public abstract long doubleTapTimeoutMillis;
     property public abstract long longPressTimeoutMillis;
-    property public default int maximumFlingVelocity;
+    property public default float maximumFlingVelocity;
     property public default long minimumTouchTargetSize;
     property public abstract float touchSlop;
   }
diff --git a/compose/ui/ui/api/restricted_1.6.0-beta01.txt b/compose/ui/ui/api/restricted_1.6.0-beta01.txt
index 6713937..2b9b005 100644
--- a/compose/ui/ui/api/restricted_1.6.0-beta01.txt
+++ b/compose/ui/ui/api/restricted_1.6.0-beta01.txt
@@ -258,7 +258,7 @@
   }
 
   public final class DragAndDropKt {
-    method public static androidx.compose.ui.draganddrop.DragAndDropTarget DragAndDropTarget(kotlin.jvm.functions.Function1<? super androidx.compose.ui.draganddrop.DragAndDropEvent,java.lang.Boolean> onDropped, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.draganddrop.DragAndDropEvent,kotlin.Unit> onStarted, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.draganddrop.DragAndDropEvent,kotlin.Unit> onEntered, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.draganddrop.DragAndDropEvent,kotlin.Unit> onMoved, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.draganddrop.DragAndDropEvent,kotlin.Unit> onChanged, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.draganddrop.DragAndDropEvent,kotlin.Unit> onExited, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.draganddrop.DragAndDropEvent,kotlin.Unit> onEnded);
+    method public static androidx.compose.ui.draganddrop.DragAndDropTarget DragAndDropTarget(kotlin.jvm.functions.Function1<? super androidx.compose.ui.draganddrop.DragAndDropEvent,java.lang.Boolean> onDrop, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.draganddrop.DragAndDropEvent,kotlin.Unit>? onStarted, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.draganddrop.DragAndDropEvent,kotlin.Unit>? onEntered, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.draganddrop.DragAndDropEvent,kotlin.Unit>? onMoved, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.draganddrop.DragAndDropEvent,kotlin.Unit>? onChanged, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.draganddrop.DragAndDropEvent,kotlin.Unit>? onExited, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.draganddrop.DragAndDropEvent,kotlin.Unit>? onEnded);
   }
 
   public interface DragAndDropModifierNode extends androidx.compose.ui.node.DelegatableNode androidx.compose.ui.draganddrop.DragAndDropTarget {
@@ -267,17 +267,18 @@
   }
 
   public final class DragAndDropNodeKt {
-    method public static androidx.compose.ui.draganddrop.DragAndDropModifierNode DragAndDropModifierNode(optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.draganddrop.DragAndDropEvent,? extends androidx.compose.ui.draganddrop.DragAndDropTarget> acceptDragAndDropTransfer);
+    method public static androidx.compose.ui.draganddrop.DragAndDropModifierNode DragAndDropModifierNode();
+    method public static androidx.compose.ui.draganddrop.DragAndDropModifierNode DragAndDropModifierNode(kotlin.jvm.functions.Function1<? super androidx.compose.ui.draganddrop.DragAndDropEvent,java.lang.Boolean> shouldStartDragAndDrop, androidx.compose.ui.draganddrop.DragAndDropTarget target);
   }
 
   public interface DragAndDropTarget {
-    method public void onChanged(androidx.compose.ui.draganddrop.DragAndDropEvent event);
-    method public boolean onDropped(androidx.compose.ui.draganddrop.DragAndDropEvent event);
-    method public void onEnded(androidx.compose.ui.draganddrop.DragAndDropEvent event);
-    method public void onEntered(androidx.compose.ui.draganddrop.DragAndDropEvent event);
-    method public void onExited(androidx.compose.ui.draganddrop.DragAndDropEvent event);
-    method public void onMoved(androidx.compose.ui.draganddrop.DragAndDropEvent event);
-    method public void onStarted(androidx.compose.ui.draganddrop.DragAndDropEvent event);
+    method public default void onChanged(androidx.compose.ui.draganddrop.DragAndDropEvent event);
+    method public boolean onDrop(androidx.compose.ui.draganddrop.DragAndDropEvent event);
+    method public default void onEnded(androidx.compose.ui.draganddrop.DragAndDropEvent event);
+    method public default void onEntered(androidx.compose.ui.draganddrop.DragAndDropEvent event);
+    method public default void onExited(androidx.compose.ui.draganddrop.DragAndDropEvent event);
+    method public default void onMoved(androidx.compose.ui.draganddrop.DragAndDropEvent event);
+    method public default void onStarted(androidx.compose.ui.draganddrop.DragAndDropEvent event);
   }
 
   public final class DragAndDropTransferData {
@@ -2798,7 +2799,7 @@
     property public long doubleTapMinTimeMillis;
     property public long doubleTapTimeoutMillis;
     property public long longPressTimeoutMillis;
-    property public int maximumFlingVelocity;
+    property public float maximumFlingVelocity;
     property public float touchSlop;
   }
 
@@ -3023,13 +3024,13 @@
     method public long getDoubleTapMinTimeMillis();
     method public long getDoubleTapTimeoutMillis();
     method public long getLongPressTimeoutMillis();
-    method public default int getMaximumFlingVelocity();
+    method public default float getMaximumFlingVelocity();
     method public default long getMinimumTouchTargetSize();
     method public float getTouchSlop();
     property public abstract long doubleTapMinTimeMillis;
     property public abstract long doubleTapTimeoutMillis;
     property public abstract long longPressTimeoutMillis;
-    property public default int maximumFlingVelocity;
+    property public default float maximumFlingVelocity;
     property public default long minimumTouchTargetSize;
     property public abstract float touchSlop;
   }
diff --git a/compose/ui/ui/api/restricted_current.ignore b/compose/ui/ui/api/restricted_current.ignore
deleted file mode 100644
index 840e374..0000000
--- a/compose/ui/ui/api/restricted_current.ignore
+++ /dev/null
@@ -1,19 +0,0 @@
-// Baseline format: 1.0
-AddedAbstractMethod: androidx.compose.ui.draganddrop.DragAndDropModifierNode#acceptDragAndDropTransfer(androidx.compose.ui.draganddrop.DragAndDropEvent):
-    Added method androidx.compose.ui.draganddrop.DragAndDropModifierNode.acceptDragAndDropTransfer(androidx.compose.ui.draganddrop.DragAndDropEvent)
-
-
-AddedClass: androidx.compose.ui.draganddrop.DragAndDropKt:
-    Added class androidx.compose.ui.draganddrop.DragAndDropKt
-AddedClass: androidx.compose.ui.platform.JvmActuals_jvmKt:
-    Added class androidx.compose.ui.platform.JvmActuals_jvmKt
-
-
-ChangedType: androidx.compose.ui.draganddrop.DragAndDropTarget#onStarted(androidx.compose.ui.draganddrop.DragAndDropEvent):
-    Method androidx.compose.ui.draganddrop.DragAndDropTarget.onStarted has changed return type from boolean to void
-
-
-ParameterNameChange: androidx.compose.ui.draganddrop.DragAndDropModifierNode#drag(androidx.compose.ui.draganddrop.DragAndDropTransferData, long, kotlin.jvm.functions.Function1<? super androidx.compose.ui.graphics.drawscope.DrawScope,kotlin.Unit>) parameter #1:
-    Attempted to change parameter name from dragDecorationSize to decorationSize in method androidx.compose.ui.draganddrop.DragAndDropModifierNode.drag
-ParameterNameChange: androidx.compose.ui.draganddrop.DragAndDropNodeKt#DragAndDropModifierNode(kotlin.jvm.functions.Function1<? super androidx.compose.ui.draganddrop.DragAndDropEvent,? extends androidx.compose.ui.draganddrop.DragAndDropTarget>) parameter #0:
-    Attempted to change parameter name from onDragAndDropStart to acceptDragAndDropTransfer in method androidx.compose.ui.draganddrop.DragAndDropNodeKt.DragAndDropModifierNode
diff --git a/compose/ui/ui/api/restricted_current.txt b/compose/ui/ui/api/restricted_current.txt
index 6713937..2b9b005 100644
--- a/compose/ui/ui/api/restricted_current.txt
+++ b/compose/ui/ui/api/restricted_current.txt
@@ -258,7 +258,7 @@
   }
 
   public final class DragAndDropKt {
-    method public static androidx.compose.ui.draganddrop.DragAndDropTarget DragAndDropTarget(kotlin.jvm.functions.Function1<? super androidx.compose.ui.draganddrop.DragAndDropEvent,java.lang.Boolean> onDropped, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.draganddrop.DragAndDropEvent,kotlin.Unit> onStarted, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.draganddrop.DragAndDropEvent,kotlin.Unit> onEntered, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.draganddrop.DragAndDropEvent,kotlin.Unit> onMoved, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.draganddrop.DragAndDropEvent,kotlin.Unit> onChanged, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.draganddrop.DragAndDropEvent,kotlin.Unit> onExited, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.draganddrop.DragAndDropEvent,kotlin.Unit> onEnded);
+    method public static androidx.compose.ui.draganddrop.DragAndDropTarget DragAndDropTarget(kotlin.jvm.functions.Function1<? super androidx.compose.ui.draganddrop.DragAndDropEvent,java.lang.Boolean> onDrop, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.draganddrop.DragAndDropEvent,kotlin.Unit>? onStarted, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.draganddrop.DragAndDropEvent,kotlin.Unit>? onEntered, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.draganddrop.DragAndDropEvent,kotlin.Unit>? onMoved, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.draganddrop.DragAndDropEvent,kotlin.Unit>? onChanged, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.draganddrop.DragAndDropEvent,kotlin.Unit>? onExited, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.draganddrop.DragAndDropEvent,kotlin.Unit>? onEnded);
   }
 
   public interface DragAndDropModifierNode extends androidx.compose.ui.node.DelegatableNode androidx.compose.ui.draganddrop.DragAndDropTarget {
@@ -267,17 +267,18 @@
   }
 
   public final class DragAndDropNodeKt {
-    method public static androidx.compose.ui.draganddrop.DragAndDropModifierNode DragAndDropModifierNode(optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.draganddrop.DragAndDropEvent,? extends androidx.compose.ui.draganddrop.DragAndDropTarget> acceptDragAndDropTransfer);
+    method public static androidx.compose.ui.draganddrop.DragAndDropModifierNode DragAndDropModifierNode();
+    method public static androidx.compose.ui.draganddrop.DragAndDropModifierNode DragAndDropModifierNode(kotlin.jvm.functions.Function1<? super androidx.compose.ui.draganddrop.DragAndDropEvent,java.lang.Boolean> shouldStartDragAndDrop, androidx.compose.ui.draganddrop.DragAndDropTarget target);
   }
 
   public interface DragAndDropTarget {
-    method public void onChanged(androidx.compose.ui.draganddrop.DragAndDropEvent event);
-    method public boolean onDropped(androidx.compose.ui.draganddrop.DragAndDropEvent event);
-    method public void onEnded(androidx.compose.ui.draganddrop.DragAndDropEvent event);
-    method public void onEntered(androidx.compose.ui.draganddrop.DragAndDropEvent event);
-    method public void onExited(androidx.compose.ui.draganddrop.DragAndDropEvent event);
-    method public void onMoved(androidx.compose.ui.draganddrop.DragAndDropEvent event);
-    method public void onStarted(androidx.compose.ui.draganddrop.DragAndDropEvent event);
+    method public default void onChanged(androidx.compose.ui.draganddrop.DragAndDropEvent event);
+    method public boolean onDrop(androidx.compose.ui.draganddrop.DragAndDropEvent event);
+    method public default void onEnded(androidx.compose.ui.draganddrop.DragAndDropEvent event);
+    method public default void onEntered(androidx.compose.ui.draganddrop.DragAndDropEvent event);
+    method public default void onExited(androidx.compose.ui.draganddrop.DragAndDropEvent event);
+    method public default void onMoved(androidx.compose.ui.draganddrop.DragAndDropEvent event);
+    method public default void onStarted(androidx.compose.ui.draganddrop.DragAndDropEvent event);
   }
 
   public final class DragAndDropTransferData {
@@ -2798,7 +2799,7 @@
     property public long doubleTapMinTimeMillis;
     property public long doubleTapTimeoutMillis;
     property public long longPressTimeoutMillis;
-    property public int maximumFlingVelocity;
+    property public float maximumFlingVelocity;
     property public float touchSlop;
   }
 
@@ -3023,13 +3024,13 @@
     method public long getDoubleTapMinTimeMillis();
     method public long getDoubleTapTimeoutMillis();
     method public long getLongPressTimeoutMillis();
-    method public default int getMaximumFlingVelocity();
+    method public default float getMaximumFlingVelocity();
     method public default long getMinimumTouchTargetSize();
     method public float getTouchSlop();
     property public abstract long doubleTapMinTimeMillis;
     property public abstract long doubleTapTimeoutMillis;
     property public abstract long longPressTimeoutMillis;
-    property public default int maximumFlingVelocity;
+    property public default float maximumFlingVelocity;
     property public default long minimumTouchTargetSize;
     property public abstract float touchSlop;
   }
diff --git a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/draganddrop/AndroidDragAndDropTest.kt b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/draganddrop/AndroidDragAndDropTest.kt
index 7408c40..4e87142 100644
--- a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/draganddrop/AndroidDragAndDropTest.kt
+++ b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/draganddrop/AndroidDragAndDropTest.kt
@@ -950,41 +950,41 @@
 
     @OptIn(ExperimentalFoundationApi::class)
     val modifier = Modifier.dragAndDropTarget(
-        acceptDragAndDropTransfer = accept@{
-            if (!acceptsDragAndDrop()) return@accept null
-            DragAndDropTarget(
-                onStarted = {
-                    startOffsets.add(
-                        Offset(x = it.dragEvent.x, y = it.dragEvent.y)
-                    )
-                },
-                onEntered = {
-                    enterOffsets.add(
-                        Offset(x = it.dragEvent.x, y = it.dragEvent.y)
-                    )
-                },
-                onMoved = {
-                    moveOffsets.add(
-                        Offset(x = it.dragEvent.x, y = it.dragEvent.y)
-                    )
-                },
-                onDropped = {
-                    dropOffsets.add(
-                        Offset(x = it.dragEvent.x, y = it.dragEvent.y)
-                    )
-                    true
-                },
-                onExited = {
-                    exitOffsets.add(
-                        Offset(x = it.dragEvent.x, y = it.dragEvent.y)
-                    )
-                },
-                onEnded = {
-                    endedOffsets.add(
-                        Offset(x = it.dragEvent.x, y = it.dragEvent.y)
-                    )
-                }
-            )
+        target = DragAndDropTarget(
+            onStarted = {
+                startOffsets.add(
+                    Offset(x = it.dragEvent.x, y = it.dragEvent.y)
+                )
+            },
+            onEntered = {
+                enterOffsets.add(
+                    Offset(x = it.dragEvent.x, y = it.dragEvent.y)
+                )
+            },
+            onMoved = {
+                moveOffsets.add(
+                    Offset(x = it.dragEvent.x, y = it.dragEvent.y)
+                )
+            },
+            onDrop = {
+                dropOffsets.add(
+                    Offset(x = it.dragEvent.x, y = it.dragEvent.y)
+                )
+                true
+            },
+            onExited = {
+                exitOffsets.add(
+                    Offset(x = it.dragEvent.x, y = it.dragEvent.y)
+                )
+            },
+            onEnded = {
+                endedOffsets.add(
+                    Offset(x = it.dragEvent.x, y = it.dragEvent.y)
+                )
+            }
+        ),
+        shouldStartDragAndDrop = {
+            acceptsDragAndDrop()
         },
     )
 }
diff --git a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/viewinterop/VelocityTrackingParityTest.kt b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/viewinterop/VelocityTrackingParityTest.kt
index 124d13f..9e512d2 100644
--- a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/viewinterop/VelocityTrackingParityTest.kt
+++ b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/viewinterop/VelocityTrackingParityTest.kt
@@ -415,8 +415,7 @@
     onDragStopped: (velocity: Velocity) -> Unit
 ) {
     val viewConfiguration = object : ViewConfiguration by LocalViewConfiguration.current {
-        override val maximumFlingVelocity: Int
-            get() = Int.MAX_VALUE // unlimited
+        override val maximumFlingVelocity: Float get() = Float.MAX_VALUE // unlimited
     }
     CompositionLocalProvider(LocalViewConfiguration provides viewConfiguration) {
         Box(
diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeView.android.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeView.android.kt
index 164f08f..150fe72 100644
--- a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeView.android.kt
+++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeView.android.kt
@@ -2302,7 +2302,7 @@
                 accepted
             }
 
-            DragEvent.ACTION_DROP -> rootDragAndDropNode.onDropped(dragAndDropEvent)
+            DragEvent.ACTION_DROP -> rootDragAndDropNode.onDrop(dragAndDropEvent)
 
             DragEvent.ACTION_DRAG_ENTERED -> {
                 rootDragAndDropNode.onEntered(dragAndDropEvent)
diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidViewConfiguration.android.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidViewConfiguration.android.kt
index b0c5420..4193477 100644
--- a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidViewConfiguration.android.kt
+++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidViewConfiguration.android.kt
@@ -31,6 +31,6 @@
     override val touchSlop: Float
         get() = viewConfiguration.scaledTouchSlop.toFloat()
 
-    override val maximumFlingVelocity: Int
-        get() = viewConfiguration.scaledMaximumFlingVelocity
+    override val maximumFlingVelocity: Float
+        get() = viewConfiguration.scaledMaximumFlingVelocity.toFloat()
 }
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/draganddrop/DragAndDrop.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/draganddrop/DragAndDrop.kt
index f00a92e..b731d64 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/draganddrop/DragAndDrop.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/draganddrop/DragAndDrop.kt
@@ -39,10 +39,10 @@
  * A factory method for creating a [DragAndDropTarget] to receive transfer data from a
  * drag and drop session.
  *
- * @param onDropped The item has been dropped inside this [DragAndDropTarget].
+ * @param onDrop The item has been dropped inside this [DragAndDropTarget].
  * returning true indicates that the [DragAndDropEvent] was consumed, false indicates it was
  * rejected.
- * @see [DragAndDropTarget.onDropped]
+ * @see [DragAndDropTarget.onDrop]
  *
  * @param onStarted The drag and drop session has begun. This gives this [DragAndDropTarget]
  * an opportunity to present itself in a way to indicate it is capable of receiving a
@@ -66,34 +66,34 @@
  * @see [DragAndDropTarget.onEnded]
  */
 fun DragAndDropTarget(
-    onDropped: (event: DragAndDropEvent) -> Boolean,
-    onStarted: (event: DragAndDropEvent) -> Unit = {},
-    onEntered: (event: DragAndDropEvent) -> Unit = {},
-    onMoved: (event: DragAndDropEvent) -> Unit = {},
-    onChanged: (event: DragAndDropEvent) -> Unit = {},
-    onExited: (event: DragAndDropEvent) -> Unit = {},
-    onEnded: (event: DragAndDropEvent) -> Unit = {},
+    onDrop: (event: DragAndDropEvent) -> Boolean,
+    onStarted: ((event: DragAndDropEvent) -> Unit)? = null,
+    onEntered: ((event: DragAndDropEvent) -> Unit)? = null,
+    onMoved: ((event: DragAndDropEvent) -> Unit)? = null,
+    onChanged: ((event: DragAndDropEvent) -> Unit)? = null,
+    onExited: ((event: DragAndDropEvent) -> Unit)? = null,
+    onEnded: ((event: DragAndDropEvent) -> Unit)? = null,
 ): DragAndDropTarget = object : DragAndDropTarget {
-    override fun onStarted(event: DragAndDropEvent) =
-        onStarted.invoke(event)
+    override fun onDrop(event: DragAndDropEvent): Boolean =
+        onDrop.invoke(event)
 
-    override fun onDropped(event: DragAndDropEvent): Boolean =
-        onDropped.invoke(event)
+    override fun onStarted(event: DragAndDropEvent) =
+        onStarted?.invoke(event) ?: Unit
 
     override fun onEntered(event: DragAndDropEvent) =
-        onEntered.invoke(event)
+        onEntered?.invoke(event) ?: Unit
 
     override fun onMoved(event: DragAndDropEvent) =
-        onMoved.invoke(event)
+        onMoved?.invoke(event) ?: Unit
 
     override fun onExited(event: DragAndDropEvent) =
-        onExited.invoke(event)
+        onExited?.invoke(event) ?: Unit
 
     override fun onChanged(event: DragAndDropEvent) =
-        onChanged.invoke(event)
+        onChanged?.invoke(event) ?: Unit
 
     override fun onEnded(event: DragAndDropEvent) =
-        onEnded.invoke(event)
+        onEnded?.invoke(event) ?: Unit
 }
 
 /**
@@ -101,45 +101,45 @@
  */
 interface DragAndDropTarget {
 
-    /** A drag and drop session has just been started and this [DragAndDropTarget] is eligible
-     * to receive it. This gives an opportunity to set the state for a [DragAndDropTarget] in
-     * preparation for consuming a drag and drop session.
-     */
-    fun onStarted(event: DragAndDropEvent)
-
     /**
      * An item has been dropped inside this [DragAndDropTarget].
      *
      * @return true to indicate that the [DragAndDropEvent] was consumed; false indicates it was
      * rejected.
      */
-    fun onDropped(event: DragAndDropEvent): Boolean
+    fun onDrop(event: DragAndDropEvent): Boolean
+
+    /** A drag and drop session has just been started and this [DragAndDropTarget] is eligible
+     * to receive it. This gives an opportunity to set the state for a [DragAndDropTarget] in
+     * preparation for consuming a drag and drop session.
+     */
+    fun onStarted(event: DragAndDropEvent) = Unit
 
     /**
      * An item being dropped has entered into the bounds of this [DragAndDropTarget].
      */
-    fun onEntered(event: DragAndDropEvent)
+    fun onEntered(event: DragAndDropEvent) = Unit
 
     /**
      * An item being dropped has moved within the bounds of this [DragAndDropTarget].
      */
-    fun onMoved(event: DragAndDropEvent)
+    fun onMoved(event: DragAndDropEvent) = Unit
 
     /**
      * An item being dropped has moved outside the bounds of this [DragAndDropTarget].
      */
-    fun onExited(event: DragAndDropEvent)
+    fun onExited(event: DragAndDropEvent) = Unit
 
     /**
      * An event in the current drag and drop session has changed within this [DragAndDropTarget]
      * bounds. Perhaps a modifier key has been pressed or released.
      */
-    fun onChanged(event: DragAndDropEvent)
+    fun onChanged(event: DragAndDropEvent) = Unit
 
     /**
      * The drag and drop session has been completed. All [DragAndDropTarget] instances in the
      * hierarchy that previously received an [onStarted] event will receive this event. This gives
      * an opportunity to reset the state for a [DragAndDropTarget].
      */
-    fun onEnded(event: DragAndDropEvent)
+    fun onEnded(event: DragAndDropEvent) = Unit
 }
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/draganddrop/DragAndDropNode.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/draganddrop/DragAndDropNode.kt
index fd067de..b7f8d70 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/draganddrop/DragAndDropNode.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/draganddrop/DragAndDropNode.kt
@@ -68,25 +68,43 @@
 }
 
 /**
- * Creates a [Modifier.Node] for integrating with platform level drag and drop events. All
- * [DragAndDropModifierNode] instances provided by this function may start drag and drop events
- * by calling [DragAndDropModifierNode.drag].
+ * Creates a [Modifier.Node] for starting platform drag and drop sessions with the intention of
+ * transferring data. A drag and stop session is started by calling [DragAndDropModifierNode.drag].
+ */
+fun DragAndDropModifierNode(): DragAndDropModifierNode = DragAndDropNode { null }
+
+/**
+ * Creates a [Modifier.Node] for receiving transfer data from platform drag and drop sessions. All
+ * [DragAndDropModifierNode] instances provided by this function may also start drag and drop
+ * sessions by calling [DragAndDropModifierNode.drag].
  *
- * @param acceptDragAndDropTransfer a provider of a [DragAndDropTarget] that allows
- * this [Modifier.Node] to receive from a drag and drop gesture on a per session basis.
+ * @param shouldStartDragAndDrop allows for inspecting the start [DragAndDropEvent] for a given
+ * session to decide whether or not the provided [DragAndDropTarget] would like to receive from it.
  *
- * If one is not provided for a given session, this [DragAndDropModifierNode] will not receive
- * [DragAndDropTarget] events for that session.
+ * @param target allows for receiving events and transfer data from a given drag and drop session.
+ *
  */
 fun DragAndDropModifierNode(
-    acceptDragAndDropTransfer: (event: DragAndDropEvent) -> DragAndDropTarget? = { null }
-): DragAndDropModifierNode = DragAndDropNode(acceptDragAndDropTransfer)
+    shouldStartDragAndDrop: (event: DragAndDropEvent) -> Boolean,
+    target: DragAndDropTarget
+): DragAndDropModifierNode = DragAndDropNode { startEvent ->
+    if (shouldStartDragAndDrop(startEvent)) target
+    else null
+}
 
 /**
  * Core implementation of drag and drop. This [Modifier.Node] implements tree traversal for
  * drag and drop, as well as hit testing and propagation of events for drag or drop gestures.
  *
  * It uses the [DragAndDropEvent] as a representation of a single mutable drag and drop session.
+ *
+ * The implementation implicitly maintains a sorted tree of nodes where the order of traversal
+ * is determined by the proximity to the last event. That is, after finding a receiving node,
+ * the next event will follow the same path the previous event did unless a fork is found and
+ * another node should receive the event.
+ *
+ * This optimizes traversal for the common case of move events where the event remains within
+ * a single node, or moves to a sibling of the node.
  */
 internal class DragAndDropNode(
     private val onDragAndDropStart: (event: DragAndDropEvent) -> DragAndDropTarget?
@@ -241,11 +259,11 @@
         lastChildDragAndDropModifierNode = null
     }
 
-    override fun onDropped(event: DragAndDropEvent): Boolean {
+    override fun onDrop(event: DragAndDropEvent): Boolean {
         return when (val currentChildDropTarget = lastChildDragAndDropModifierNode) {
-            null -> thisDragAndDropTarget?.onDropped(event = event) ?: false
+            null -> thisDragAndDropTarget?.onDrop(event = event) ?: false
 
-            else -> currentChildDropTarget.onDropped(event = event)
+            else -> currentChildDropTarget.onDrop(event = event)
         }
     }
 
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/platform/ViewConfiguration.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/platform/ViewConfiguration.kt
index 6a829a5..be65629 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/platform/ViewConfiguration.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/platform/ViewConfiguration.kt
@@ -56,7 +56,7 @@
         get() = DpSize(48.dp, 48.dp)
 
     /**
-     * The maximum velocity a fling can start with.
+     * The maximum velocity a fling have at any given time. This value should be in pixels/second.
      */
-    val maximumFlingVelocity: Int get() = Int.MAX_VALUE
+    val maximumFlingVelocity: Float get() = Float.MAX_VALUE
 }
diff --git a/core/core/api/current.txt b/core/core/api/current.txt
index dab752b..3f2ca45 100644
--- a/core/core/api/current.txt
+++ b/core/core/api/current.txt
@@ -875,6 +875,11 @@
     method public void removeOnPictureInPictureModeChangedListener(androidx.core.util.Consumer<androidx.core.app.PictureInPictureModeChangedInfo> listener);
   }
 
+  public interface OnUserLeaveHintProvider {
+    method public void addOnUserLeaveHintListener(Runnable listener);
+    method public void removeOnUserLeaveHintListener(Runnable listener);
+  }
+
   public final class PendingIntentCompat {
     method public static android.app.PendingIntent getActivities(android.content.Context, int, android.content.Intent![], int, android.os.Bundle?, boolean);
     method public static android.app.PendingIntent getActivities(android.content.Context, int, android.content.Intent![], int, boolean);
diff --git a/core/core/api/restricted_current.txt b/core/core/api/restricted_current.txt
index 4b5af4c2..0d02c43 100644
--- a/core/core/api/restricted_current.txt
+++ b/core/core/api/restricted_current.txt
@@ -977,6 +977,11 @@
     method public void removeOnPictureInPictureModeChangedListener(androidx.core.util.Consumer<androidx.core.app.PictureInPictureModeChangedInfo> listener);
   }
 
+  public interface OnUserLeaveHintProvider {
+    method public void addOnUserLeaveHintListener(Runnable listener);
+    method public void removeOnUserLeaveHintListener(Runnable listener);
+  }
+
   public final class PendingIntentCompat {
     method public static android.app.PendingIntent getActivities(android.content.Context, int, android.content.Intent![], int, android.os.Bundle?, boolean);
     method public static android.app.PendingIntent getActivities(android.content.Context, int, android.content.Intent![], int, boolean);
diff --git a/core/core/src/main/java/androidx/core/app/OnUserLeaveHintProvider.kt b/core/core/src/main/java/androidx/core/app/OnUserLeaveHintProvider.kt
new file mode 100644
index 0000000..6df787c
--- /dev/null
+++ b/core/core/src/main/java/androidx/core/app/OnUserLeaveHintProvider.kt
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2023 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.core.app
+
+import android.app.Activity
+
+/**
+ * Interface for components that can dispatch calls from
+ * [Activity.onUserLeaveHint].
+ */
+interface OnUserLeaveHintProvider {
+    /**
+     * Add a new listener that will get a callback associated with
+     * [Activity.onUserLeaveHint]
+     *
+     * @param listener The listener that should be called whenever
+     * [Activity.onUserLeaveHint] was called.
+     */
+    fun addOnUserLeaveHintListener(
+        listener: Runnable
+    )
+
+    /**
+     * Remove a previously added listener. It will not receive any future callbacks.
+     *
+     * @param listener The listener previously added with
+     * [addOnUserLeaveHintListener] that should be removed.
+     */
+    fun removeOnUserLeaveHintListener(
+        listener: Runnable
+    )
+}
diff --git a/development/split_change_into_owners.sh b/development/split_change_into_owners.sh
index 9405140..7d7996f 100755
--- a/development/split_change_into_owners.sh
+++ b/development/split_change_into_owners.sh
@@ -1,3 +1,4 @@
+#!/bin/bash
 set -e
 
 if [ ! -e .git ]; then
@@ -21,6 +22,7 @@
 ownedDirs="$(echo "$ownersFiles" | sed 's|/OWNERS||' | sort -r)"
 
 for d in $ownedDirs; do
+  echo "Checking $d"
   git add "$d"
   if git status | grep -i "changes to be committed" >/dev/null; then
     echo making commit for "$d"
diff --git a/development/suppressFailingTests.py b/development/suppressFailingTests.py
index 0e38902..d9c71af 100755
--- a/development/suppressFailingTests.py
+++ b/development/suppressFailingTests.py
@@ -11,27 +11,41 @@
 parser = argparse.ArgumentParser(
   description=__doc__
 )
-parser.add_argument("config_path", help="Path of file to process, downloaded from go/androidx-test-failures", nargs="+")
+parser.add_argument("-v", help="Verbose", action="store_true")
 
 dirOfThisScript = os.path.dirname(os.path.realpath(__file__))
 supportRoot = os.path.dirname(dirOfThisScript)
 
+logger = None
+
+class PrintLogger(object):
+  def log(self, message):
+    print(message)
+
+class DisabledLogger(object):
+  def log(self, message):
+    pass
+
+def log(message):
+  logger.log(message)
+
 class LocatedFailure(object):
-  def __init__(self, failure, location):
+  def __init__(self, failure, location, bugId):
     self.failure = failure
     self.location = location
+    self.bugId = bugId
 
 class TestFailure(object):
-  def __init__(self, qualifiedClassName, methodName, testDefinitionName, consistent, branchName, testResultId):
+  def __init__(self, qualifiedClassName, methodName, testDefinitionName, branchName, testFailureUrl, bugId):
     self.qualifiedClassName = qualifiedClassName
     self.methodName = methodName
     self.testDefinitionName = testDefinitionName
-    self.consistent = consistent
     self.branchName = branchName
-    self.testResultId = testResultId
+    self.failureUrl = testFailureUrl
+    self.bugId = bugId
 
   def getUrl(self):
-    return "https://android-build.googleplex.com/builds/tests/view?testResultId=" + self.testResultId
+    return self.testFailureUrl
 
 class FailuresDatabase(object):
   """A collection of LocatedFailure instances, organized by their locations"""
@@ -47,11 +61,6 @@
     lineNumber = locatedFailure.location.lineNumber
     if lineNumber not in failuresAtPath:
       failuresAtPath[lineNumber] = locatedFailure
-    else:
-      # already have a failure at this location
-      if not failuresAtPath[lineNumber].failure.consistent:
-        # if the previously detected failure wasn't consistent, update with the new one
-        failuresAtPath[lineNumber] = locatedFailure
 
   # returns Map<String, LocatedFailure> with key being filePath
   def getAll(self):
@@ -65,39 +74,78 @@
       results[path] = resultsAtPath
     return results
 
-# parses the data file containing the failing tests
-def parse():
-  arguments = parser.parse_args()
-  configPath = arguments.config_path[0]
+def parseBugLine(bugId, line):
+  components = line.split(" | ")
+  if len(components) < 3:
+    return None
+  testLink = components[1]
+  # Example test link: [compose-ui-uidebugAndroidTest.xml androidx.compose.ui.window.PopupAlignmentTest#popup_correctPosition_alignmentTopCenter_rtl](https://android-build.googleplex.com/builds/tests/view?testResultId=TR96929024659298098)
+  closeBracketIndex = testLink.rindex("]")
+  if closeBracketIndex <= 0:
+    raise Exception("Failed to parse b/" + bugId + " '" + line + "', testLink '" + testLink + "', closeBracketIndex = " + str(closeBracketIndex))
+  linkText = testLink[1:closeBracketIndex]
+  linkDest = testLink[closeBracketIndex + 1:]
+  # Example linkText: compose-ui-uidebugAndroidTest.xml androidx.compose.ui.window.PopupAlignmentTest#popup_correctPosition_alignmentTopCenter_rtl
+  # Example linkDest: (https://android-build.googleplex.com/builds/tests/view?testResultId=TR96929024659298098)
+  testResultUrl = linkDest.replace("(", "").replace(")", "")
+  # Example testResultUrl: https://android-build.googleplex.com/builds/tests/view?testResultId=TR96929024659298098
+  spaceIndex = linkText.index(" ")
+  if spaceIndex <= 0:
+    raise Exception("Failed to parse b/" + bugId + " '" + line + "', linkText = '" + linkText + ", spaceIndex = " + str(spaceIndex))
+  testDefinitionName = linkText[:spaceIndex]
+  testPath = linkText[spaceIndex+1:]
+  # Example test path: androidx.compose.ui.window.PopupAlignmentTest#popup_correctPosition_alignmentTopCenter_rtl
+  testPathSplit = testPath.split("#")
+  if len(testPathSplit) != 2:
+    raise Exception("Failed to parse b/" + bugId + " '" + line + "', testPath = '" + testPath + "', len(testPathSplit) = " + str(len(testPathSplit)))
+  testClass, testMethod = testPathSplit
+
+  branchName = components[2].strip()
+  print("  parsed test failure class=" + testClass + " method='" + testMethod + "' definition=" + testDefinitionName + " branch=" + branchName + " failureUrl=" + testResultUrl + " bugId=" + bugId)
+  return TestFailure(testClass, testMethod, testDefinitionName, branchName, testResultUrl, bugId)
+
+def parseBug(bugId):
+  bugText = shellRunner.runAndGetOutput(["bugged", "show", bugId])
+  log("bug text = '" + bugText + "'")
   failures = []
-  with open(configPath) as configFile:
-    config = csv.DictReader(configFile, delimiter="\t")
-    for item in config:
-      # Decide whether this failure appears to be happening reliably (consistent = True)
-      # or flakily (consistent = False).
-      #
-      # A flaky failure will probably occur a small number (probably 1) of times in a row
-      # and a small fraction of times (slightly more than 0%),
-      #
-      # whereas a consistent failure will probably occur a large number of times (until we address
-      # it, probably at least 3) and about 100% of the time
-      #
-      # These cutoff values are mostly arbitrary, about halfway between the expectations for these
-      # two types of failures
-      if int(item["consecutive_failures"]) >= 2 and float(item["failure_rate"]) > 0.5:
-        consistent = True
-      else:
-        consistent = False
-      failures.append(
-        TestFailure(
-          item["test_class"],
-          item["method"],
-          item["test_definition_name"],
-          consistent,
-          item["branch_name"],
-          item["test_result_id"]
-        )
-      )
+  bugLines = bugText.split("\n")
+
+  stillFailing = True
+  listingTests = False
+  for i in range(len(bugLines)):
+    line = bugLines[i]
+    #log("parsing bug line " + line)
+    if listingTests:
+      failure = parseBugLine(bugId, line)
+      if failure is not None:
+        failures.append(failure)
+    if "---|---|---|---|---" in line: # table start
+      listingTests = True
+    if " # " in line: # start of new section
+      listingTests = False
+    if "There are no more failing tests in this regression" in line or "ATN has not seen a failure for this regression recently." in line or "This regression has been identified as a duplicate of another one" in line:
+      stillFailing = False
+  if len(failures) < 1:
+    raise Exception("Failed to parse b/" + bugId + ": identified 0 failures. Rerun with -v for more information")
+  if not stillFailing:
+    print("tests no longer failing")
+    return []
+  return failures
+
+# identifies failing tests
+def getFailureData():
+  bugsQuery = ["bugged", "search", "hotlistid:5083126 status:open", "--columns", "issue"]
+  print("Searching for bugs: " + str(bugsQuery))
+  bugsOutput = shellRunner.runAndGetOutput(bugsQuery)
+  bugIds = bugsOutput.split("\n")
+  print("Checking " + str(len(bugIds)) + " bugs")
+  failures = []
+  for i in range(len(bugIds)):
+    bugId = bugIds[i].strip()
+    if bugId != "issue" and bugId != "":
+      print("")
+      print("Parsing bug " + bugId + " (" + str(i) + "/" + str(len(bugIds)) + ")")
+      failures += parseBug(bugId)
   return failures
 
 class FileLocation(object):
@@ -315,131 +363,68 @@
   def save(self):
     writeFile(self.path, "\n".join(self.lines))
 
-# searches for bugs matching certain criteria, using the `bugged` CLI tool
-class BugFinder(object):
-  def __init__(self):
-    self.bugsByQuery = {}
-
-  def findForFailure(self, testFailure):
-    qualifiedName = testFailure.qualifiedClassName
-    text = ["title:" + qualifiedName, "status:open", "--columns=issue"]
-    return self.query(text)
-
-  def query(self, args):
-    text = " ".join(args)
-    if text not in self.bugsByQuery:
-      response = None
-      try:
-        response = shellRunner.runAndGetOutput(["bugged", "search"] + args)
-      except FileNotFoundError as e:
-        raise FileNotFoundError("The `bugged` command-line tool is required but was not found. See go/bugged to install.")
-      lines = response.split("\n")
-      result = None
-      for line in response.split("\n"):
-        if line != "issue":
-          result = line
-          break
-      if result == "":
-        result = None
-      self.bugsByQuery[text] = result
-    return self.bugsByQuery[text]
-
-bugFinder = BugFinder()
-
 # converts from a List<TestFailure> to a FailuresDatabase containing LocatedFailure
 def locate(failures):
   db = FailuresDatabase()
   for failure in failures:
     location = classFinder.findMethod(failure.qualifiedClassName, failure.methodName)
     if location is not None:
-      db.add(LocatedFailure(failure, location))
+      db.add(LocatedFailure(failure, location, failure.bugId))
     else:
-      message = "Could not locate " + str(failure.qualifiedClassName) + "#" + str(failure.methodName)
+      message = "Could not locate " + str(failure.qualifiedClassName) + "#" + str(failure.methodName) + " for " + str(failure.bugId)
       if failure.branchName != "aosp-androidx-main":
         message += ", should be in " + failure.branchName
       print(message)
   return db
 
-# removes test result urls from the commit
-def uncommitTestResultUrls():
-  # first, remove test results urls from the files
-  shellRunner.run(["bash", "-c", "git log -1 --name-only | grep -v ' ' | xargs sed -i 's| // .*testResultId.*||g'"])
-  # commit the removal of these test result urls
-  shellRunner.run(["git", "add", "."])
-  shellRunner.run(["git", "commit", "-q", "--amend", "--no-edit"])
-  # restore the previous versions of the files
-  shellRunner.run(["git", "checkout", "-q", "HEAD@{1}", "--", "."])
-  shellRunner.run(["git", "reset", "-q"])
-
 # Given a FailureDatabase, disables all of the tests mentioned in it, by adding the appropriate
 # annotations:
-#  consistent failures get annotated with @Ignore ,
-#  flaky failures get annotated with @FlakyTest.
+#  failures get annotated with @Ignore ,
 # Annotations link to the associated bug if possible
 def disable(failuresDatabase):
-  mentionedBugs = set()
   numUpdates = 0
   failuresByPath = failuresDatabase.getAll()
   for path, failuresAtPath in failuresByPath.items():
     source = SourceFile(path)
     addedIgnore = False
-    addedFlaky = False
     for failure in failuresAtPath:
       lineNumber = failure.location.lineNumber
-      if source.hasAnnotation(lineNumber, "@FlakyTest") or source.hasAnnotation(lineNumber, "@Ignore"):
+      if source.hasAnnotation(lineNumber, "@Ignore"):
         continue
-      bug = bugFinder.findForFailure(failure.failure)
-      if bug is not None:
-        mentionedBugs.add(bug)
-      if failure.failure.consistent:
-        if bug is not None:
-          bugText = '"b/' + bug + '"'
-        else:
-          bugText = '"why"'
-        source.addAnnotation(lineNumber, "@Ignore(" + bugText + ") // " + failure.failure.getUrl())
-        addedIgnore = True
-      else:
-        if bug is not None:
-          bugText = "bugId = " + bug
-        else:
-          bugText = "bugId = num"
-        source.addAnnotation(lineNumber, "@FlakyTest(" + bugText + ") // " + failure.failure.getUrl())
-        addedFlaky = True
+      bugId = failure.bugId
+      bugText = '"b/' + bugId + '"'
+      source.addAnnotation(lineNumber, "@Ignore(" + bugText + ")")
+      addedIgnore = True
     if addedIgnore:
       source.addImport("org.junit.Ignore")
-    if addedFlaky:
-      source.addImport("androidx.test.filters.FlakyTest")
-    if addedIgnore or addedFlaky:
-      # save file
       source.save()
       numUpdates += 1
-  # make git commit
-  commitHeader = """Mostly autogenerated suppression of test failures
+  print("Made " + str(numUpdates) + " updates")
+
+def commit():
+  print("Generating git commit per OWNERS file")
+  os.chdir(supportRoot)
+  commitMessage = """Autogenerated suppression of test failures
 
 This commit was created with the help of development/suppressFailingTests.py
-
 """
+  shellRunner.run(["development/split_change_into_owners.sh", commitMessage])
 
-  bugStanzas = "\n".join(["Bug: " + bug for bug in sorted(mentionedBugs)])
-  commitMessage = commitHeader + bugStanzas
-
-  # make git commit containing the suppressions
-  os.chdir(supportRoot)
-  shellRunner.run(["git", "add", "."])
-  shellRunner.run(["git", "commit", "-q", "--no-edit", "-m", commitMessage])
-
-  # Remove test result urls from the git commit but keep them in the tree
-  uncommitTestResultUrls()
-  print("")
-  print("Committed updates to " + str(numUpdates) + " files. Inspect/fix as needed.")
-  print("")
-  print("Additional context (test failure urls) has been added but not committed.")
-  print("You can manually remove this information or you can run `git checkout -- <path>` to discard uncommitted changes under <path>")
 
 def main():
-  failures = parse()
+  global logger
+  arguments = parser.parse_args()
+  if arguments.v:
+    logger = PrintLogger()
+  else:
+    logger = DisabledLogger()
+  failures = getFailureData()
+  if len(failures) < 1:
+    print("Found 0 failures")
+    return
   locations = locate(failures)
   disable(locations)
+  commit()
 
 if __name__ == "__main__":
   main()
diff --git a/emoji/emoji/src/androidTest/java/androidx/emoji/text/AllEmojisTest.java b/emoji/emoji/src/androidTest/java/androidx/emoji/text/AllEmojisTest.java
index d703043..b56b862 100644
--- a/emoji/emoji/src/androidTest/java/androidx/emoji/text/AllEmojisTest.java
+++ b/emoji/emoji/src/androidTest/java/androidx/emoji/text/AllEmojisTest.java
@@ -29,7 +29,6 @@
 import androidx.emoji.util.TestString;
 import androidx.test.core.app.ApplicationProvider;
 import androidx.test.filters.LargeTest;
-import androidx.test.filters.SdkSuppress;
 
 import org.junit.BeforeClass;
 import org.junit.Test;
@@ -49,7 +48,6 @@
  */
 @LargeTest
 @RunWith(Parameterized.class)
-@SdkSuppress(minSdkVersion = 19)
 public class AllEmojisTest {
 
     /**
diff --git a/emoji2/emoji2-benchmark/src/androidTest/java/androidx/emoji2/benchmark/text/CachedEmojiCompatInitBenchmark.kt b/emoji2/emoji2-benchmark/src/androidTest/java/androidx/emoji2/benchmark/text/CachedEmojiCompatInitBenchmark.kt
index 211dd7f..ea969a4 100644
--- a/emoji2/emoji2-benchmark/src/androidTest/java/androidx/emoji2/benchmark/text/CachedEmojiCompatInitBenchmark.kt
+++ b/emoji2/emoji2-benchmark/src/androidTest/java/androidx/emoji2/benchmark/text/CachedEmojiCompatInitBenchmark.kt
@@ -24,7 +24,6 @@
 import androidx.test.core.app.ApplicationProvider
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.LargeTest
-import androidx.test.filters.SdkSuppress
 import org.junit.Assert.assertNotNull
 import org.junit.Rule
 import org.junit.Test
@@ -32,7 +31,6 @@
 
 @RunWith(AndroidJUnit4::class)
 @LargeTest
-@SdkSuppress(minSdkVersion = 19)
 class CachedEmojiCompatInitBenchmark {
 
     @get:Rule
diff --git a/emoji2/emoji2-benchmark/src/androidTest/java/androidx/emoji2/benchmark/text/EmojiHasGlyphBenchmark.kt b/emoji2/emoji2-benchmark/src/androidTest/java/androidx/emoji2/benchmark/text/EmojiHasGlyphBenchmark.kt
index b42d928..a4fd128 100644
--- a/emoji2/emoji2-benchmark/src/androidTest/java/androidx/emoji2/benchmark/text/EmojiHasGlyphBenchmark.kt
+++ b/emoji2/emoji2-benchmark/src/androidTest/java/androidx/emoji2/benchmark/text/EmojiHasGlyphBenchmark.kt
@@ -22,14 +22,12 @@
 import androidx.core.graphics.PaintCompat
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.LargeTest
-import androidx.test.filters.SdkSuppress
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
 
 @RunWith(AndroidJUnit4::class)
 @LargeTest
-@SdkSuppress(minSdkVersion = 19)
 class EmojiHasGlyphBenchmark {
 
     @get:Rule
diff --git a/emoji2/emoji2-benchmark/src/androidTest/java/androidx/emoji2/benchmark/text/EmojiSpanDrawBenchmark.kt b/emoji2/emoji2-benchmark/src/androidTest/java/androidx/emoji2/benchmark/text/EmojiSpanDrawBenchmark.kt
index d7701c9..561f342 100644
--- a/emoji2/emoji2-benchmark/src/androidTest/java/androidx/emoji2/benchmark/text/EmojiSpanDrawBenchmark.kt
+++ b/emoji2/emoji2-benchmark/src/androidTest/java/androidx/emoji2/benchmark/text/EmojiSpanDrawBenchmark.kt
@@ -26,14 +26,12 @@
 import androidx.emoji2.text.EmojiSpan
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.LargeTest
-import androidx.test.filters.SdkSuppress
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
 
 @RunWith(AndroidJUnit4::class)
 @LargeTest
-@SdkSuppress(minSdkVersion = 19)
 class EmojiSpanDrawBenchmark {
 
     @get:Rule
diff --git a/emoji2/emoji2-bundled/src/androidTest/java/androidx/emoji2/bundled/AllEmojisTest.java b/emoji2/emoji2-bundled/src/androidTest/java/androidx/emoji2/bundled/AllEmojisTest.java
index 1cf822f..adc05e1 100644
--- a/emoji2/emoji2-bundled/src/androidTest/java/androidx/emoji2/bundled/AllEmojisTest.java
+++ b/emoji2/emoji2-bundled/src/androidTest/java/androidx/emoji2/bundled/AllEmojisTest.java
@@ -33,7 +33,6 @@
 import androidx.emoji2.text.TypefaceEmojiRasterizer;
 import androidx.test.core.app.ApplicationProvider;
 import androidx.test.filters.LargeTest;
-import androidx.test.filters.SdkSuppress;
 
 import org.junit.BeforeClass;
 import org.junit.Test;
@@ -53,7 +52,6 @@
  */
 @LargeTest
 @RunWith(Parameterized.class)
-@SdkSuppress(minSdkVersion = 19)
 public class AllEmojisTest {
 
     /**
diff --git a/emoji2/emoji2-bundled/src/androidTest/java/androidx/emoji2/bundled/ConfigTest.java b/emoji2/emoji2-bundled/src/androidTest/java/androidx/emoji2/bundled/ConfigTest.java
index 228edf4..9e04b8f 100644
--- a/emoji2/emoji2-bundled/src/androidTest/java/androidx/emoji2/bundled/ConfigTest.java
+++ b/emoji2/emoji2-bundled/src/androidTest/java/androidx/emoji2/bundled/ConfigTest.java
@@ -39,7 +39,6 @@
 import androidx.test.core.app.ApplicationProvider;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.MediumTest;
-import androidx.test.filters.SdkSuppress;
 import androidx.test.platform.app.InstrumentationRegistry;
 
 import org.hamcrest.Matchers;
@@ -59,28 +58,24 @@
     }
 
     @Test(expected = NullPointerException.class)
-    @SdkSuppress(minSdkVersion = 19)
     public void testConstructor_throwsExceptionIfMetadataLoaderNull() {
         //noinspection ConstantConditions
         new TestConfigBuilder.TestConfig(null);
     }
 
     @Test(expected = NullPointerException.class)
-    @SdkSuppress(minSdkVersion = 19)
     public void testInitCallback_throwsExceptionIfNull() {
         //noinspection ConstantConditions
         new ValidTestConfig().registerInitCallback(null);
     }
 
     @Test(expected = NullPointerException.class)
-    @SdkSuppress(minSdkVersion = 19)
     public void testUnregisterInitCallback_throwsExceptionIfNull() {
         //noinspection ConstantConditions
         new ValidTestConfig().unregisterInitCallback(null);
     }
 
     @Test
-    @SdkSuppress(minSdkVersion = 19)
     public void testBuild_withDefaultValues() {
         final EmojiCompat.Config config = new ValidTestConfig().setReplaceAll(true);
 
@@ -94,7 +89,6 @@
     }
 
     @Test
-    @SdkSuppress(minSdkVersion = 19)
     public void testInitCallback_callsSuccessCallback() {
         final EmojiCompat.InitCallback initCallback1 = mock(EmojiCompat.InitCallback.class);
         final EmojiCompat.InitCallback initCallback2 = mock(EmojiCompat.InitCallback.class);
@@ -109,7 +103,6 @@
     }
 
     @Test
-    @SdkSuppress(minSdkVersion = 19) //Fail callback never called for pre 19
     public void testInitCallback_callsFailCallback() {
         final EmojiCompat.InitCallback initCallback1 = mock(EmojiCompat.InitCallback.class);
         final EmojiCompat.InitCallback initCallback2 = mock(EmojiCompat.InitCallback.class);
@@ -128,7 +121,6 @@
     }
 
     @Test
-    @SdkSuppress(minSdkVersion = 19)
     public void testBuild_withEmojiSpanIndicator() {
         EmojiCompat.Config config = new ValidTestConfig();
         EmojiCompat emojiCompat = EmojiCompat.reset(config);
@@ -142,7 +134,6 @@
     }
 
     @Test
-    @SdkSuppress(minSdkVersion = 19)
     public void testBuild_withEmojiSpanIndicatorColor() {
         EmojiCompat.Config config = new ValidTestConfig();
         EmojiCompat emojiCompat = EmojiCompat.reset(config);
@@ -156,7 +147,6 @@
     }
 
     @Test
-    @SdkSuppress(minSdkVersion = 19)
     public void testBuild_defaultEmojiSpanIndicatorColor() {
         final EmojiCompat.Config config = new ValidTestConfig().setEmojiSpanIndicatorEnabled(true);
         final EmojiCompat emojiCompat = EmojiCompat.reset(config);
@@ -177,7 +167,6 @@
     }
 
     @Test
-    @SdkSuppress(minSdkVersion = 19)
     public void testGlyphCheckerInstance_EmojiSpan_isNotAdded_whenHasGlyph_returnsTrue() {
         final EmojiCompat.GlyphChecker glyphChecker = mock(EmojiCompat.GlyphChecker.class);
         when(glyphChecker.hasGlyph(any(CharSequence.class), anyInt(), anyInt(), anyInt()))
@@ -197,7 +186,6 @@
     }
 
     @Test
-    @SdkSuppress(minSdkVersion = 19)
     public void testGlyphCheckerInstance_EmojiSpan_isAdded_whenHasGlyph_returnsFalse() {
         final EmojiCompat.GlyphChecker glyphChecker = mock(EmojiCompat.GlyphChecker.class);
         when(glyphChecker.hasGlyph(any(CharSequence.class), anyInt(), anyInt(), anyInt()))
diff --git a/emoji2/emoji2-bundled/src/androidTest/java/androidx/emoji2/bundled/EmojiCompatTest.java b/emoji2/emoji2-bundled/src/androidTest/java/androidx/emoji2/bundled/EmojiCompatTest.java
index b3345ca..78fad3c 100644
--- a/emoji2/emoji2-bundled/src/androidTest/java/androidx/emoji2/bundled/EmojiCompatTest.java
+++ b/emoji2/emoji2-bundled/src/androidTest/java/androidx/emoji2/bundled/EmojiCompatTest.java
@@ -29,7 +29,6 @@
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.reset;
-import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyNoMoreInteractions;
@@ -39,26 +38,21 @@
 import android.content.Context;
 import android.os.Bundle;
 import android.text.Editable;
-import android.text.Selection;
 import android.text.Spannable;
 import android.text.SpannableString;
 import android.text.SpannableStringBuilder;
 import android.text.SpannedString;
-import android.view.KeyEvent;
 import android.view.inputmethod.EditorInfo;
-import android.view.inputmethod.InputConnection;
 
 import androidx.annotation.RequiresApi;
 import androidx.emoji2.bundled.util.Emoji;
 import androidx.emoji2.bundled.util.EmojiMatcher;
-import androidx.emoji2.bundled.util.KeyboardUtil;
 import androidx.emoji2.bundled.util.TestString;
 import androidx.emoji2.text.DefaultEmojiCompatConfig;
 import androidx.emoji2.text.EmojiCompat;
 import androidx.emoji2.text.EmojiSpan;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.MediumTest;
-import androidx.test.filters.SdkSuppress;
 import androidx.test.platform.app.InstrumentationRegistry;
 
 import org.hamcrest.Matchers;
@@ -120,7 +114,6 @@
     }
 
     @Test
-    @SdkSuppress(minSdkVersion = 19)
     public void testInitWithContext_returnsInstanceWhenFound() {
         EmojiCompat.reset((EmojiCompat) null);
         EmojiCompat.skipDefaultConfigurationLookup(false);
@@ -158,7 +151,6 @@
     }
 
     @Test
-    @SdkSuppress(minSdkVersion = 19)
     public void testProcess_returnsEmptySpanned_withEmptyString() {
         final CharSequence charSequence = EmojiCompat.get().process("");
         assertNotNull(charSequence);
@@ -205,7 +197,6 @@
     }
 
     @Test
-    @SdkSuppress(minSdkVersion = 19)
     public void testProcess_doesNotAddEmojiSpan() {
         final String string = "abc";
         final CharSequence charSequence = EmojiCompat.get().process(string);
@@ -215,58 +206,36 @@
     }
 
     @Test
-    @SdkSuppress(maxSdkVersion = 18)
-    public void testProcess_returnsSameCharSequence_pre19() {
-        assertNull(EmojiCompat.get().process(null));
-
-        CharSequence testString = "abc";
-        assertSame(testString, EmojiCompat.get().process(testString));
-
-        testString = new SpannableString("abc");
-        assertSame(testString, EmojiCompat.get().process(testString));
-
-        testString = new TestString(Emoji.CHAR_DEFAULT_EMOJI_STYLE).toString();
-        assertSame(testString, EmojiCompat.get().process(testString));
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = 19)
     public void testProcess_addsSingleCodePointEmoji() {
         assertCodePointMatch(Emoji.EMOJI_SINGLE_CODEPOINT);
     }
 
     @Test
-    @SdkSuppress(minSdkVersion = 19)
     public void testProcess_addsFlagEmoji() {
         assertCodePointMatch(Emoji.EMOJI_FLAG);
     }
 
     @Test
-    @SdkSuppress(minSdkVersion = 19)
     public void testProcess_addsUnknownFlagEmoji() {
         assertCodePointMatch(Emoji.EMOJI_UNKNOWN_FLAG);
     }
 
     @Test
-    @SdkSuppress(minSdkVersion = 19)
     public void testProcess_addsRegionalIndicatorSymbol() {
         assertCodePointMatch(Emoji.EMOJI_REGIONAL_SYMBOL);
     }
 
     @Test
-    @SdkSuppress(minSdkVersion = 19)
     public void testProcess_addsKeyCapEmoji() {
         assertCodePointMatch(Emoji.EMOJI_DIGIT_KEYCAP);
     }
 
     @Test
-    @SdkSuppress(minSdkVersion = 19)
     public void testProcess_doesNotAddEmojiForNumbers() {
         assertCodePointDoesNotMatch(new int[] {Emoji.CHAR_DIGIT});
     }
 
     @Test
-    @SdkSuppress(minSdkVersion = 19)
     public void testProcess_doesNotAddEmojiForNumbers_1() {
         final TestString string = new TestString(Emoji.EMOJI_SINGLE_CODEPOINT).append('1', 'f');
         CharSequence charSequence = EmojiCompat.get().process(string.toString());
@@ -274,50 +243,42 @@
     }
 
     @Test
-    @SdkSuppress(minSdkVersion = 19)
     public void testProcess_addsVariantSelectorEmoji() {
         assertCodePointMatch(Emoji.EMOJI_DIGIT_ES);
     }
 
     @Test
-    @SdkSuppress(minSdkVersion = 19)
     public void testProcess_doesNotAddVariantSelectorTextStyle() {
         assertCodePointDoesNotMatch(new int[]{Emoji.CHAR_DIGIT, Emoji.CHAR_VS_TEXT});
     }
 
     @Test
-    @SdkSuppress(minSdkVersion = 19)
     public void testProcess_addsVariantSelectorAndKeyCapEmoji() {
         assertCodePointMatch(Emoji.EMOJI_DIGIT_ES_KEYCAP);
     }
 
     @Test
-    @SdkSuppress(minSdkVersion = 19)
     public void testProcess_doesNotAddEmoji_forVariantBaseWithoutSelector() {
         assertCodePointDoesNotMatch(new int[]{Emoji.CHAR_DIGIT});
     }
 
     @Test
-    @SdkSuppress(minSdkVersion = 19)
     public void testProcess_addsAsteriskKeyCapEmoji() {
         assertCodePointMatch(Emoji.EMOJI_ASTERISK_KEYCAP);
     }
 
     @Test
-    @SdkSuppress(minSdkVersion = 19)
     public void testProcess_addsSkinModifierEmoji() {
         assertCodePointMatch(Emoji.EMOJI_SKIN_MODIFIER);
         assertCodePointMatch(Emoji.EMOJI_SKIN_MODIFIER_TYPE_ONE);
     }
 
     @Test
-    @SdkSuppress(minSdkVersion = 19)
     public void testProcess_addsSkinModifierEmoji_withVariantSelector() {
         assertCodePointMatch(Emoji.EMOJI_SKIN_MODIFIER_WITH_VS);
     }
 
     @Test
-    @SdkSuppress(minSdkVersion = 19)
     public void testProcess_addsSkinModifierEmoji_270c_withVariantSelector() {
         // 0x270c is a Standardized Variant Base, Emoji Modifier Base and also Emoji
         // therefore it is different than i.e. 0x1f3c3. The code actually failed for this test
@@ -327,14 +288,12 @@
     }
 
     @Test
-    @SdkSuppress(minSdkVersion = 19)
     public void testProcess_defaultStyleDoesNotAddSpan() {
         assertCodePointDoesNotMatch(new int[]{Emoji.CHAR_DEFAULT_TEXT_STYLE});
         assertCodePointMatch(Emoji.DEFAULT_TEXT_STYLE);
     }
 
     @Test
-    @SdkSuppress(minSdkVersion = 19)
     public void testProcess_defaultEmojiStyle_withTextStyleVs() {
         assertCodePointMatch(Emoji.EMOJI_SINGLE_CODEPOINT.id(),
                 new int[]{Emoji.CHAR_DEFAULT_EMOJI_STYLE, Emoji.CHAR_VS_EMOJI});
@@ -342,14 +301,12 @@
     }
 
     @Test
-    @SdkSuppress(minSdkVersion = 19)
     public void testProcess_genderEmoji() {
         assertCodePointMatch(Emoji.EMOJI_GENDER);
         assertCodePointMatch(Emoji.EMOJI_GENDER_WITHOUT_VS);
     }
 
     @Test
-    @SdkSuppress(minSdkVersion = 19)
     public void testProcess_standardizedVariantEmojiExceptions() {
         final int[][] exceptions = new int[][]{
                 {0x2600, 0xF034D},
@@ -373,13 +330,11 @@
     }
 
     @Test
-    @SdkSuppress(minSdkVersion = 19)
     public void testProcess_addsZwjEmoji() {
         assertCodePointMatch(Emoji.EMOJI_WITH_ZWJ);
     }
 
     @Test
-    @SdkSuppress(minSdkVersion = 19)
     public void testProcess_doesNotAddEmojiForNumbersAfterZwjEmo() {
         TestString string = new TestString(Emoji.EMOJI_WITH_ZWJ).append(0x20, 0x2B, 0x31)
                 .withSuffix().withPrefix();
@@ -395,7 +350,6 @@
     }
 
     @Test
-    @SdkSuppress(minSdkVersion = 19)
     public void testProcess_addsEmojiThatFollowsDigit() {
         TestString string = new TestString(Emoji.EMOJI_SINGLE_CODEPOINT).prepend('N', '5');
         CharSequence charSequence = EmojiCompat.get().process(string.toString());
@@ -413,7 +367,6 @@
     }
 
     @Test
-    @SdkSuppress(minSdkVersion = 19)
     public void testProcess_withAppend() {
         final Editable editable = new SpannableStringBuilder(new TestString('a').withPrefix()
                 .withSuffix().toString());
@@ -437,7 +390,6 @@
     }
 
     @Test
-    @SdkSuppress(minSdkVersion = 19)
     public void testProcess_reprocess() {
         final String string = new TestString(Emoji.EMOJI_SINGLE_CODEPOINT)
                 .append(Emoji.EMOJI_SINGLE_CODEPOINT)
@@ -462,7 +414,6 @@
 
     @SuppressLint("Range")
     @Test(expected = IllegalArgumentException.class)
-    @SdkSuppress(minSdkVersion = 19)
     public void testProcess_throwsException_withMaxEmojiSetToNegative() {
         final String original = new TestString(Emoji.EMOJI_SINGLE_CODEPOINT).toString();
 
@@ -473,7 +424,6 @@
     }
 
     @Test
-    @SdkSuppress(minSdkVersion = 19)
     public void testProcess_withMaxEmojiSetToZero() {
         final String original = new TestString(Emoji.EMOJI_SINGLE_CODEPOINT).toString();
 
@@ -484,7 +434,6 @@
     }
 
     @Test
-    @SdkSuppress(minSdkVersion = 19)
     public void testProcess_withMaxEmojiSetToOne() {
         final String original = new TestString(Emoji.EMOJI_SINGLE_CODEPOINT).toString();
 
@@ -496,7 +445,6 @@
     }
 
     @Test
-    @SdkSuppress(minSdkVersion = 19)
     public void testProcess_withMaxEmojiSetToLessThenExistingSpanCount() {
         final String original = new TestString(Emoji.EMOJI_SINGLE_CODEPOINT)
                 .append(Emoji.EMOJI_SINGLE_CODEPOINT)
@@ -520,7 +468,6 @@
     }
 
     @Test
-    @SdkSuppress(minSdkVersion = 19)
     public void testProcess_withMaxEmojiSet_withExistingEmojis() {
         // test string with two emoji characters
         final String original = new TestString(Emoji.EMOJI_SINGLE_CODEPOINT)
@@ -561,7 +508,6 @@
     }
 
     @Test
-    @SdkSuppress(minSdkVersion = 19)
     public void testProcess_withReplaceNonExistent_callsGlyphChecker() {
         final EmojiCompat.GlyphChecker glyphChecker = mock(EmojiCompat.GlyphChecker.class);
         final EmojiCompat.Config config = TestConfigBuilder.freshConfig()
@@ -586,7 +532,6 @@
     }
 
     @Test
-    @SdkSuppress(minSdkVersion = 19)
     public void testProcess_withReplaceDefault_doesNotCallGlyphChecker() {
         final EmojiCompat.GlyphChecker glyphChecker = mock(EmojiCompat.GlyphChecker.class);
         final EmojiCompat.Config config = TestConfigBuilder.freshConfig()
@@ -611,7 +556,6 @@
     }
 
     @Test
-    @SdkSuppress(minSdkVersion = 19)
     public void testProcess_withSpanned_replaceNonExistent() {
         final EmojiCompat.GlyphChecker glyphChecker = mock(EmojiCompat.GlyphChecker.class);
         final EmojiCompat.Config config = TestConfigBuilder.freshConfig()
@@ -654,15 +598,6 @@
     }
 
     @Test
-    @SdkSuppress(maxSdkVersion = 18)
-    public void testEmojiMatch_pre19() {
-        String sequence = new TestString(Emoji.CHAR_DEFAULT_EMOJI_STYLE).toString();
-        assertEquals(EmojiCompat.EMOJI_UNSUPPORTED,
-                EmojiCompat.get().getEmojiMatch(sequence, Integer.MAX_VALUE));
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = 19)
     public void testGetEmojiMatch_returnsMatchForExistingEmoji() {
         final String sequence = new TestString(Emoji.EMOJI_FLAG).toString();
         assertEquals(EmojiCompat.EMOJI_SUPPORTED,
@@ -672,14 +607,12 @@
 
     @SuppressWarnings("deprecation")
     @Test
-    @SdkSuppress(minSdkVersion = 19)
     public void testGetEmojiMatch_returnsMatchForExistingEmoji_deprecatedPath() {
         final String sequence = new TestString(Emoji.EMOJI_FLAG).toString();
         assertTrue(EmojiCompat.get().hasEmojiGlyph(sequence));
     }
 
     @Test
-    @SdkSuppress(minSdkVersion = 19)
     public void testEmojiMatch_returnsDecomposeForPartialMatch() {
         final String sequence = new TestString(Emoji.EMOJI_FLAG)
                 .append(Emoji.EMOJI_FLAG)
@@ -690,7 +623,6 @@
 
     @SuppressWarnings("deprecation")
     @Test
-    @SdkSuppress(minSdkVersion = 19)
     public void testEmojiMatch_returnsDecomposeForPartialMatch_deprecatedPath() {
         final String sequence = new TestString(Emoji.EMOJI_FLAG)
                 .append(Emoji.EMOJI_FLAG)
@@ -713,7 +645,6 @@
     }
 
     @Test
-    @SdkSuppress(minSdkVersion = 19)
     public void testHashEmojiGlyph_withDefaultEmojiStyles() {
         String sequence = new TestString(Emoji.CHAR_DEFAULT_EMOJI_STYLE).toString();
         assertEquals(EmojiCompat.EMOJI_SUPPORTED,
@@ -726,7 +657,6 @@
     }
 
     @Test
-    @SdkSuppress(minSdkVersion = 19)
     public void testEmojiGlyph_TextStyle_doesNotMatch() {
         String sequence = new TestString(Emoji.CHAR_DEFAULT_EMOJI_STYLE, Emoji.CHAR_VS_TEXT)
                 .toString();
@@ -735,7 +665,6 @@
     }
 
     @Test
-    @SdkSuppress(minSdkVersion = 19)
     public void testHashEmojiGlyph_withMetadataVersion() {
         final String sequence = new TestString(Emoji.EMOJI_SINGLE_CODEPOINT).toString();
         assertEquals(EmojiCompat.EMOJI_UNSUPPORTED,
@@ -745,13 +674,6 @@
     }
 
     @Test
-    @SdkSuppress(maxSdkVersion = 18)
-    public void testGetLoadState_returnsSuccess_pre19() {
-        assertEquals(EmojiCompat.get().getLoadState(), EmojiCompat.LOAD_STATE_SUCCEEDED);
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = 19)
     public void testGetLoadState_returnsSuccessIfLoadSuccess() throws InterruptedException {
         final TestConfigBuilder.WaitingDataLoader
                 metadataLoader = new TestConfigBuilder.WaitingDataLoader(true /*success*/);
@@ -768,7 +690,6 @@
     }
 
     @Test
-    @SdkSuppress(minSdkVersion = 19)
     public void testGetLoadState_returnsFailIfLoadFail() throws InterruptedException {
         final TestConfigBuilder.WaitingDataLoader
                 metadataLoader = new TestConfigBuilder.WaitingDataLoader(false/*fail*/);
@@ -785,7 +706,6 @@
     }
 
     @Test
-    @SdkSuppress(minSdkVersion = 19)
     public void testUpdateEditorInfoAttrs_doesNotSetKeyIfNotInitialized() {
         final EditorInfo editorInfo = new EditorInfo();
         editorInfo.extras = new Bundle();
@@ -805,7 +725,6 @@
     }
 
     @Test(expected = IllegalStateException.class)
-    @SdkSuppress(minSdkVersion = 19)
     public void testLoad_throwsException_whenLoadStrategyDefault() {
         final EmojiCompat.MetadataRepoLoader loader = mock(EmojiCompat.MetadataRepoLoader.class);
         final EmojiCompat.Config config = new TestConfigBuilder.TestConfig(loader);
@@ -815,24 +734,6 @@
     }
 
     @Test
-    @SdkSuppress(maxSdkVersion = 18)
-    public void testLoad_pre19() {
-        final EmojiCompat.MetadataRepoLoader loader =
-                Mockito.spy(new TestConfigBuilder.TestEmojiDataLoader());
-        final EmojiCompat.Config config = new TestConfigBuilder.TestConfig(loader)
-                .setMetadataLoadStrategy(EmojiCompat.LOAD_STRATEGY_MANUAL);
-
-        EmojiCompat.reset(config);
-
-        verify(loader, never()).load(any(EmojiCompat.MetadataRepoLoaderCallback.class));
-        assertEquals(EmojiCompat.LOAD_STATE_DEFAULT, EmojiCompat.get().getLoadState());
-
-        EmojiCompat.get().load();
-        assertEquals(EmojiCompat.LOAD_STATE_SUCCEEDED, EmojiCompat.get().getLoadState());
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = 19)
     public void testLoad_startsLoading() {
         final EmojiCompat.MetadataRepoLoader loader =
                 Mockito.spy(new TestConfigBuilder.TestEmojiDataLoader());
@@ -850,7 +751,6 @@
     }
 
     @Test
-    @SdkSuppress(minSdkVersion = 19)
     public void testLoad_onceSuccessDoesNotStartLoading() {
         final EmojiCompat.MetadataRepoLoader loader =
                 Mockito.spy(new TestConfigBuilder.TestEmojiDataLoader());
@@ -870,7 +770,6 @@
     }
 
     @Test
-    @SdkSuppress(minSdkVersion = 19)
     public void testLoad_onceLoadingDoesNotStartLoading() throws InterruptedException {
         final TestConfigBuilder.WaitingDataLoader loader = Mockito.spy(
                 new TestConfigBuilder.WaitingDataLoader(true /*success*/));
@@ -897,14 +796,6 @@
     }
 
     @Test
-    @SdkSuppress(maxSdkVersion = 18)
-    public void testGetAssetSignature() {
-        final String signature = EmojiCompat.get().getAssetSignature();
-        assertTrue(signature.isEmpty());
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = 19)
     public void testGetAssetSignature_api19() {
         final String signature = EmojiCompat.get().getAssetSignature();
         assertNotNull(signature);
@@ -912,7 +803,6 @@
     }
 
     @Test
-    @SdkSuppress(minSdkVersion = 19)
     public void testUpdateEditorInfoAttrs_setsKeysIfInitialized() {
         final EditorInfo editorInfo = new EditorInfo();
         editorInfo.extras = new Bundle();
@@ -935,7 +825,6 @@
     }
 
     @Test
-    @SdkSuppress(minSdkVersion = 19)
     public void testUpdateEditorInfoAttrs_makesBundleIfNull() {
         final EditorInfo editorInfo = new EditorInfo();
         editorInfo.extras = null;
@@ -950,40 +839,6 @@
     }
 
     @Test
-    @SdkSuppress(maxSdkVersion = 18)
-    public void testHandleDeleteSurroundingText_pre19() {
-        final TestString testString = new TestString(Emoji.EMOJI_SINGLE_CODEPOINT);
-        final InputConnection inputConnection = mock(InputConnection.class);
-        final Editable editable = spy(new SpannableStringBuilder(testString.toString()));
-
-        Selection.setSelection(editable, testString.emojiEndIndex());
-
-        reset(editable);
-        reset(inputConnection);
-        verifyNoMoreInteractions(editable);
-        verifyNoMoreInteractions(inputConnection);
-
-        // try backwards delete 1 character
-        assertFalse(EmojiCompat.handleDeleteSurroundingText(inputConnection, editable,
-                1 /*beforeLength*/, 0 /*afterLength*/, false /*inCodePoints*/));
-    }
-
-    @Test
-    @SdkSuppress(maxSdkVersion = 18)
-    public void testOnKeyDown_pre19() {
-        final TestString testString = new TestString(Emoji.EMOJI_SINGLE_CODEPOINT);
-        final Editable editable = spy(new SpannableStringBuilder(testString.toString()));
-        Selection.setSelection(editable, testString.emojiEndIndex());
-        final KeyEvent event = KeyboardUtil.del();
-
-        reset(editable);
-        verifyNoMoreInteractions(editable);
-
-        assertFalse(EmojiCompat.handleOnKeyDown(editable, event.getKeyCode(), event));
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = 19)
     public void testUseEmojiAsDefaultStyle_whenEmojiInTheMiddle() {
         final EmojiCompat.Config config = TestConfigBuilder.config().setReplaceAll(true);
         EmojiCompat.reset(config);
@@ -997,7 +852,6 @@
     }
 
     @Test
-    @SdkSuppress(minSdkVersion = 19)
     public void testUseEmojiAsDefaultStyle_whenEmojiAtTheEnd() {
         final EmojiCompat.Config config = TestConfigBuilder.config().setReplaceAll(true);
         EmojiCompat.reset(config);
@@ -1011,7 +865,6 @@
     }
 
     @Test
-    @SdkSuppress(minSdkVersion = 19)
     public void testUseEmojiAsDefaultStyle_noEmojisAdded_whenMarkedAsException() {
         final String s = new TestString(Emoji.CHAR_DEFAULT_TEXT_STYLE).toString();
         final List<Integer> exceptions =
@@ -1024,7 +877,6 @@
     }
 
     @Test
-    @SdkSuppress(minSdkVersion = 19)
     public void testUseEmojiAsDefaultStyle_emojisAdded_whenNotMarkedAsException() {
         final String s = new TestString(Emoji.CHAR_DEFAULT_TEXT_STYLE).toString();
         final List<Integer> exceptions =
diff --git a/emoji2/emoji2-bundled/src/androidTest/java/androidx/emoji2/bundled/EmojiKeyboardTest.java b/emoji2/emoji2-bundled/src/androidTest/java/androidx/emoji2/bundled/EmojiKeyboardTest.java
index 51cb8b1..8e2ab48 100644
--- a/emoji2/emoji2-bundled/src/androidTest/java/androidx/emoji2/bundled/EmojiKeyboardTest.java
+++ b/emoji2/emoji2-bundled/src/androidTest/java/androidx/emoji2/bundled/EmojiKeyboardTest.java
@@ -30,7 +30,6 @@
 import androidx.emoji2.text.EmojiCompat;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.LargeTest;
-import androidx.test.filters.SdkSuppress;
 import androidx.test.filters.Suppress;
 import androidx.test.platform.app.InstrumentationRegistry;
 import androidx.testutils.PollingCheck;
@@ -64,7 +63,6 @@
     }
 
     @Test
-    @SdkSuppress(minSdkVersion = 19)
     public void testAppendWithSoftKeyboard() throws Exception {
         TestActivity activity = mActivityRule.getActivity();
         final EditText editText = (EditText) activity.findViewById(R.id.editText);
@@ -82,7 +80,6 @@
     }
 
     @Test
-    @SdkSuppress(minSdkVersion = 19)
     public void testBackDeleteWithSoftKeyboard() throws Exception {
         TestActivity activity = mActivityRule.getActivity();
         final EditText editText = (EditText) activity.findViewById(R.id.editText);
@@ -105,7 +102,6 @@
     }
 
     @Test
-    @SdkSuppress(minSdkVersion = 19)
     public void testForwardDeleteWithSoftKeyboard() throws Exception {
         TestActivity activity = mActivityRule.getActivity();
         final EditText editText = (EditText) activity.findViewById(R.id.editText);
@@ -129,7 +125,6 @@
     }
 
     @Test
-    @SdkSuppress(minSdkVersion = 19)
     public void testBackDeleteWithHardwareKeyboard() throws Exception {
         TestActivity activity = mActivityRule.getActivity();
         final EditText editText = (EditText) activity.findViewById(R.id.editText);
@@ -159,7 +154,6 @@
     }
 
     @Test
-    @SdkSuppress(minSdkVersion = 19)
     public void testForwardDeleteWithHardwareKeyboard() throws Exception {
         TestActivity activity = mActivityRule.getActivity();
         final EditText editText = (EditText) activity.findViewById(R.id.editText);
diff --git a/emoji2/emoji2-bundled/src/androidTest/java/androidx/emoji2/bundled/EmojiSpanInstrumentationTest.java b/emoji2/emoji2-bundled/src/androidTest/java/androidx/emoji2/bundled/EmojiSpanInstrumentationTest.java
index 6c9eb50..f7aeb3a 100644
--- a/emoji2/emoji2-bundled/src/androidTest/java/androidx/emoji2/bundled/EmojiSpanInstrumentationTest.java
+++ b/emoji2/emoji2-bundled/src/androidTest/java/androidx/emoji2/bundled/EmojiSpanInstrumentationTest.java
@@ -35,7 +35,6 @@
 import androidx.emoji2.text.EmojiSpan;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.LargeTest;
-import androidx.test.filters.SdkSuppress;
 import androidx.test.platform.app.InstrumentationRegistry;
 
 import org.junit.Before;
@@ -46,7 +45,6 @@
 
 @LargeTest
 @RunWith(AndroidJUnit4.class)
-@SdkSuppress(minSdkVersion = 19)
 public class EmojiSpanInstrumentationTest {
 
     @SuppressWarnings("deprecation")
diff --git a/emoji2/emoji2-bundled/src/androidTest/java/androidx/emoji2/bundled/FontRequestEmojiCompatConfigTest.java b/emoji2/emoji2-bundled/src/androidTest/java/androidx/emoji2/bundled/FontRequestEmojiCompatConfigTest.java
index 0366734..253bf64 100644
--- a/emoji2/emoji2-bundled/src/androidTest/java/androidx/emoji2/bundled/FontRequestEmojiCompatConfigTest.java
+++ b/emoji2/emoji2-bundled/src/androidTest/java/androidx/emoji2/bundled/FontRequestEmojiCompatConfigTest.java
@@ -58,7 +58,6 @@
 import androidx.test.core.app.ApplicationProvider;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.LargeTest;
-import androidx.test.filters.SdkSuppress;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -104,7 +103,6 @@
     }
 
     @Test
-    @SdkSuppress(minSdkVersion = 19)
     public void testLoad_whenGetFontThrowsException() throws NameNotFoundException {
         final Exception exception = new RuntimeException();
         doThrow(exception).when(mFontProviderHelper).fetchFonts(
@@ -119,7 +117,6 @@
     }
 
     @Test
-    @SdkSuppress(minSdkVersion = 19)
     public void testLoad_providerNotFound() throws NameNotFoundException {
         doThrow(new NameNotFoundException()).when(mFontProviderHelper).fetchFonts(
                 any(Context.class), any(FontRequest.class));
@@ -137,14 +134,12 @@
     }
 
     @Test
-    @SdkSuppress(minSdkVersion = 19)
     public void testLoad_wrongCertificate() throws NameNotFoundException {
         verifyLoaderOnFailedCalled(STATUS_WRONG_CERTIFICATES, null /* fonts */,
                 "fetchFonts failed (" + STATUS_WRONG_CERTIFICATES + ")");
     }
 
     @Test
-    @SdkSuppress(minSdkVersion = 19)
     public void testLoad_fontNotFound() throws NameNotFoundException {
         verifyLoaderOnFailedCalled(STATUS_OK,
                 getTestFontInfoWithInvalidPath(RESULT_CODE_FONT_NOT_FOUND),
@@ -152,7 +147,6 @@
     }
 
     @Test
-    @SdkSuppress(minSdkVersion = 19)
     public void testLoad_fontUnavailable() throws NameNotFoundException {
         verifyLoaderOnFailedCalled(STATUS_OK,
                 getTestFontInfoWithInvalidPath(RESULT_CODE_FONT_UNAVAILABLE),
@@ -160,7 +154,6 @@
     }
 
     @Test
-    @SdkSuppress(minSdkVersion = 19)
     public void testLoad_malformedQuery() throws NameNotFoundException {
         verifyLoaderOnFailedCalled(STATUS_OK,
                 getTestFontInfoWithInvalidPath(RESULT_CODE_MALFORMED_QUERY),
@@ -168,21 +161,18 @@
     }
 
     @Test
-    @SdkSuppress(minSdkVersion = 19)
     public void testLoad_resultNotFound() throws NameNotFoundException {
         verifyLoaderOnFailedCalled(STATUS_OK, new FontInfo[] {},
                 "fetchFonts failed (empty result)");
     }
 
     @Test
-    @SdkSuppress(minSdkVersion = 19)
     public void testLoad_nullFontInfo() throws NameNotFoundException {
         verifyLoaderOnFailedCalled(STATUS_OK, null /* fonts */,
                 "fetchFonts failed (empty result)");
     }
 
     @Test
-    @SdkSuppress(minSdkVersion = 19)
     public void testLoad_cannotLoadTypeface() throws NameNotFoundException {
         // getTestFontInfoWithInvalidPath returns FontInfo with invalid path to file.
         verifyLoaderOnFailedCalled(STATUS_OK,
@@ -191,7 +181,6 @@
     }
 
     @Test
-    @SdkSuppress(minSdkVersion = 19)
     public void testLoad_success() throws IOException, NameNotFoundException {
         final File file = loadFont(mContext, "NotoColorEmojiCompat.ttf");
         final FontInfo[] fonts =  new FontInfo[] {
@@ -211,7 +200,6 @@
     }
 
     @Test
-    @SdkSuppress(minSdkVersion = 19)
     public void testLoad_retryPolicy() throws IOException, NameNotFoundException {
         final File file = loadFont(mContext, "NotoColorEmojiCompat.ttf");
         final FontInfo[] fonts =  new FontInfo[] {
@@ -234,7 +222,6 @@
     }
 
     @Test
-    @SdkSuppress(minSdkVersion = 19)
     public void testLoad_keepRetryingAndGiveUp() throws IOException, NameNotFoundException {
         final File file = loadFont(mContext, "NotoColorEmojiCompat.ttf");
         final FontInfo[] fonts =  new FontInfo[] {
@@ -261,7 +248,6 @@
     }
 
     @Test
-    @SdkSuppress(minSdkVersion = 19)
     public void testLoad_keepRetryingAndFail() throws IOException, NameNotFoundException {
         final File file = loadFont(mContext, "NotoColorEmojiCompat.ttf");
         final Uri uri = Uri.fromFile(file);
@@ -315,7 +301,6 @@
     }
 
     @Test
-    @SdkSuppress(minSdkVersion = 19)
     public void testLoad_keepRetryingAndSuccess() throws IOException, NameNotFoundException {
         final File file = loadFont(mContext, "NotoColorEmojiCompat.ttf");
         final Uri uri = Uri.fromFile(file);
@@ -370,7 +355,6 @@
     }
 
     @Test
-    @SdkSuppress(minSdkVersion = 19)
     public void testLoad_ObserverNotifyAndSuccess() throws IOException, NameNotFoundException {
         final File file = loadFont(mContext, "NotoColorEmojiCompat.ttf");
         final Uri uri = Uri.fromFile(file);
@@ -425,7 +409,6 @@
     }
 
     @Test
-    @SdkSuppress(minSdkVersion = 19)
     public void testLoad_ObserverNotifyAndFail() throws IOException, NameNotFoundException {
         final File file = loadFont(mContext, "NotoColorEmojiCompat.ttf");
         final Uri uri = Uri.fromFile(file);
diff --git a/emoji2/emoji2-bundled/src/androidTest/java/androidx/emoji2/bundled/HardDeleteTest.java b/emoji2/emoji2-bundled/src/androidTest/java/androidx/emoji2/bundled/HardDeleteTest.java
index 5ef6199..6129e29 100644
--- a/emoji2/emoji2-bundled/src/androidTest/java/androidx/emoji2/bundled/HardDeleteTest.java
+++ b/emoji2/emoji2-bundled/src/androidTest/java/androidx/emoji2/bundled/HardDeleteTest.java
@@ -30,7 +30,6 @@
 import androidx.emoji2.bundled.util.TestString;
 import androidx.emoji2.text.EmojiCompat;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SdkSuppress;
 import androidx.test.filters.SmallTest;
 
 import org.hamcrest.MatcherAssert;
@@ -42,7 +41,6 @@
 
 @SmallTest
 @RunWith(AndroidJUnit4.class)
-@SdkSuppress(minSdkVersion = 19)
 public class HardDeleteTest {
 
     private TestString mTestString;
diff --git a/emoji2/emoji2-bundled/src/androidTest/java/androidx/emoji2/bundled/InitCallbackTest.java b/emoji2/emoji2-bundled/src/androidTest/java/androidx/emoji2/bundled/InitCallbackTest.java
index 76755aa..e3ccdd4 100644
--- a/emoji2/emoji2-bundled/src/androidTest/java/androidx/emoji2/bundled/InitCallbackTest.java
+++ b/emoji2/emoji2-bundled/src/androidTest/java/androidx/emoji2/bundled/InitCallbackTest.java
@@ -27,7 +27,6 @@
 import androidx.emoji2.text.EmojiCompat;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.MediumTest;
-import androidx.test.filters.SdkSuppress;
 import androidx.test.platform.app.InstrumentationRegistry;
 
 import org.junit.Test;
@@ -56,7 +55,6 @@
     }
 
     @Test
-    @SdkSuppress(minSdkVersion = 19)
     public void testRegisterInitCallback_callsFailCallback() {
         final EmojiCompat.InitCallback initCallback1 = mock(EmojiCompat.InitCallback.class);
         final EmojiCompat.InitCallback initCallback2 = mock(EmojiCompat.InitCallback.class);
@@ -76,7 +74,6 @@
     }
 
     @Test
-    @SdkSuppress(minSdkVersion = 19)
     public void testRegisterInitCallback_callsFailCallback_whenOnFailCalledByLoader() {
         final EmojiCompat.InitCallback initCallback = mock(EmojiCompat.InitCallback.class);
         final EmojiCompat.MetadataRepoLoader loader = new EmojiCompat.MetadataRepoLoader() {
@@ -95,7 +92,6 @@
     }
 
     @Test
-    @SdkSuppress(minSdkVersion = 19)
     public void testRegisterInitCallback_callsFailCallback_whenMetadataRepoIsNull() {
         final EmojiCompat.InitCallback initCallback = mock(EmojiCompat.InitCallback.class);
         final EmojiCompat.MetadataRepoLoader loader = new EmojiCompat.MetadataRepoLoader() {
@@ -114,7 +110,6 @@
     }
 
     @Test
-    @SdkSuppress(minSdkVersion = 19)
     public void testUnregisterInitCallback_doesNotInteractWithCallback()
             throws InterruptedException {
         // will be registered
diff --git a/emoji2/emoji2-bundled/src/androidTest/java/androidx/emoji2/bundled/SoftDeleteTest.java b/emoji2/emoji2-bundled/src/androidTest/java/androidx/emoji2/bundled/SoftDeleteTest.java
index a945feb..eed9de6 100644
--- a/emoji2/emoji2-bundled/src/androidTest/java/androidx/emoji2/bundled/SoftDeleteTest.java
+++ b/emoji2/emoji2-bundled/src/androidTest/java/androidx/emoji2/bundled/SoftDeleteTest.java
@@ -31,7 +31,6 @@
 import androidx.emoji2.bundled.util.TestString;
 import androidx.emoji2.text.EmojiCompat;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SdkSuppress;
 import androidx.test.filters.SmallTest;
 
 import org.hamcrest.MatcherAssert;
@@ -43,7 +42,6 @@
 
 @SmallTest
 @RunWith(AndroidJUnit4.class)
-@SdkSuppress(minSdkVersion = 19)
 public class SoftDeleteTest {
     private InputConnection mInputConnection;
     private TestString mTestString;
diff --git a/emoji2/emoji2-bundled/src/androidTest/java/androidx/emoji2/bundled/viewstests/EmojiEditTextParameters.java b/emoji2/emoji2-bundled/src/androidTest/java/androidx/emoji2/bundled/viewstests/EmojiEditTextParameters.java
index f1f76b9..877f710 100644
--- a/emoji2/emoji2-bundled/src/androidTest/java/androidx/emoji2/bundled/viewstests/EmojiEditTextParameters.java
+++ b/emoji2/emoji2-bundled/src/androidTest/java/androidx/emoji2/bundled/viewstests/EmojiEditTextParameters.java
@@ -30,7 +30,6 @@
 import androidx.emoji2.widget.EmojiEditText;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.MediumTest;
-import androidx.test.filters.SdkSuppress;
 import androidx.test.platform.app.InstrumentationRegistry;
 
 import org.junit.Before;
@@ -60,7 +59,6 @@
     }
 
     @Test
-    @SdkSuppress(minSdkVersion = 19)
     public void testSetMaxCount() {
         final TestActivity activity = mActivityRule.getActivity();
         final EmojiEditText editText = activity.findViewById(R.id.emojiEditTextWithMaxCount);
diff --git a/emoji2/emoji2-bundled/src/androidTest/java/androidx/emoji2/bundled/viewstests/EmojiEditTextProcessesText.java b/emoji2/emoji2-bundled/src/androidTest/java/androidx/emoji2/bundled/viewstests/EmojiEditTextProcessesText.java
index 84ec876..3427106 100644
--- a/emoji2/emoji2-bundled/src/androidTest/java/androidx/emoji2/bundled/viewstests/EmojiEditTextProcessesText.java
+++ b/emoji2/emoji2-bundled/src/androidTest/java/androidx/emoji2/bundled/viewstests/EmojiEditTextProcessesText.java
@@ -30,7 +30,6 @@
 import androidx.emoji2.widget.EmojiEditText;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.MediumTest;
-import androidx.test.filters.SdkSuppress;
 import androidx.test.platform.app.InstrumentationRegistry;
 
 import org.junit.Before;
@@ -59,7 +58,6 @@
     }
 
     @Test
-    @SdkSuppress(minSdkVersion = 19)
     public void testDoesReplaceEmoji() {
         final TestActivity activity = mActivityRule.getActivity();
         final EmojiEditText editText = activity.findViewById(R.id.emojiEditText);
diff --git a/emoji2/emoji2-bundled/src/androidTest/java/androidx/emoji2/bundled/viewstests/EmojiTextViewProcessTest.java b/emoji2/emoji2-bundled/src/androidTest/java/androidx/emoji2/bundled/viewstests/EmojiTextViewProcessTest.java
index 3abc80a..fb97bf9 100644
--- a/emoji2/emoji2-bundled/src/androidTest/java/androidx/emoji2/bundled/viewstests/EmojiTextViewProcessTest.java
+++ b/emoji2/emoji2-bundled/src/androidTest/java/androidx/emoji2/bundled/viewstests/EmojiTextViewProcessTest.java
@@ -65,7 +65,6 @@
     }
 
     @Test
-    @SdkSuppress(minSdkVersion = 19)
     public void whenEmojiTextView_setText_emojiIsProcessedToSpans() {
         final TestActivity activity = mActivityRule.getActivity();
         final TextView textView = activity.findViewById(R.id.emojiTextView);
@@ -81,7 +80,6 @@
     }
 
     @Test
-    @SdkSuppress(minSdkVersion = 19)
     public void precomputedText_addsReplacementSpans() {
         final TestActivity activity = mActivityRule.getActivity();
         final TextView textView = activity.findViewById(R.id.emojiTextView);
@@ -120,7 +118,7 @@
     }
 
     @Test
-    @SdkSuppress(minSdkVersion = 19, maxSdkVersion = 28)
+    @SdkSuppress(maxSdkVersion = 28)
     public void precomputedText_notModifiedWhenNoEmoji_beforePlatformPrecomputedText() {
         final TestActivity activity = mActivityRule.getActivity();
         final TextView textView = activity.findViewById(R.id.emojiTextView);
diff --git a/emoji2/emoji2-views-helper/src/androidTest/java/androidx/emoji2/viewsintegration/EmojiEditTextHelperDisabledTest.java b/emoji2/emoji2-views-helper/src/androidTest/java/androidx/emoji2/viewsintegration/EmojiEditTextHelperDisabledTest.java
index aed93b1..e60f0ba 100644
--- a/emoji2/emoji2-views-helper/src/androidTest/java/androidx/emoji2/viewsintegration/EmojiEditTextHelperDisabledTest.java
+++ b/emoji2/emoji2-views-helper/src/androidTest/java/androidx/emoji2/viewsintegration/EmojiEditTextHelperDisabledTest.java
@@ -34,7 +34,6 @@
 import androidx.test.core.app.ApplicationProvider;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.LargeTest;
-import androidx.test.filters.SdkSuppress;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -43,7 +42,6 @@
 
 @LargeTest
 @RunWith(AndroidJUnit4.class)
-@SdkSuppress(minSdkVersion = 19)
 public class EmojiEditTextHelperDisabledTest {
     EmojiEditTextHelper mEmojiEditTextHelper;
     EditText mEditText;
diff --git a/emoji2/emoji2-views-helper/src/androidTest/java/androidx/emoji2/viewsintegration/EmojiEditTextHelperPre19Test.java b/emoji2/emoji2-views-helper/src/androidTest/java/androidx/emoji2/viewsintegration/EmojiEditTextHelperPre19Test.java
deleted file mode 100644
index fbd32df..0000000
--- a/emoji2/emoji2-views-helper/src/androidTest/java/androidx/emoji2/viewsintegration/EmojiEditTextHelperPre19Test.java
+++ /dev/null
@@ -1,99 +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.emoji2.viewsintegration;
-
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertSame;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyNoMoreInteractions;
-
-import android.text.TextWatcher;
-import android.text.method.KeyListener;
-import android.view.inputmethod.EditorInfo;
-import android.view.inputmethod.InputConnection;
-import android.widget.EditText;
-
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.LargeTest;
-import androidx.test.filters.SdkSuppress;
-import androidx.test.filters.SmallTest;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-@SdkSuppress(maxSdkVersion = 18)
-public class EmojiEditTextHelperPre19Test {
-    EmojiEditTextHelper mEmojiEditTextHelper;
-
-    @Before
-    public void setup() {
-        final EditText editText = mock(EditText.class);
-        mEmojiEditTextHelper = new EmojiEditTextHelper(editText);
-        verifyNoMoreInteractions(editText);
-    }
-
-    @Test
-    public void testGetKeyListener_returnsSameKeyListener() {
-        final KeyListener param = mock(KeyListener.class);
-        final KeyListener keyListener = mEmojiEditTextHelper.getKeyListener(
-                param);
-
-        assertSame(param, keyListener);
-    }
-
-    @LargeTest
-    @Test
-    public void testGetOnCreateInputConnection_returnsSameInputConnection() {
-        final InputConnection param = mock(InputConnection.class);
-        final InputConnection inputConnection = mEmojiEditTextHelper.onCreateInputConnection(param,
-                new EditorInfo());
-
-        assertSame(param, inputConnection);
-    }
-
-    @Test
-    public void testGetOnCreateInputConnection_withNullAttrs_returnsSameInputConnection() {
-        final InputConnection param = mock(InputConnection.class);
-        final InputConnection inputConnection = mEmojiEditTextHelper.onCreateInputConnection(param,
-                null);
-
-        assertSame(param, inputConnection);
-    }
-
-    @Test
-    public void testGetOnCreateInputConnection_withNullInputConnection_returnsNull() {
-        final InputConnection inputConnection = mEmojiEditTextHelper.onCreateInputConnection(null,
-                new EditorInfo());
-        assertNull(inputConnection);
-    }
-
-    @Test
-    public void testDoesNotAttachTextWatcher() {
-        final EditText editText = mock(EditText.class);
-
-        mEmojiEditTextHelper = new EmojiEditTextHelper(editText);
-
-        verify(editText, times(0)).addTextChangedListener(any(TextWatcher.class));
-    }
-
-}
diff --git a/emoji2/emoji2-views-helper/src/androidTest/java/androidx/emoji2/viewsintegration/EmojiEditTextHelperTest.java b/emoji2/emoji2-views-helper/src/androidTest/java/androidx/emoji2/viewsintegration/EmojiEditTextHelperTest.java
index 50399d5..ea865fb 100644
--- a/emoji2/emoji2-views-helper/src/androidTest/java/androidx/emoji2/viewsintegration/EmojiEditTextHelperTest.java
+++ b/emoji2/emoji2-views-helper/src/androidTest/java/androidx/emoji2/viewsintegration/EmojiEditTextHelperTest.java
@@ -39,7 +39,6 @@
 import androidx.test.core.app.ApplicationProvider;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.LargeTest;
-import androidx.test.filters.SdkSuppress;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -48,7 +47,6 @@
 
 @LargeTest
 @RunWith(AndroidJUnit4.class)
-@SdkSuppress(minSdkVersion = 19)
 public class EmojiEditTextHelperTest {
     EmojiEditTextHelper mEmojiEditTextHelper;
     EditText mEditText;
diff --git a/emoji2/emoji2-views-helper/src/androidTest/java/androidx/emoji2/viewsintegration/EmojiInputConnectionTest.java b/emoji2/emoji2-views-helper/src/androidTest/java/androidx/emoji2/viewsintegration/EmojiInputConnectionTest.java
index 42b6263..a322fd0 100644
--- a/emoji2/emoji2-views-helper/src/androidTest/java/androidx/emoji2/viewsintegration/EmojiInputConnectionTest.java
+++ b/emoji2/emoji2-views-helper/src/androidTest/java/androidx/emoji2/viewsintegration/EmojiInputConnectionTest.java
@@ -38,7 +38,6 @@
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.LargeTest;
-import androidx.test.filters.SdkSuppress;
 import androidx.test.platform.app.InstrumentationRegistry;
 
 import org.junit.Before;
@@ -47,7 +46,6 @@
 
 @LargeTest
 @RunWith(AndroidJUnit4.class)
-@SdkSuppress(minSdkVersion = 19)
 public class EmojiInputConnectionTest {
 
     private InputConnection mInputConnection;
diff --git a/emoji2/emoji2-views-helper/src/androidTest/java/androidx/emoji2/viewsintegration/EmojiInputFilterTest.java b/emoji2/emoji2-views-helper/src/androidTest/java/androidx/emoji2/viewsintegration/EmojiInputFilterTest.java
index a21adf7..197e878 100644
--- a/emoji2/emoji2-views-helper/src/androidTest/java/androidx/emoji2/viewsintegration/EmojiInputFilterTest.java
+++ b/emoji2/emoji2-views-helper/src/androidTest/java/androidx/emoji2/viewsintegration/EmojiInputFilterTest.java
@@ -53,7 +53,6 @@
 
 @LargeTest
 @RunWith(AndroidJUnit4.class)
-@SdkSuppress(minSdkVersion = 19)
 public class EmojiInputFilterTest {
 
     private EmojiInputFilter mInputFilter;
@@ -240,7 +239,6 @@
     }
 
     @Test
-    @SdkSuppress(minSdkVersion = 19)
     public void initCallback_doesntCrashWhenNotAttached() {
         Context context = InstrumentationRegistry.getInstrumentation().getContext();
         EditText editText = new EditText(context);
diff --git a/emoji2/emoji2-views-helper/src/androidTest/java/androidx/emoji2/viewsintegration/EmojiKeyListenerTest.java b/emoji2/emoji2-views-helper/src/androidTest/java/androidx/emoji2/viewsintegration/EmojiKeyListenerTest.java
index f73e1a2..0e8feb4 100644
--- a/emoji2/emoji2-views-helper/src/androidTest/java/androidx/emoji2/viewsintegration/EmojiKeyListenerTest.java
+++ b/emoji2/emoji2-views-helper/src/androidTest/java/androidx/emoji2/viewsintegration/EmojiKeyListenerTest.java
@@ -34,7 +34,6 @@
 import android.view.View;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SdkSuppress;
 import androidx.test.filters.SmallTest;
 
 import org.junit.Before;
@@ -43,7 +42,6 @@
 
 @SmallTest
 @RunWith(AndroidJUnit4.class)
-@SdkSuppress(minSdkVersion = 19)
 public class EmojiKeyListenerTest {
 
     private KeyListener mKeyListener;
diff --git a/emoji2/emoji2-views-helper/src/androidTest/java/androidx/emoji2/viewsintegration/EmojiTextViewHelperPre19Test.java b/emoji2/emoji2-views-helper/src/androidTest/java/androidx/emoji2/viewsintegration/EmojiTextViewHelperPre19Test.java
deleted file mode 100644
index b54be67..0000000
--- a/emoji2/emoji2-views-helper/src/androidTest/java/androidx/emoji2/viewsintegration/EmojiTextViewHelperPre19Test.java
+++ /dev/null
@@ -1,87 +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.emoji2.viewsintegration;
-
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertSame;
-import static org.mockito.Mockito.mock;
-
-import android.text.InputFilter;
-import android.text.method.TransformationMethod;
-import android.widget.TextView;
-
-import androidx.test.core.app.ApplicationProvider;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SdkSuppress;
-import androidx.test.filters.SmallTest;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-@SdkSuppress(maxSdkVersion = 18)
-public class EmojiTextViewHelperPre19Test {
-    EmojiTextViewHelper mTextViewHelper;
-    TextView mTextView;
-
-    @Before
-    public void setup() {
-        mTextView = new TextView(ApplicationProvider.getApplicationContext());
-        mTextViewHelper = new EmojiTextViewHelper(mTextView);
-    }
-
-    @Test
-    public void testUpdateTransformationMethod_doesNotUpdateTransformationMethod() {
-        final TransformationMethod tm = mock(TransformationMethod.class);
-        mTextView.setTransformationMethod(tm);
-
-        mTextViewHelper.updateTransformationMethod();
-
-        assertSame(tm, mTextView.getTransformationMethod());
-    }
-
-    @Test
-    public void testGetFilters_returnsSameFilters() {
-        final InputFilter existingFilter = mock(InputFilter.class);
-        final InputFilter[] filters = new InputFilter[]{existingFilter};
-
-        final InputFilter[] newFilters = mTextViewHelper.getFilters(filters);
-
-        assertSame(filters, newFilters);
-    }
-
-    @Test
-    public void testGetTransformationMethod_returnSameTransformationMethod() {
-        assertNull(mTextViewHelper.wrapTransformationMethod(null));
-
-        final TransformationMethod tm = mock(TransformationMethod.class);
-        assertSame(tm, mTextViewHelper.wrapTransformationMethod(tm));
-    }
-
-    @Test
-    public void testSetAllCaps_doesNotUpdateTransformationMethod() {
-        final TransformationMethod tm = mock(TransformationMethod.class);
-        mTextView.setTransformationMethod(tm);
-        mTextViewHelper.setAllCaps(true);
-        assertSame(tm, mTextView.getTransformationMethod());
-
-        mTextViewHelper.setAllCaps(false);
-        assertSame(tm, mTextView.getTransformationMethod());
-    }
-}
diff --git a/emoji2/emoji2-views-helper/src/androidTest/java/androidx/emoji2/viewsintegration/EmojiTextViewHelperTest.java b/emoji2/emoji2-views-helper/src/androidTest/java/androidx/emoji2/viewsintegration/EmojiTextViewHelperTest.java
index bb51061..a39ee45 100644
--- a/emoji2/emoji2-views-helper/src/androidTest/java/androidx/emoji2/viewsintegration/EmojiTextViewHelperTest.java
+++ b/emoji2/emoji2-views-helper/src/androidTest/java/androidx/emoji2/viewsintegration/EmojiTextViewHelperTest.java
@@ -35,7 +35,6 @@
 import androidx.emoji2.text.EmojiCompat;
 import androidx.test.core.app.ApplicationProvider;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SdkSuppress;
 import androidx.test.filters.SmallTest;
 
 import org.hamcrest.CoreMatchers;
@@ -47,7 +46,6 @@
 
 @SmallTest
 @RunWith(AndroidJUnit4.class)
-@SdkSuppress(minSdkVersion = 19)
 public class EmojiTextViewHelperTest {
     EmojiTextViewHelper mTextViewHelper;
     TextView mTextView;
diff --git a/emoji2/emoji2-views-helper/src/androidTest/java/androidx/emoji2/viewsintegration/EmojiTextWatcherDisabledTest.java b/emoji2/emoji2-views-helper/src/androidTest/java/androidx/emoji2/viewsintegration/EmojiTextWatcherDisabledTest.java
index 09a12ad..fec6db2 100644
--- a/emoji2/emoji2-views-helper/src/androidTest/java/androidx/emoji2/viewsintegration/EmojiTextWatcherDisabledTest.java
+++ b/emoji2/emoji2-views-helper/src/androidTest/java/androidx/emoji2/viewsintegration/EmojiTextWatcherDisabledTest.java
@@ -34,7 +34,6 @@
 import androidx.emoji2.text.EmojiCompat;
 import androidx.emoji2.util.EmojiMatcher;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SdkSuppress;
 import androidx.test.filters.SmallTest;
 
 import org.junit.Before;
@@ -43,7 +42,6 @@
 
 @SmallTest
 @RunWith(AndroidJUnit4.class)
-@SdkSuppress(minSdkVersion = 19) // class is not instantiated prior to API19
 public class EmojiTextWatcherDisabledTest {
 
     private EmojiTextWatcher mTextWatcher;
diff --git a/emoji2/emoji2-views-helper/src/androidTest/java/androidx/emoji2/viewsintegration/EmojiTextWatcherTest.java b/emoji2/emoji2-views-helper/src/androidTest/java/androidx/emoji2/viewsintegration/EmojiTextWatcherTest.java
index 5034e2b..31632b2 100644
--- a/emoji2/emoji2-views-helper/src/androidTest/java/androidx/emoji2/viewsintegration/EmojiTextWatcherTest.java
+++ b/emoji2/emoji2-views-helper/src/androidTest/java/androidx/emoji2/viewsintegration/EmojiTextWatcherTest.java
@@ -49,7 +49,6 @@
 
 @SmallTest
 @RunWith(AndroidJUnit4.class)
-@SdkSuppress(minSdkVersion = 19) // class is not instantiated prior to API19
 public class EmojiTextWatcherTest {
 
     private EmojiTextWatcher mTextWatcher;
diff --git a/emoji2/emoji2-views/src/androidTest/java/androidx/emoji2/widget/EmojiExtractTextLayoutTest.java b/emoji2/emoji2-views/src/androidTest/java/androidx/emoji2/widget/EmojiExtractTextLayoutTest.java
index 9ea2da4..c7995bf 100644
--- a/emoji2/emoji2-views/src/androidTest/java/androidx/emoji2/widget/EmojiExtractTextLayoutTest.java
+++ b/emoji2/emoji2-views/src/androidTest/java/androidx/emoji2/widget/EmojiExtractTextLayoutTest.java
@@ -43,7 +43,6 @@
 import androidx.test.annotation.UiThreadTest;
 import androidx.test.core.app.ApplicationProvider;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SdkSuppress;
 import androidx.test.filters.SmallTest;
 
 import org.junit.Before;
@@ -125,7 +124,6 @@
 
     @Test
     @UiThreadTest
-    @SdkSuppress(minSdkVersion = 19)
     public void testSetEmojiReplaceStrategyCallEmojiCompatWithCorrectStrategy() {
         final Context context = ApplicationProvider.getApplicationContext();
 
diff --git a/emoji2/emoji2/src/androidTest/java/androidx/emoji2/text/DefaultEmojiCompatConfigTest.java b/emoji2/emoji2/src/androidTest/java/androidx/emoji2/text/DefaultEmojiCompatConfigTest.java
index 8d936cb..eda1b9d 100644
--- a/emoji2/emoji2/src/androidTest/java/androidx/emoji2/text/DefaultEmojiCompatConfigTest.java
+++ b/emoji2/emoji2/src/androidTest/java/androidx/emoji2/text/DefaultEmojiCompatConfigTest.java
@@ -44,7 +44,6 @@
 import androidx.test.core.app.ApplicationProvider;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.MediumTest;
-import androidx.test.filters.SdkSuppress;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -89,7 +88,6 @@
     }
 
     @Test
-    @SdkSuppress(minSdkVersion = 19)
     public void whenNoLookup_returnsNull() throws PackageManager.NameNotFoundException {
         Context mockContext = mock(Context.class);
         when(mockContext.getPackageManager()).thenReturn(mock(PackageManager.class));
@@ -102,7 +100,6 @@
     }
 
     @Test
-    @SdkSuppress(minSdkVersion = 19)
     public void whenProviderFound_returnsConfig() throws PackageManager.NameNotFoundException {
         ResolveInfo info = generateResolveInfo(
                 "some package", "some authority", ApplicationInfo.FLAG_SYSTEM
@@ -123,7 +120,6 @@
     }
 
     @Test
-    @SdkSuppress(minSdkVersion = 19)
     public void whenProviderFound_butNotSystemInstalled_returnsNull()
             throws PackageManager.NameNotFoundException {
         ResolveInfo info = generateResolveInfo(
@@ -143,7 +139,6 @@
     }
 
     @Test
-    @SdkSuppress(minSdkVersion = 19)
     public void whenQueryingIntent_usesRightIntent() throws PackageManager.NameNotFoundException {
         ResolveInfo info = generateResolveInfo(
                 "some package", "some authority", 0
@@ -167,7 +162,6 @@
     }
 
     @Test
-    @SdkSuppress(minSdkVersion = 19)
     public void whenProviderFound_configMapsCorrectly()
             throws PackageManager.NameNotFoundException {
         String packageName = "queried package name";
@@ -193,7 +187,6 @@
     }
 
     @Test
-    @SdkSuppress(minSdkVersion = 19)
     public void whenProviderFound_returnsDifferentConfig_everyCallToGet()
             throws PackageManager.NameNotFoundException {
         ResolveInfo info = generateResolveInfo(
diff --git a/emoji2/emoji2/src/androidTest/java/androidx/emoji2/text/EmojiProcessorTest.java b/emoji2/emoji2/src/androidTest/java/androidx/emoji2/text/EmojiProcessorTest.java
index 631e108..31f0f8b 100644
--- a/emoji2/emoji2/src/androidTest/java/androidx/emoji2/text/EmojiProcessorTest.java
+++ b/emoji2/emoji2/src/androidTest/java/androidx/emoji2/text/EmojiProcessorTest.java
@@ -24,7 +24,6 @@
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.LargeTest;
-import androidx.test.filters.SdkSuppress;
 
 import junit.framework.TestCase;
 
@@ -38,7 +37,6 @@
 
 @LargeTest
 @RunWith(AndroidJUnit4.class)
-@SdkSuppress(minSdkVersion = 19)
 public class EmojiProcessorTest extends TestCase {
     private EmojiProcessor mProcessor;
 
@@ -198,4 +196,4 @@
         assertEquals(expectedStart, actual.getSpanStart(spans[0]));
         assertEquals(expectedEnd, actual.getSpanEnd(spans[0]));
     }
-}
\ No newline at end of file
+}
diff --git a/emoji2/emoji2/src/androidTest/java/androidx/emoji2/text/EmojiSpanTest.java b/emoji2/emoji2/src/androidTest/java/androidx/emoji2/text/EmojiSpanTest.java
index 23a48cc..1c81967 100644
--- a/emoji2/emoji2/src/androidTest/java/androidx/emoji2/text/EmojiSpanTest.java
+++ b/emoji2/emoji2/src/androidTest/java/androidx/emoji2/text/EmojiSpanTest.java
@@ -39,7 +39,6 @@
 import androidx.annotation.NonNull;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.LargeTest;
-import androidx.test.filters.SdkSuppress;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -49,7 +48,6 @@
 
 @LargeTest
 @RunWith(AndroidJUnit4.class)
-@SdkSuppress(minSdkVersion = 19)
 public class EmojiSpanTest {
 
     @Before
diff --git a/emoji2/emoji2/src/androidTest/java/androidx/emoji2/text/MetadataRepoTest.java b/emoji2/emoji2/src/androidTest/java/androidx/emoji2/text/MetadataRepoTest.java
index b4e78ec..af249b6 100644
--- a/emoji2/emoji2/src/androidTest/java/androidx/emoji2/text/MetadataRepoTest.java
+++ b/emoji2/emoji2/src/androidTest/java/androidx/emoji2/text/MetadataRepoTest.java
@@ -22,7 +22,6 @@
 import android.graphics.Typeface;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SdkSuppress;
 import androidx.test.filters.SmallTest;
 
 import org.junit.Before;
@@ -31,7 +30,6 @@
 
 @SmallTest
 @RunWith(AndroidJUnit4.class)
-@SdkSuppress(minSdkVersion = 19)
 public class MetadataRepoTest {
 
     MetadataRepo mMetadataRepo;
diff --git a/emoji2/emoji2/src/androidTest/java/androidx/emoji2/text/TypefaceEmojiRasterizerTest.java b/emoji2/emoji2/src/androidTest/java/androidx/emoji2/text/TypefaceEmojiRasterizerTest.java
index 4aa19d8..7e64fdd 100644
--- a/emoji2/emoji2/src/androidTest/java/androidx/emoji2/text/TypefaceEmojiRasterizerTest.java
+++ b/emoji2/emoji2/src/androidTest/java/androidx/emoji2/text/TypefaceEmojiRasterizerTest.java
@@ -17,7 +17,6 @@
 package androidx.emoji2.text;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SdkSuppress;
 import androidx.test.filters.SmallTest;
 
 import junit.framework.TestCase;
@@ -27,7 +26,6 @@
 
 @SmallTest
 @RunWith(AndroidJUnit4.class)
-@SdkSuppress(minSdkVersion = 19)
 public class TypefaceEmojiRasterizerTest extends TestCase {
 
     @Test
diff --git a/emoji2/emoji2/src/androidTest/java/androidx/emoji2/text/UninitializedStateTest.java b/emoji2/emoji2/src/androidTest/java/androidx/emoji2/text/UninitializedStateTest.java
index c1730b4..fb848fa 100644
--- a/emoji2/emoji2/src/androidTest/java/androidx/emoji2/text/UninitializedStateTest.java
+++ b/emoji2/emoji2/src/androidTest/java/androidx/emoji2/text/UninitializedStateTest.java
@@ -16,7 +16,6 @@
 package androidx.emoji2.text;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SdkSuppress;
 import androidx.test.filters.SmallTest;
 
 import org.junit.Before;
@@ -25,7 +24,6 @@
 
 @SmallTest
 @RunWith(AndroidJUnit4.class)
-@SdkSuppress(minSdkVersion = 19)
 public class UninitializedStateTest {
 
     @Before
diff --git a/health/connect/connect-client/src/main/java/androidx/health/connect/client/HealthConnectClient.kt b/health/connect/connect-client/src/main/java/androidx/health/connect/client/HealthConnectClient.kt
index f8d943a..c1d9b78 100644
--- a/health/connect/connect-client/src/main/java/androidx/health/connect/client/HealthConnectClient.kt
+++ b/health/connect/connect-client/src/main/java/androidx/health/connect/client/HealthConnectClient.kt
@@ -26,7 +26,6 @@
 import androidx.annotation.IntDef
 import androidx.annotation.RequiresApi
 import androidx.annotation.RestrictTo
-import androidx.core.content.getSystemService
 import androidx.core.content.pm.PackageInfoCompat
 import androidx.health.connect.client.aggregate.AggregateMetric
 import androidx.health.connect.client.aggregate.AggregationResult
@@ -316,11 +315,6 @@
 
         @RestrictTo(RestrictTo.Scope.LIBRARY)
         internal const val DEFAULT_PROVIDER_MIN_VERSION_CODE = 68623
-
-        @RestrictTo(RestrictTo.Scope.LIBRARY)
-        const val ACTION_HEALTH_CONNECT_SETTINGS_LEGACY =
-            "androidx.health.ACTION_HEALTH_CONNECT_SETTINGS"
-
         /**
          * Intent action to open Health Connect settings on this phone. Developers should use this
          * if they want to re-direct the user to Health Connect.
@@ -399,25 +393,6 @@
             return SDK_AVAILABLE
         }
 
-        @JvmOverloads
-        @JvmStatic
-        @AvailabilityStatus
-        @RestrictTo(RestrictTo.Scope.LIBRARY)
-        fun getSdkStatusLegacy(
-            context: Context,
-            providerPackageName: String = DEFAULT_PROVIDER_PACKAGE_NAME,
-        ): Int {
-            @Suppress("Deprecation")
-            if (!isSdkVersionSufficient()) {
-                return SDK_UNAVAILABLE
-            }
-            @Suppress("Deprecation")
-            if (!isProviderAvailableLegacy(context, providerPackageName)) {
-                return SDK_UNAVAILABLE_PROVIDER_UPDATE_REQUIRED
-            }
-            return SDK_AVAILABLE
-        }
-
         /**
          * Retrieves an IPC-backed [HealthConnectClient] instance binding to an available
          * implementation.
@@ -453,25 +428,6 @@
             )
         }
 
-        @JvmOverloads
-        @JvmStatic
-        @RestrictTo(RestrictTo.Scope.LIBRARY)
-        fun getOrCreateLegacy(
-            context: Context,
-            providerPackageName: String = DEFAULT_PROVIDER_PACKAGE_NAME,
-        ): HealthConnectClient {
-            val status = getSdkStatusLegacy(context, providerPackageName)
-            if (status == SDK_UNAVAILABLE) {
-                throw UnsupportedOperationException("SDK version too low")
-            }
-            if (status == SDK_UNAVAILABLE_PROVIDER_UPDATE_REQUIRED) {
-                throw IllegalStateException("Service not available")
-            }
-            return HealthConnectClientImpl(
-                HealthDataService.getClient(context, providerPackageName)
-            )
-        }
-
         /**
          * Intent to open Health Connect data management screen on this phone. Developers should use
          * this if they want to re-direct the user to Health Connect data management.
@@ -522,13 +478,6 @@
             )
         }
 
-        internal fun isProviderAvailableLegacy(
-            context: Context,
-            providerPackageName: String = DEFAULT_PROVIDER_PACKAGE_NAME,
-        ): Boolean {
-            return isPackageInstalled(context.packageManager, providerPackageName)
-        }
-
         private fun isPackageInstalled(
             packageManager: PackageManager,
             packageName: String,
diff --git a/health/connect/connect-client/src/main/java/androidx/health/connect/client/PermissionController.kt b/health/connect/connect-client/src/main/java/androidx/health/connect/client/PermissionController.kt
index bf1963e..2ff38f8 100644
--- a/health/connect/connect-client/src/main/java/androidx/health/connect/client/PermissionController.kt
+++ b/health/connect/connect-client/src/main/java/androidx/health/connect/client/PermissionController.kt
@@ -16,11 +16,9 @@
 package androidx.health.connect.client
 
 import androidx.activity.result.contract.ActivityResultContract
-import androidx.annotation.RestrictTo
 import androidx.health.connect.client.HealthConnectClient.Companion.DEFAULT_PROVIDER_PACKAGE_NAME
 import androidx.health.connect.client.contracts.HealthPermissionsRequestContract
 import androidx.health.connect.client.permission.HealthPermission
-import androidx.health.connect.client.permission.HealthPermissionsRequestAppContract
 
 @JvmDefaultWithCompatibility
 /** Interface for operations related to permissions. */
@@ -48,15 +46,6 @@
 
     companion object {
 
-        @JvmStatic
-        @JvmOverloads
-        @RestrictTo(RestrictTo.Scope.LIBRARY)
-        fun createRequestPermissionResultContractLegacy(
-            providerPackageName: String = DEFAULT_PROVIDER_PACKAGE_NAME
-        ): ActivityResultContract<Set<String>, Set<String>> {
-            return HealthPermissionsRequestAppContract(providerPackageName = providerPackageName)
-        }
-
         /**
          * Creates an [ActivityResultContract] to request Health permissions.
          *
diff --git a/health/connect/connect-client/src/test/java/androidx/health/connect/client/HealthConnectClientTest.kt b/health/connect/connect-client/src/test/java/androidx/health/connect/client/HealthConnectClientTest.kt
index 9ce84cb..95d9889 100644
--- a/health/connect/connect-client/src/test/java/androidx/health/connect/client/HealthConnectClientTest.kt
+++ b/health/connect/connect-client/src/test/java/androidx/health/connect/client/HealthConnectClientTest.kt
@@ -136,29 +136,6 @@
     }
 
     @Test
-    @Config(sdk = [Build.VERSION_CODES.P])
-    @Suppress("Deprecation")
-    fun backingImplementationLegacy_enabledSupportedVersion_isAvailable() {
-        installPackage(
-            context,
-            HealthConnectClient.DEFAULT_PROVIDER_PACKAGE_NAME,
-            versionCode = HealthConnectClient.DEFAULT_PROVIDER_MIN_VERSION_CODE,
-            enabled = true
-        )
-        installService(context, HealthConnectClient.DEFAULT_PROVIDER_PACKAGE_NAME)
-
-        assertThat(
-                HealthConnectClient.getSdkStatusLegacy(
-                    context,
-                    HealthConnectClient.DEFAULT_PROVIDER_PACKAGE_NAME
-                )
-            )
-            .isEqualTo(HealthConnectClient.SDK_AVAILABLE)
-        assertThat(HealthConnectClient.getOrCreateLegacy(context))
-            .isInstanceOf(HealthConnectClientImpl::class.java)
-    }
-
-    @Test
     @Config(sdk = [Build.VERSION_CODES.O_MR1])
     @Suppress("Deprecation")
     fun sdkVersionTooOld_unavailable() {
@@ -170,17 +147,6 @@
     }
 
     @Test
-    @Config(sdk = [Build.VERSION_CODES.O_MR1])
-    @Suppress("Deprecation")
-    fun sdkVersionTooOld_legacyClient_unavailable() {
-        assertThat(HealthConnectClient.getSdkStatusLegacy(context, PROVIDER_PACKAGE_NAME))
-            .isEqualTo(HealthConnectClient.SDK_UNAVAILABLE)
-        assertThrows(UnsupportedOperationException::class.java) {
-            HealthConnectClient.getOrCreateLegacy(context, PROVIDER_PACKAGE_NAME)
-        }
-    }
-
-    @Test
     @Config(sdk = [Build.VERSION_CODES.P])
     fun getHealthConnectManageDataAction_noProvider_returnsDefaultIntent() {
         assertThat(HealthConnectClient.getHealthConnectManageDataIntent(context).action)
diff --git a/health/connect/connect-client/src/test/java/androidx/health/connect/client/PermissionControllerTest.kt b/health/connect/connect-client/src/test/java/androidx/health/connect/client/PermissionControllerTest.kt
index 71b7317..cfab9d6 100644
--- a/health/connect/connect-client/src/test/java/androidx/health/connect/client/PermissionControllerTest.kt
+++ b/health/connect/connect-client/src/test/java/androidx/health/connect/client/PermissionControllerTest.kt
@@ -72,19 +72,4 @@
             .containsExactly(HealthPermission.WRITE_STEPS, HealthPermission.READ_DISTANCE)
         assertThat(intent.`package`).isNull()
     }
-
-    @Test
-    @Config(minSdk = VERSION_CODES.UPSIDE_DOWN_CAKE)
-    fun createIntentLegacy_UpsideDownCake() {
-        val requestPermissionContract =
-            PermissionController.createRequestPermissionResultContractLegacy(PROVIDER_PACKAGE_NAME)
-        val intent =
-            requestPermissionContract.createIntent(
-                context,
-                setOf(HealthPermission.WRITE_STEPS, HealthPermission.READ_DISTANCE)
-            )
-
-        assertThat(intent.action).isEqualTo("androidx.health.ACTION_REQUEST_PERMISSIONS")
-        assertThat(intent.`package`).isEqualTo(PROVIDER_PACKAGE_NAME)
-    }
 }
diff --git a/input/input-motionprediction/src/main/java/androidx/input/motionprediction/MotionEventPredictor.java b/input/input-motionprediction/src/main/java/androidx/input/motionprediction/MotionEventPredictor.java
index a08bba9..6dcaaa3 100644
--- a/input/input-motionprediction/src/main/java/androidx/input/motionprediction/MotionEventPredictor.java
+++ b/input/input-motionprediction/src/main/java/androidx/input/motionprediction/MotionEventPredictor.java
@@ -23,6 +23,7 @@
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
+import androidx.input.motionprediction.common.SystemProperty;
 import androidx.input.motionprediction.kalman.KalmanMotionEventPredictor;
 import androidx.input.motionprediction.system.SystemMotionEventPredictor;
 
@@ -71,7 +72,8 @@
     @NonNull
     static MotionEventPredictor newInstance(@NonNull View view) {
         Context context = view.getContext();
-        if (Build.VERSION.SDK_INT >= 34) {
+        if (Build.VERSION.SDK_INT >= 34
+                && SystemProperty.getBoolean("debug.input.prefer_system_prediction")) {
             return SystemMotionEventPredictor.newInstance(context);
         } else {
             return new KalmanMotionEventPredictor(context);
diff --git a/input/input-motionprediction/src/main/java/androidx/input/motionprediction/utils/PredictionEstimator.java b/input/input-motionprediction/src/main/java/androidx/input/motionprediction/common/PredictionEstimator.java
similarity index 98%
rename from input/input-motionprediction/src/main/java/androidx/input/motionprediction/utils/PredictionEstimator.java
rename to input/input-motionprediction/src/main/java/androidx/input/motionprediction/common/PredictionEstimator.java
index 3ed9a1b..3376643 100644
--- a/input/input-motionprediction/src/main/java/androidx/input/motionprediction/utils/PredictionEstimator.java
+++ b/input/input-motionprediction/src/main/java/androidx/input/motionprediction/common/PredictionEstimator.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package androidx.input.motionprediction.utils;
+package androidx.input.motionprediction.common;
 
 import static androidx.annotation.RestrictTo.Scope.LIBRARY;
 
diff --git a/input/input-motionprediction/src/main/java/androidx/input/motionprediction/common/SystemProperty.java b/input/input-motionprediction/src/main/java/androidx/input/motionprediction/common/SystemProperty.java
new file mode 100644
index 0000000..2899359
--- /dev/null
+++ b/input/input-motionprediction/src/main/java/androidx/input/motionprediction/common/SystemProperty.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2023 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.input.motionprediction.common;
+
+import static androidx.annotation.RestrictTo.Scope.LIBRARY;
+
+import android.annotation.SuppressLint;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.RestrictTo;
+
+import java.lang.reflect.Method;
+
+/**
+ */
+@RestrictTo(LIBRARY)
+public class SystemProperty {
+    private static final boolean PROPERTY_DEFAULT = false;
+
+    private SystemProperty() {
+        // This class is non-instantiable.
+    }
+
+    /**
+     * Reads a system property and returns its boolean value.
+     *
+     * @param name the name of the system property
+     * @return true if the property is set and true, false otherwise
+     */
+    public static boolean getBoolean(@NonNull String name) {
+        try {
+            @SuppressLint("PrivateApi")
+            Class<?> systemProperties = Class.forName("android.os.SystemProperties");
+            Method getMethod = systemProperties.getMethod(
+                    "getBoolean",
+                    String.class,
+                    boolean.class);
+            @SuppressLint("BanUncheckedReflection")
+            Boolean result = (Boolean) getMethod.invoke(systemProperties, name, PROPERTY_DEFAULT);
+            if (result != null) {
+                return result.booleanValue();
+            }
+        } catch (Exception e) {
+        }
+        return false;
+    }
+}
diff --git a/input/input-motionprediction/src/main/java/androidx/input/motionprediction/kalman/KalmanMotionEventPredictor.java b/input/input-motionprediction/src/main/java/androidx/input/motionprediction/kalman/KalmanMotionEventPredictor.java
index 1b7d73b..64adc26 100644
--- a/input/input-motionprediction/src/main/java/androidx/input/motionprediction/kalman/KalmanMotionEventPredictor.java
+++ b/input/input-motionprediction/src/main/java/androidx/input/motionprediction/kalman/KalmanMotionEventPredictor.java
@@ -25,7 +25,7 @@
 import androidx.annotation.Nullable;
 import androidx.annotation.RestrictTo;
 import androidx.input.motionprediction.MotionEventPredictor;
-import androidx.input.motionprediction.utils.PredictionEstimator;
+import androidx.input.motionprediction.common.PredictionEstimator;
 
 /**
  */
diff --git a/input/input-motionprediction/src/main/java/androidx/input/motionprediction/system/SystemMotionEventPredictor.java b/input/input-motionprediction/src/main/java/androidx/input/motionprediction/system/SystemMotionEventPredictor.java
index 93beded..a1a6b20 100644
--- a/input/input-motionprediction/src/main/java/androidx/input/motionprediction/system/SystemMotionEventPredictor.java
+++ b/input/input-motionprediction/src/main/java/androidx/input/motionprediction/system/SystemMotionEventPredictor.java
@@ -28,8 +28,8 @@
 import androidx.annotation.RequiresApi;
 import androidx.annotation.RestrictTo;
 import androidx.input.motionprediction.MotionEventPredictor;
+import androidx.input.motionprediction.common.PredictionEstimator;
 import androidx.input.motionprediction.kalman.MultiPointerPredictor;
-import androidx.input.motionprediction.utils.PredictionEstimator;
 
 import java.util.concurrent.TimeUnit;
 
diff --git a/kruth/kruth/src/commonMain/kotlin/androidx/kruth/StringSubject.kt b/kruth/kruth/src/commonMain/kotlin/androidx/kruth/StringSubject.kt
index 97e56c3..e0976d2 100644
--- a/kruth/kruth/src/commonMain/kotlin/androidx/kruth/StringSubject.kt
+++ b/kruth/kruth/src/commonMain/kotlin/androidx/kruth/StringSubject.kt
@@ -25,7 +25,8 @@
 class StringSubject internal constructor(
     actual: String?,
     metadata: FailureMetadata = FailureMetadata(),
-) : ComparableSubject<String>(actual = actual, metadata = metadata) {
+) : ComparableSubject<String>(actual = actual, metadata = metadata),
+    PlatformStringSubject by PlatformStringSubjectImpl(actual, metadata) {
 
     /**
      * Fails if the string does not contain the given sequence.
@@ -114,84 +115,41 @@
 
     /** Fails if the string does not match the given [regex]. */
     fun matches(regex: String) {
-        matches(regex.toRegex()) {
+        matchesImpl(regex.toRegex()) {
             "Looks like you want to use .isEqualTo() for an exact equality assertion."
         }
     }
 
     /** Fails if the string does not match the given [regex]. */
     fun matches(regex: Regex) {
-        matches(regex) {
+        matchesImpl(regex) {
             "If you want an exact equality assertion you can escape your regex with Regex.escape()."
         }
     }
 
-    private inline fun matches(regex: Regex, equalToStringErrorMsg: () -> String) {
-        if (actual == null) {
-            failWithActual("Expected a string that matches", regex)
-        }
-
-        if (actual.matches(regex)) {
-            return
-        }
-
-        if (regex.toString() == actual) {
-            failWithoutActual(
-                fact("Expected to match", regex),
-                fact("but was", actual),
-                simpleFact(equalToStringErrorMsg()),
-            )
-        } else {
-            failWithActual("Expected to match", regex);
-        }
-    }
-
     /** Fails if the string matches the given regex.  */
     fun doesNotMatch(regex: String) {
-        doesNotMatch(regex.toRegex())
+        doesNotMatchImpl(regex.toRegex())
     }
 
     /** Fails if the string matches the given regex.  */
     fun doesNotMatch(regex: Regex) {
-        if (actual == null) {
-            failWithActual("Expected a string that does not match", regex)
-        }
-
-        if (actual.matches(regex)) {
-            failWithActual("Expected not to match", regex)
-        }
+        doesNotMatchImpl(regex)
     }
 
     /** Fails if the string does not contain a match on the given regex.  */
     fun containsMatch(regex: Regex) {
-        if (actual == null) {
-            failWithActual("Expected a string that contains a match for", regex)
-        }
-
-        if (!regex.containsMatchIn(actual)) {
-            failWithActual("Expected to contain a match for", regex)
-        }
+        containsMatchImpl(regex)
     }
 
     /** Fails if the string does not contain a match on the given regex.  */
     fun containsMatch(regex: String) {
-        containsMatch(regex.toRegex())
+        containsMatchImpl(regex.toRegex())
     }
 
     /** Fails if the string contains a match on the given regex.  */
     fun doesNotContainMatch(regex: Regex) {
-        if (actual == null) {
-            failWithActual("expected a string that does not contain a match for", regex)
-        }
-
-        val result = regex.find(actual)
-        if (result != null) {
-            failWithoutActual(
-                fact("Expected not to contain a match for", regex),
-                fact("but contained", result.value),
-                fact("Full string", actual)
-            )
-        }
+        doesNotContainMatchImpl(regex)
     }
 
     /** Fails if the string contains a match on the given regex.  */
@@ -302,3 +260,65 @@
         }
     }
 }
+
+internal inline fun Subject<String>.matchesImpl(regex: Regex, equalToStringErrorMsg: () -> String) {
+    if (actual == null) {
+        failWithActualInternal("Expected a string that matches", regex)
+    }
+
+    if (actual.matches(regex)) {
+        return
+    }
+
+    if (regex.toString() == actual) {
+        failWithoutActualInternal(
+            fact("Expected to match", regex),
+            fact("but was", actual),
+            simpleFact(equalToStringErrorMsg()),
+        )
+    } else {
+        failWithActualInternal("Expected to match", regex);
+    }
+}
+
+internal fun Subject<String>.doesNotMatchImpl(regex: Regex) {
+    if (actual == null) {
+        failWithActualInternal("Expected a string that does not match", regex)
+    }
+
+    if (actual.matches(regex)) {
+        failWithActualInternal("Expected not to match", regex)
+    }
+}
+
+internal fun Subject<String>.containsMatchImpl(regex: Regex) {
+    if (actual == null) {
+        failWithActualInternal("Expected a string that contains a match for", regex)
+    }
+
+    if (!regex.containsMatchIn(actual)) {
+        failWithActualInternal("Expected to contain a match for", regex)
+    }
+}
+
+internal fun Subject<String>.doesNotContainMatchImpl(regex: Regex) {
+    if (actual == null) {
+        failWithActualInternal("expected a string that does not contain a match for", regex)
+    }
+
+    val result = regex.find(actual)
+    if (result != null) {
+        failWithoutActualInternal(
+            fact("Expected not to contain a match for", regex),
+            fact("but contained", result.value),
+            fact("Full string", actual)
+        )
+    }
+}
+
+internal expect interface PlatformStringSubject
+
+internal expect class PlatformStringSubjectImpl(
+    actual: String?,
+    metadata: FailureMetadata,
+) : Subject<String>, PlatformStringSubject
diff --git a/kruth/kruth/src/commonMain/kotlin/androidx/kruth/Subject.kt b/kruth/kruth/src/commonMain/kotlin/androidx/kruth/Subject.kt
index 429cfbb..2a8af72 100644
--- a/kruth/kruth/src/commonMain/kotlin/androidx/kruth/Subject.kt
+++ b/kruth/kruth/src/commonMain/kotlin/androidx/kruth/Subject.kt
@@ -122,6 +122,12 @@
 
     // TODO(KT-20427): Only needed to enable extensions in internal sources.
     @Suppress("NOTHING_TO_INLINE")
+    internal inline fun failWithActualInternal(key: String, value: Any? = null): Nothing {
+        failWithActual(key = key, value = value)
+    }
+
+    // TODO(KT-20427): Only needed to enable extensions in internal sources.
+    @Suppress("NOTHING_TO_INLINE")
     internal inline fun failWithActualInternal(vararg facts: Fact): Nothing {
         failWithActual(*facts)
     }
diff --git a/kruth/kruth/src/jvmMain/kotlin/androidx/kruth/PlatformStringSubject.kt b/kruth/kruth/src/jvmMain/kotlin/androidx/kruth/PlatformStringSubject.kt
new file mode 100644
index 0000000..6b00eb5
--- /dev/null
+++ b/kruth/kruth/src/jvmMain/kotlin/androidx/kruth/PlatformStringSubject.kt
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2023 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.kruth
+
+import java.util.regex.Pattern
+
+internal actual interface PlatformStringSubject {
+
+    /** Fails if the string does not match the given regex. */
+    fun matches(regex: Pattern)
+
+    /** Fails if the string matches the given regex. */
+    fun doesNotMatch(regex: Pattern)
+
+    /** Fails if the string does not contain a match on the given regex. */
+    fun containsMatch(regex: Pattern)
+
+    /** Fails if the string contains a match on the given regex. */
+    fun doesNotContainMatch(regex: Pattern)
+}
+
+internal actual class PlatformStringSubjectImpl actual constructor(
+    actual: String?,
+    metadata: FailureMetadata,
+) : Subject<String>(actual, metadata), PlatformStringSubject {
+
+    override fun matches(regex: Pattern) {
+        matchesImpl(regex.toRegex()) {
+            "If you want an exact equality assertion you can escape your regex with " +
+                "Pattern.quote()."
+        }
+    }
+
+    override fun doesNotMatch(regex: Pattern) {
+        doesNotMatchImpl(regex.toRegex())
+    }
+
+    override fun containsMatch(regex: Pattern) {
+        containsMatchImpl(regex.toRegex())
+    }
+
+    override fun doesNotContainMatch(regex: Pattern) {
+        doesNotContainMatchImpl(regex.toRegex())
+    }
+}
diff --git a/kruth/kruth/src/jvmTest/kotlin/androidx/kruth/StringSubjectJvmTest.kt b/kruth/kruth/src/jvmTest/kotlin/androidx/kruth/StringSubjectJvmTest.kt
new file mode 100644
index 0000000..730fbae
--- /dev/null
+++ b/kruth/kruth/src/jvmTest/kotlin/androidx/kruth/StringSubjectJvmTest.kt
@@ -0,0 +1,104 @@
+/*
+ * Copyright 2023 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.kruth
+
+import kotlin.test.Test
+import kotlin.test.assertFailsWith
+
+class StringSubjectJvmTest {
+
+    @Test
+    fun stringMatchesPattern() {
+        assertThat("abcaaadev").matches(".*aaa.*".toPattern())
+    }
+
+    @Test
+    fun stringMatchesPatternWithFail() {
+        assertFailsWith<AssertionError> {
+            assertThat("abcaqadev").matches(".*aaa.*".toPattern())
+        }
+    }
+
+    @Test
+    fun stringMatchesPatternFailNull() {
+        assertFailsWith<AssertionError> {
+            assertThat(null as String?).matches(".*aaa.*".toPattern())
+        }
+    }
+
+    @Test
+    fun stringMatchesPatternLiteralFail() {
+        assertFailsWith<AssertionError> {
+            assertThat("\$abc").matches("\$abc".toPattern())
+        }
+    }
+
+    @Test
+    fun stringDoesNotMatchPattern() {
+        assertThat("abcaqadev").doesNotMatch(".*aaa.*".toPattern())
+    }
+
+    @Test
+    fun stringDoesNotMatchPatternWithFail() {
+        assertFailsWith<AssertionError> {
+            assertThat("abcaaadev").doesNotMatch(".*aaa.*".toPattern())
+        }
+    }
+
+    @Test
+    fun stringDoesNotMatchPatternFailNull() {
+        assertFailsWith<AssertionError> {
+            assertThat(null as String?).doesNotMatch(".*aaa.*".toPattern())
+        }
+    }
+
+    @Test
+    fun stringContainsMatchStringUsesFind() {
+        assertThat("aba").containsMatch("[b]")
+        assertThat("aba").containsMatch("[b]".toPattern())
+    }
+
+    @Test
+    fun stringContainsMatchPattern() {
+        assertThat("aba").containsMatch(".*b.*".toPattern())
+        assertFailsWith<AssertionError> {
+            assertThat("aaa").containsMatch(".*b.*".toPattern())
+        }
+    }
+
+    @Test
+    fun stringContainsMatchPatternFailNull() {
+        assertFailsWith<AssertionError> {
+            assertThat(null as String?).containsMatch(".*b.*".toPattern())
+        }
+    }
+
+    @Test
+    fun stringDoesNotContainMatchPattern() {
+        assertThat("zzaaazz").doesNotContainMatch(".b.".toPattern())
+        assertFailsWith<AssertionError> {
+            assertThat("zzabazz").doesNotContainMatch(".b.".toPattern())
+        }
+    }
+
+    @Test
+    fun stringDoesNotContainMatchPatternFailNull() {
+        assertFailsWith<AssertionError> {
+            assertThat(null as String?).doesNotContainMatch(".b.".toPattern())
+        }
+    }
+}
diff --git a/camera/camera-viewfinder/src/main/java/androidx/camera/viewfinder/internal/utils/executor/package-info.java b/kruth/kruth/src/nativeMain/kotlin/androidx/kruth/PlatformStringSubject.kt
similarity index 64%
copy from camera/camera-viewfinder/src/main/java/androidx/camera/viewfinder/internal/utils/executor/package-info.java
copy to kruth/kruth/src/nativeMain/kotlin/androidx/kruth/PlatformStringSubject.kt
index 4312b3d..dcc22dd 100644
--- a/camera/camera-viewfinder/src/main/java/androidx/camera/viewfinder/internal/utils/executor/package-info.java
+++ b/kruth/kruth/src/nativeMain/kotlin/androidx/kruth/PlatformStringSubject.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright 2022 The Android Open Source Project
+ * Copyright 2023 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.
@@ -14,9 +14,11 @@
  * limitations under the License.
  */
 
-/**
- */
-@RestrictTo(RestrictTo.Scope.LIBRARY)
-package androidx.camera.viewfinder.internal.utils.executor;
+package androidx.kruth
 
-import androidx.annotation.RestrictTo;
+internal actual interface PlatformStringSubject
+
+internal actual class PlatformStringSubjectImpl actual constructor(
+    actual: String?,
+    metadata: FailureMetadata,
+) : Subject<String>(actual, metadata), PlatformStringSubject
diff --git a/libraryversions.toml b/libraryversions.toml
index 6e854ff..9b6a6ce 100644
--- a/libraryversions.toml
+++ b/libraryversions.toml
@@ -20,13 +20,13 @@
 CARDVIEW = "1.1.0-alpha01"
 CAR_APP = "1.7.0-alpha01"
 COLLECTION = "1.4.0-beta01"
-COMPOSE = "1.6.0-beta01"
+COMPOSE = "1.7.0-alpha01"
 COMPOSE_COMPILER = "1.5.4"
 COMPOSE_MATERIAL3 = "1.2.0-alpha11"
 COMPOSE_MATERIAL3_ADAPTIVE = "1.0.0-alpha01"
 COMPOSE_MATERIAL3_ADAPTIVE_NAVIGATION_SUITE = "1.0.0-alpha01"
 COMPOSE_MATERIAL3_COMMON = "1.0.0-alpha01"
-COMPOSE_RUNTIME_TRACING = "1.0.0-alpha05"
+COMPOSE_RUNTIME_TRACING = "1.0.0-beta01"
 CONSTRAINTLAYOUT = "2.2.0-alpha13"
 CONSTRAINTLAYOUT_COMPOSE = "1.1.0-alpha13"
 CONSTRAINTLAYOUT_CORE = "1.1.0-alpha13"
@@ -83,10 +83,10 @@
 INTERPOLATOR = "1.1.0-alpha01"
 JAVASCRIPTENGINE = "1.0.0-beta01"
 KRUTH = "1.1.0-alpha01"
-LEANBACK = "1.2.0-alpha03"
-LEANBACK_GRID = "1.0.0-alpha02"
-LEANBACK_PAGING = "1.1.0-alpha10"
-LEANBACK_PREFERENCE = "1.2.0-alpha03"
+LEANBACK = "1.2.0-alpha04"
+LEANBACK_GRID = "1.0.0-alpha03"
+LEANBACK_PAGING = "1.1.0-alpha11"
+LEANBACK_PREFERENCE = "1.2.0-alpha04"
 LEANBACK_TAB = "1.1.0-beta01"
 LEGACY = "1.1.0-alpha01"
 LIBYUV = "0.1.0-dev01"
@@ -156,12 +156,12 @@
 WEAR_INPUT_TESTING = "1.2.0-alpha03"
 WEAR_ONGOING = "1.1.0-alpha02"
 WEAR_PHONE_INTERACTIONS = "1.1.0-alpha04"
-WEAR_PROTOLAYOUT = "1.1.0-alpha01"
+WEAR_PROTOLAYOUT = "1.1.0-alpha02"
 WEAR_REMOTE_INTERACTIONS = "1.1.0-alpha01"
-WEAR_TILES = "1.3.0-alpha01"
+WEAR_TILES = "1.3.0-alpha02"
 WEAR_TOOLING_PREVIEW = "1.0.0-rc01"
 WEAR_WATCHFACE = "1.3.0-alpha01"
-WEBKIT = "1.9.0-rc01"
+WEBKIT = "1.10.0-alpha01"
 # Adding a comment to prevent merge conflicts for Window artifact
 WINDOW = "1.3.0-alpha01"
 WINDOW_EXTENSIONS = "1.3.0-alpha01"
@@ -196,6 +196,7 @@
 COMPOSE_MATERIAL = { group = "androidx.compose.material", atomicGroupVersion = "versions.COMPOSE" }
 COMPOSE_MATERIAL3 = { group = "androidx.compose.material3", atomicGroupVersion = "versions.COMPOSE_MATERIAL3" }
 COMPOSE_RUNTIME = { group = "androidx.compose.runtime", atomicGroupVersion = "versions.COMPOSE" }
+COMPOSE_RUNTIME_TRACING = { group = "androidx.compose.runtime", atomicGroupVersion = "versions.COMPOSE_RUNTIME_TRACING", overrideInclude = [ ":compose:runtime:runtime-tracing" ] }
 COMPOSE_UI = { group = "androidx.compose.ui", atomicGroupVersion = "versions.COMPOSE" }
 CONCURRENT = { group = "androidx.concurrent", atomicGroupVersion = "versions.FUTURES" }
 CONSTRAINTLAYOUT = { group = "androidx.constraintlayout" }
diff --git a/media/media/src/main/java/android/support/v4/media/MediaBrowserCompat.java b/media/media/src/main/java/android/support/v4/media/MediaBrowserCompat.java
index c5b4614..b3ce8b3 100644
--- a/media/media/src/main/java/android/support/v4/media/MediaBrowserCompat.java
+++ b/media/media/src/main/java/android/support/v4/media/MediaBrowserCompat.java
@@ -83,7 +83,6 @@
 import androidx.annotation.RequiresApi;
 import androidx.annotation.RestrictTo;
 import androidx.collection.ArrayMap;
-import androidx.core.app.BundleCompat;
 import androidx.media.MediaBrowserCompatUtils;
 import androidx.media.MediaBrowserServiceCompat;
 
@@ -1928,7 +1927,7 @@
                 return;
             }
             mServiceVersion = extras.getInt(EXTRA_SERVICE_VERSION, 0);
-            IBinder serviceBinder = BundleCompat.getBinder(extras, EXTRA_MESSENGER_BINDER);
+            IBinder serviceBinder = extras.getBinder(EXTRA_MESSENGER_BINDER);
             if (serviceBinder != null) {
                 mServiceBinderWrapper = new ServiceBinderWrapper(serviceBinder, mRootHints);
                 mCallbacksMessenger = new Messenger(mHandler);
@@ -1940,7 +1939,7 @@
                 }
             }
             IMediaSession sessionToken = IMediaSession.Stub.asInterface(
-                    BundleCompat.getBinder(extras, EXTRA_SESSION_BINDER));
+                    extras.getBinder(EXTRA_SESSION_BINDER));
             if (sessionToken != null) {
                 mMediaSessionToken = MediaSessionCompat.Token.fromToken(
                         mBrowserFwk.getSessionToken(), sessionToken);
@@ -2211,7 +2210,7 @@
                 throws RemoteException {
             Bundle data = new Bundle();
             data.putString(DATA_MEDIA_ITEM_ID, parentId);
-            BundleCompat.putBinder(data, DATA_CALLBACK_TOKEN, callbackToken);
+            data.putBinder(DATA_CALLBACK_TOKEN, callbackToken);
             data.putBundle(DATA_OPTIONS, options);
             sendRequest(CLIENT_MSG_ADD_SUBSCRIPTION, data, callbacksMessenger);
         }
@@ -2221,7 +2220,7 @@
                 throws RemoteException {
             Bundle data = new Bundle();
             data.putString(DATA_MEDIA_ITEM_ID, parentId);
-            BundleCompat.putBinder(data, DATA_CALLBACK_TOKEN, callbackToken);
+            data.putBinder(DATA_CALLBACK_TOKEN, callbackToken);
             sendRequest(CLIENT_MSG_REMOVE_SUBSCRIPTION, data, callbacksMessenger);
         }
 
diff --git a/media/media/src/main/java/android/support/v4/media/session/MediaControllerCompat.java b/media/media/src/main/java/android/support/v4/media/session/MediaControllerCompat.java
index 19f88c9..49620d4 100644
--- a/media/media/src/main/java/android/support/v4/media/session/MediaControllerCompat.java
+++ b/media/media/src/main/java/android/support/v4/media/session/MediaControllerCompat.java
@@ -52,7 +52,6 @@
 import androidx.annotation.Nullable;
 import androidx.annotation.RequiresApi;
 import androidx.annotation.RestrictTo;
-import androidx.core.app.BundleCompat;
 import androidx.media.AudioAttributesCompat;
 import androidx.media.R;
 import androidx.media.VolumeProviderCompat;
@@ -2327,8 +2326,8 @@
                 synchronized (mediaControllerImpl.mLock) {
                     mediaControllerImpl.mSessionToken.setExtraBinder(
                             IMediaSession.Stub.asInterface(
-                                    BundleCompat.getBinder(
-                                            resultData, MediaSessionCompat.KEY_EXTRA_BINDER)));
+                                    resultData.getBinder(
+                                            MediaSessionCompat.KEY_EXTRA_BINDER)));
                     mediaControllerImpl.mSessionToken.setSession2Token(
                             ParcelUtils.getVersionedParcelable(resultData,
                                     MediaSessionCompat.KEY_SESSION2_TOKEN));
diff --git a/media/media/src/main/java/android/support/v4/media/session/MediaSessionCompat.java b/media/media/src/main/java/android/support/v4/media/session/MediaSessionCompat.java
index 43538e0..a92d2198 100644
--- a/media/media/src/main/java/android/support/v4/media/session/MediaSessionCompat.java
+++ b/media/media/src/main/java/android/support/v4/media/session/MediaSessionCompat.java
@@ -72,7 +72,6 @@
 import androidx.annotation.Nullable;
 import androidx.annotation.RequiresApi;
 import androidx.annotation.RestrictTo;
-import androidx.core.app.BundleCompat;
 import androidx.media.MediaSessionManager;
 import androidx.media.MediaSessionManager.RemoteUserInfo;
 import androidx.media.VolumeProviderCompat;
@@ -1535,7 +1534,7 @@
                         Bundle result = new Bundle();
                         Token token = sessionImpl.getSessionToken();
                         IMediaSession extraBinder = token.getExtraBinder();
-                        BundleCompat.putBinder(result, KEY_EXTRA_BINDER,
+                        result.putBinder(KEY_EXTRA_BINDER,
                                 extraBinder == null ? null : extraBinder.asBinder());
                         ParcelUtils.putVersionedParcelable(result,
                                 KEY_SESSION2_TOKEN, token.getSession2Token());
@@ -2077,7 +2076,7 @@
             bundle.putParcelable(KEY_TOKEN, this);
             synchronized (mLock) {
                 if (mExtraBinder != null) {
-                    BundleCompat.putBinder(bundle, KEY_EXTRA_BINDER, mExtraBinder.asBinder());
+                    bundle.putBinder(KEY_EXTRA_BINDER, mExtraBinder.asBinder());
                 }
                 if (mSession2Token != null) {
                     ParcelUtils.putVersionedParcelable(bundle, KEY_SESSION2_TOKEN, mSession2Token);
@@ -2100,7 +2099,7 @@
             }
             tokenBundle.setClassLoader(Token.class.getClassLoader());
             IMediaSession extraSession = IMediaSession.Stub.asInterface(
-                    BundleCompat.getBinder(tokenBundle, KEY_EXTRA_BINDER));
+                    tokenBundle.getBinder(KEY_EXTRA_BINDER));
             VersionedParcelable session2Token = ParcelUtils.getVersionedParcelable(tokenBundle,
                     KEY_SESSION2_TOKEN);
             Token token = tokenBundle.getParcelable(KEY_TOKEN);
diff --git a/media/media/src/main/java/androidx/media/MediaBrowserServiceCompat.java b/media/media/src/main/java/androidx/media/MediaBrowserServiceCompat.java
index db98259..1cadfef 100644
--- a/media/media/src/main/java/androidx/media/MediaBrowserServiceCompat.java
+++ b/media/media/src/main/java/androidx/media/MediaBrowserServiceCompat.java
@@ -86,7 +86,6 @@
 import androidx.annotation.RequiresApi;
 import androidx.annotation.RestrictTo;
 import androidx.collection.ArrayMap;
-import androidx.core.app.BundleCompat;
 import androidx.core.util.Pair;
 import androidx.media.MediaSessionManager.RemoteUserInfo;
 
@@ -328,7 +327,7 @@
                 IMediaSession extraBinder = token.getExtraBinder();
                 if (extraBinder != null) {
                     for (Bundle rootExtras : mRootExtrasList) {
-                        BundleCompat.putBinder(rootExtras, EXTRA_SESSION_BINDER,
+                        rootExtras.putBinder(EXTRA_SESSION_BINDER,
                                 extraBinder.asBinder());
                     }
                 }
@@ -359,10 +358,10 @@
                 mMessenger = new Messenger(mHandler);
                 rootExtras = new Bundle();
                 rootExtras.putInt(EXTRA_SERVICE_VERSION, SERVICE_VERSION_CURRENT);
-                BundleCompat.putBinder(rootExtras, EXTRA_MESSENGER_BINDER, mMessenger.getBinder());
+                rootExtras.putBinder(EXTRA_MESSENGER_BINDER, mMessenger.getBinder());
                 if (mSession != null) {
                     IMediaSession extraBinder = mSession.getExtraBinder();
-                    BundleCompat.putBinder(rootExtras, EXTRA_SESSION_BINDER,
+                    rootExtras.putBinder(EXTRA_SESSION_BINDER,
                             extraBinder == null ? null : extraBinder.asBinder());
                 } else {
                     mRootExtrasList.add(rootExtras);
@@ -1604,7 +1603,7 @@
 
                 mServiceBinderImpl.addSubscription(
                         data.getString(DATA_MEDIA_ITEM_ID),
-                        BundleCompat.getBinder(data, DATA_CALLBACK_TOKEN),
+                        data.getBinder(DATA_CALLBACK_TOKEN),
                         options,
                         new ServiceCallbacksCompat(msg.replyTo));
                 break;
@@ -1612,7 +1611,7 @@
             case CLIENT_MSG_REMOVE_SUBSCRIPTION:
                 mServiceBinderImpl.removeSubscription(
                         data.getString(DATA_MEDIA_ITEM_ID),
-                        BundleCompat.getBinder(data, DATA_CALLBACK_TOKEN),
+                        data.getBinder(DATA_CALLBACK_TOKEN),
                         new ServiceCallbacksCompat(msg.replyTo));
                 break;
             case CLIENT_MSG_GET_MEDIA_ITEM:
diff --git a/media/media/src/main/java/androidx/media/app/NotificationCompat.java b/media/media/src/main/java/androidx/media/app/NotificationCompat.java
index 97c445e..56928a6 100644
--- a/media/media/src/main/java/androidx/media/app/NotificationCompat.java
+++ b/media/media/src/main/java/androidx/media/app/NotificationCompat.java
@@ -40,7 +40,6 @@
 import androidx.annotation.RequiresApi;
 import androidx.annotation.RequiresPermission;
 import androidx.annotation.RestrictTo;
-import androidx.core.app.BundleCompat;
 import androidx.core.app.NotificationBuilderWithBuilderAccessor;
 import androidx.media.R;
 
@@ -117,7 +116,7 @@
                         return MediaSessionCompat.Token.fromToken(tokenInner);
                     }
                 } else {
-                    IBinder tokenInner = BundleCompat.getBinder(extras,
+                    IBinder tokenInner = extras.getBinder(
                             androidx.core.app.NotificationCompat.EXTRA_MEDIA_SESSION);
                     if (tokenInner != null) {
                         Parcel p = Parcel.obtain();
@@ -617,4 +616,4 @@
             return style;
         }
     }
-}
\ No newline at end of file
+}
diff --git a/privacysandbox/activity/activity-client/src/main/java/androidx/privacysandbox/activity/client/SdkActivityLaunchers.kt b/privacysandbox/activity/activity-client/src/main/java/androidx/privacysandbox/activity/client/SdkActivityLaunchers.kt
index 2dd726e..01e5c50 100644
--- a/privacysandbox/activity/activity-client/src/main/java/androidx/privacysandbox/activity/client/SdkActivityLaunchers.kt
+++ b/privacysandbox/activity/activity-client/src/main/java/androidx/privacysandbox/activity/client/SdkActivityLaunchers.kt
@@ -21,7 +21,6 @@
 import android.app.Activity
 import android.os.Bundle
 import android.os.IBinder
-import androidx.core.os.BundleCompat
 import androidx.lifecycle.LifecycleOwner
 import androidx.lifecycle.lifecycleScope
 import androidx.privacysandbox.activity.core.ISdkActivityLauncher
@@ -68,7 +67,7 @@
 fun SdkActivityLauncher.toLauncherInfo(): Bundle {
     val binderDelegate = SdkActivityLauncherBinderDelegate(this)
     return Bundle().also { bundle ->
-        BundleCompat.putBinder(bundle, SDK_ACTIVITY_LAUNCHER_BINDER_KEY, binderDelegate)
+        bundle.putBinder(SDK_ACTIVITY_LAUNCHER_BINDER_KEY, binderDelegate)
     }
 }
 
diff --git a/privacysandbox/activity/activity-provider/src/main/java/androidx/privacysandbox/activity/provider/SdkActivityLauncherFactory.kt b/privacysandbox/activity/activity-provider/src/main/java/androidx/privacysandbox/activity/provider/SdkActivityLauncherFactory.kt
index 8e53590..c6ae8f1 100644
--- a/privacysandbox/activity/activity-provider/src/main/java/androidx/privacysandbox/activity/provider/SdkActivityLauncherFactory.kt
+++ b/privacysandbox/activity/activity-provider/src/main/java/androidx/privacysandbox/activity/provider/SdkActivityLauncherFactory.kt
@@ -18,7 +18,6 @@
 
 import android.os.Bundle
 import android.os.IBinder
-import androidx.core.os.BundleCompat
 import androidx.privacysandbox.activity.core.ISdkActivityLauncher
 import androidx.privacysandbox.activity.core.ISdkActivityLauncherCallback
 import androidx.privacysandbox.activity.core.ProtocolConstants.SDK_ACTIVITY_LAUNCHER_BINDER_KEY
@@ -39,7 +38,7 @@
     @JvmStatic
     fun fromLauncherInfo(launcherInfo: Bundle): SdkActivityLauncher {
         val remote: ISdkActivityLauncher? = ISdkActivityLauncher.Stub.asInterface(
-            BundleCompat.getBinder(launcherInfo, SDK_ACTIVITY_LAUNCHER_BINDER_KEY)
+            launcherInfo.getBinder(SDK_ACTIVITY_LAUNCHER_BINDER_KEY)
         )
         requireNotNull(remote) { "Invalid SdkActivityLauncher info bundle." }
         return SdkActivityLauncherProxy(remote)
diff --git a/privacysandbox/sdkruntime/sdkruntime-client/lint-baseline.xml b/privacysandbox/sdkruntime/sdkruntime-client/lint-baseline.xml
index fdc15ae..24c78da 100644
--- a/privacysandbox/sdkruntime/sdkruntime-client/lint-baseline.xml
+++ b/privacysandbox/sdkruntime/sdkruntime-client/lint-baseline.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<issues format="6" by="lint 8.3.0-alpha04" type="baseline" client="gradle" dependencies="false" name="AGP (8.3.0-alpha04)" variant="all" version="8.3.0-alpha04">
+<issues format="6" by="lint 8.3.0-alpha10" type="baseline" client="gradle" dependencies="false" name="AGP (8.3.0-alpha10)" variant="all" version="8.3.0-alpha10">
 
     <issue
         id="BanThreadSleep"
@@ -64,22 +64,4 @@
             file="src/main/java/androidx/privacysandbox/sdkruntime/client/SdkSandboxManagerCompat.kt"/>
     </issue>
 
-    <issue
-        id="ObsoleteSdkInt"
-        message="Unnecessary; SDK_INT is always >= 19"
-        errorLine1="        if (Build.VERSION.SDK_INT >= JELLY_BEAN_MR2) {"
-        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/privacysandbox/sdkruntime/client/loader/storage/CachedLocalSdkStorage.kt"/>
-    </issue>
-
-    <issue
-        id="ObsoleteSdkInt"
-        message="Unnecessary; SDK_INT is always >= 18"
-        errorLine1="    @RequiresApi(JELLY_BEAN_MR2)"
-        errorLine2="    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/privacysandbox/sdkruntime/client/loader/storage/CachedLocalSdkStorage.kt"/>
-    </issue>
-
 </issues>
diff --git a/privacysandbox/sdkruntime/sdkruntime-client/src/androidTest/java/androidx/privacysandbox/sdkruntime/client/SdkSandboxManagerAppOwnedInterfacesTest.kt b/privacysandbox/sdkruntime/sdkruntime-client/src/androidTest/java/androidx/privacysandbox/sdkruntime/client/SdkSandboxManagerAppOwnedInterfacesTest.kt
index edea99d..a29c74b 100644
--- a/privacysandbox/sdkruntime/sdkruntime-client/src/androidTest/java/androidx/privacysandbox/sdkruntime/client/SdkSandboxManagerAppOwnedInterfacesTest.kt
+++ b/privacysandbox/sdkruntime/sdkruntime-client/src/androidTest/java/androidx/privacysandbox/sdkruntime/client/SdkSandboxManagerAppOwnedInterfacesTest.kt
@@ -16,17 +16,23 @@
 
 package androidx.privacysandbox.sdkruntime.client
 
+import android.annotation.SuppressLint
 import android.app.sdksandbox.SdkSandboxManager
 import android.content.Context
 import android.os.Binder
+import android.os.Build
 import android.os.Bundle
+import android.os.ext.SdkExtensions
+import androidx.annotation.DoNotInline
+import androidx.annotation.RequiresExtension
 import androidx.core.content.getSystemService
+import androidx.core.os.BuildCompat
 import androidx.privacysandbox.sdkruntime.client.loader.asTestSdk
 import androidx.privacysandbox.sdkruntime.core.AdServicesInfo
-import androidx.privacysandbox.sdkruntime.core.AppOwnedInterfaceConverter
 import androidx.privacysandbox.sdkruntime.core.AppOwnedSdkSandboxInterfaceCompat
 import androidx.test.core.app.ApplicationProvider
 import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SdkSuppress
 import androidx.test.filters.SmallTest
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.runBlocking
@@ -53,12 +59,13 @@
         sandboxManagerCompat = SdkSandboxManagerCompat.from(context)
     }
 
+    @SuppressLint("NewApi", "ClassVerificationFailure") // For supporting DP Builds
     @After
     fun tearDown() {
         SdkSandboxManagerCompat.reset()
         if (isAppOwnedInterfacesApiAvailable()) {
-            val registeredInterfaces = getRegisteredInterfaces()
-            unregisterInterfaces(registeredInterfaces)
+            val registeredInterfaces = getRegisteredInterfaces(context)
+            unregisterInterfaces(context, registeredInterfaces)
         }
     }
 
@@ -82,6 +89,9 @@
     }
 
     @Test
+    // TODO(b/262577044) Remove RequiresExtension after extensions support in @SdkSuppress
+    @RequiresExtension(extension = SdkExtensions.AD_SERVICES, version = 8)
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.TIRAMISU)
     fun registerAppOwnedSdkSandboxInterface_whenApiAvailable_registerInPlatform() {
         assumeTrue(
             "Requires AppOwnedInterfacesApi API available",
@@ -95,7 +105,7 @@
         )
 
         sandboxManagerCompat.registerAppOwnedSdkSandboxInterface(appOwnedInterface)
-        val platformRegisteredInterfaces = getRegisteredInterfaces()
+        val platformRegisteredInterfaces = getRegisteredInterfaces(context)
         assertThat(platformRegisteredInterfaces).hasSize(1)
         assertThat(platformRegisteredInterfaces[0].getName()).isEqualTo(appOwnedInterface.getName())
     }
@@ -116,6 +126,9 @@
     }
 
     @Test
+    // TODO(b/262577044) Remove RequiresExtension after extensions support in @SdkSuppress
+    @RequiresExtension(extension = SdkExtensions.AD_SERVICES, version = 8)
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.TIRAMISU)
     fun unregisterAppOwnedSdkSandboxInterface_whenApiAvailable_unregisterFromPlatform() {
         assumeTrue(
             "Requires AppOwnedInterfacesApi API available",
@@ -131,7 +144,7 @@
         sandboxManagerCompat.registerAppOwnedSdkSandboxInterface(appOwnedInterface)
         sandboxManagerCompat.unregisterAppOwnedSdkSandboxInterface(appOwnedInterface.getName())
 
-        val platformRegisteredInterfaces = getRegisteredInterfaces()
+        val platformRegisteredInterfaces = getRegisteredInterfaces(context)
         assertThat(platformRegisteredInterfaces).isEmpty()
     }
 
@@ -162,28 +175,29 @@
         assertThat(appOwnedSdkResult.getInterface()).isEqualTo(registeredAppOwnedSdk.getInterface())
     }
 
-    private fun getRegisteredInterfaces(): List<AppOwnedSdkSandboxInterfaceCompat> {
-        val converter = AppOwnedInterfaceConverter()
+    @RequiresExtension(extension = SdkExtensions.AD_SERVICES, version = 8)
+    companion object AppOwnedInterfacesApi { // to avoid class verification fails
+        @DoNotInline
+        fun getRegisteredInterfaces(
+            context: Context
+        ): List<AppOwnedSdkSandboxInterfaceCompat> {
+            val sandboxManager = context.getSystemService<SdkSandboxManager>()!!
+            val results = sandboxManager.getAppOwnedSdkSandboxInterfaces()
+            return results.map { AppOwnedSdkSandboxInterfaceCompat(it) }
+        }
 
-        val sandboxManager = context.getSystemService<SdkSandboxManager>()!!
-        val getInterfacesMethod = sandboxManager.javaClass.getMethod(
-            "getAppOwnedSdkSandboxInterfaces"
-        )
-
-        val results = getInterfacesMethod.invoke(sandboxManager) as List<*>
-        return results.map { converter.toCompat(it!!) }
-    }
-
-    private fun unregisterInterfaces(appOwnedInterfaces: List<AppOwnedSdkSandboxInterfaceCompat>) {
-        val sandboxManager = context.getSystemService<SdkSandboxManager>()!!
-        val unregisterMethod = sandboxManager.javaClass.getMethod(
-            "unregisterAppOwnedSdkSandboxInterface",
-            /* parameter1 */ String::class.java
-        )
-
-        appOwnedInterfaces.forEach { unregisterMethod.invoke(sandboxManager, it.getName()) }
+        @DoNotInline
+        fun unregisterInterfaces(
+            context: Context,
+            appOwnedInterfaces: List<AppOwnedSdkSandboxInterfaceCompat>
+        ) {
+            val sandboxManager = context.getSystemService<SdkSandboxManager>()!!
+            appOwnedInterfaces.forEach {
+                sandboxManager.unregisterAppOwnedSdkSandboxInterface(it.getName())
+            }
+        }
     }
 
     private fun isAppOwnedInterfacesApiAvailable() =
-        AdServicesInfo.isDeveloperPreview()
+        BuildCompat.AD_SERVICES_EXTENSION_INT >= 8 || AdServicesInfo.isDeveloperPreview()
 }
diff --git a/privacysandbox/sdkruntime/sdkruntime-client/src/androidTest/java/androidx/privacysandbox/sdkruntime/client/controller/impl/PlatformAppOwnedSdkRegistryTest.kt b/privacysandbox/sdkruntime/sdkruntime-client/src/androidTest/java/androidx/privacysandbox/sdkruntime/client/controller/impl/PlatformAppOwnedSdkRegistryTest.kt
index 819055d..80c7f9a 100644
--- a/privacysandbox/sdkruntime/sdkruntime-client/src/androidTest/java/androidx/privacysandbox/sdkruntime/client/controller/impl/PlatformAppOwnedSdkRegistryTest.kt
+++ b/privacysandbox/sdkruntime/sdkruntime-client/src/androidTest/java/androidx/privacysandbox/sdkruntime/client/controller/impl/PlatformAppOwnedSdkRegistryTest.kt
@@ -23,9 +23,9 @@
 import android.os.ext.SdkExtensions.AD_SERVICES
 import androidx.annotation.RequiresExtension
 import androidx.core.content.getSystemService
+import androidx.core.os.BuildCompat
 import androidx.privacysandbox.sdkruntime.client.controller.AppOwnedSdkRegistry
 import androidx.privacysandbox.sdkruntime.core.AdServicesInfo
-import androidx.privacysandbox.sdkruntime.core.AppOwnedInterfaceConverter
 import androidx.privacysandbox.sdkruntime.core.AppOwnedSdkSandboxInterfaceCompat
 import androidx.test.core.app.ApplicationProvider
 import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -41,7 +41,7 @@
 @SmallTest
 @RunWith(AndroidJUnit4::class)
 // TODO(b/262577044) Remove RequiresExtension after extensions support in @SdkSuppress
-@RequiresExtension(extension = AD_SERVICES, version = 4)
+@RequiresExtension(extension = AD_SERVICES, version = 8)
 @SdkSuppress(minSdkVersion = Build.VERSION_CODES.TIRAMISU)
 class PlatformAppOwnedSdkRegistryTest {
 
@@ -52,7 +52,7 @@
     fun setUp() {
         assumeTrue(
             "Requires AppOwnedInterfacesApi API available",
-            AdServicesInfo.isDeveloperPreview()
+            isAppOwnedInterfacesApiAvailable()
         )
         context = ApplicationProvider.getApplicationContext()
         sdkRegistry = PlatformAppOwnedSdkRegistry(context)
@@ -60,7 +60,7 @@
 
     @After
     fun tearDown() {
-        if (AdServicesInfo.isDeveloperPreview()) {
+        if (isAppOwnedInterfacesApiAvailable()) {
             val registeredInterfaces = getRegisteredInterfaces()
             unregisterInterfaces(registeredInterfaces)
         }
@@ -119,24 +119,18 @@
     }
 
     private fun getRegisteredInterfaces(): List<AppOwnedSdkSandboxInterfaceCompat> {
-        val converter = AppOwnedInterfaceConverter()
-
         val sandboxManager = context.getSystemService<SdkSandboxManager>()!!
-        val getInterfacesMethod = sandboxManager.javaClass.getMethod(
-            "getAppOwnedSdkSandboxInterfaces"
-        )
-
-        val results = getInterfacesMethod.invoke(sandboxManager) as List<*>
-        return results.map { converter.toCompat(it!!) }
+        val results = sandboxManager.getAppOwnedSdkSandboxInterfaces()
+        return results.map { AppOwnedSdkSandboxInterfaceCompat(it) }
     }
 
     private fun unregisterInterfaces(appOwnedInterfaces: List<AppOwnedSdkSandboxInterfaceCompat>) {
         val sandboxManager = context.getSystemService<SdkSandboxManager>()!!
-        val unregisterMethod = sandboxManager.javaClass.getMethod(
-            "unregisterAppOwnedSdkSandboxInterface",
-            /* parameter1 */ String::class.java
-        )
-
-        appOwnedInterfaces.forEach { unregisterMethod.invoke(sandboxManager, it.getName()) }
+        appOwnedInterfaces.forEach {
+            sandboxManager.unregisterAppOwnedSdkSandboxInterface(it.getName())
+        }
     }
+
+    private fun isAppOwnedInterfacesApiAvailable() =
+        BuildCompat.AD_SERVICES_EXTENSION_INT >= 8 || AdServicesInfo.isDeveloperPreview()
 }
diff --git a/privacysandbox/sdkruntime/sdkruntime-client/src/androidTest/java/androidx/privacysandbox/sdkruntime/client/loader/storage/CachedLocalSdkStorageTest.kt b/privacysandbox/sdkruntime/sdkruntime-client/src/androidTest/java/androidx/privacysandbox/sdkruntime/client/loader/storage/CachedLocalSdkStorageTest.kt
index 900ac0a..03e88ab 100644
--- a/privacysandbox/sdkruntime/sdkruntime-client/src/androidTest/java/androidx/privacysandbox/sdkruntime/client/loader/storage/CachedLocalSdkStorageTest.kt
+++ b/privacysandbox/sdkruntime/sdkruntime-client/src/androidTest/java/androidx/privacysandbox/sdkruntime/client/loader/storage/CachedLocalSdkStorageTest.kt
@@ -176,12 +176,9 @@
     private fun disabledLowSpaceModeThreshold(): Long =
         availableBytes() - 10_000_000
 
-    @Suppress("DEPRECATION")
     private fun availableBytes(): Long {
         val path = Environment.getDataDirectory()
         val stat = StatFs(path.path)
-        val blockSize = stat.blockSize.toLong()
-        val availableBlocks = stat.availableBlocks.toLong()
-        return availableBlocks * blockSize
+        return stat.availableBytes
     }
 }
diff --git a/privacysandbox/sdkruntime/sdkruntime-client/src/main/java/androidx/privacysandbox/sdkruntime/client/SdkSandboxManagerCompat.kt b/privacysandbox/sdkruntime/sdkruntime-client/src/main/java/androidx/privacysandbox/sdkruntime/client/SdkSandboxManagerCompat.kt
index e3106df..a3f2e01 100644
--- a/privacysandbox/sdkruntime/sdkruntime-client/src/main/java/androidx/privacysandbox/sdkruntime/client/SdkSandboxManagerCompat.kt
+++ b/privacysandbox/sdkruntime/sdkruntime-client/src/main/java/androidx/privacysandbox/sdkruntime/client/SdkSandboxManagerCompat.kt
@@ -28,6 +28,7 @@
 import androidx.annotation.DoNotInline
 import androidx.annotation.RequiresApi
 import androidx.annotation.RequiresExtension
+import androidx.core.os.BuildCompat
 import androidx.core.os.asOutcomeReceiver
 import androidx.privacysandbox.sdkruntime.client.activity.LocalSdkActivityHandlerRegistry
 import androidx.privacysandbox.sdkruntime.client.activity.LocalSdkActivityStarter
@@ -447,11 +448,7 @@
                 if (instance == null) {
                     val configHolder = LocalSdkConfigsHolder.load(context)
                     val localSdks = LocallyLoadedSdks()
-                    val appOwnedSdkRegistry = if (AdServicesInfo.isDeveloperPreview()) {
-                        PlatformAppOwnedSdkRegistry(context)
-                    } else {
-                        LocalAppOwnedSdkRegistry()
-                    }
+                    val appOwnedSdkRegistry = AppOwnedSdkRegistryFactory.create(context)
                     val controllerFactory = LocalControllerFactory(localSdks, appOwnedSdkRegistry)
                     val sdkLoader = SdkLoader.create(context, controllerFactory)
                     val platformApi = PlatformApiFactory.create(context)
@@ -490,4 +487,17 @@
             }
         }
     }
+
+    private object AppOwnedSdkRegistryFactory {
+        @SuppressLint("NewApi", "ClassVerificationFailure") // For supporting DP Builds
+        fun create(context: Context): AppOwnedSdkRegistry {
+            return if (BuildCompat.AD_SERVICES_EXTENSION_INT >= 8 ||
+                AdServicesInfo.isDeveloperPreview()
+            ) {
+                PlatformAppOwnedSdkRegistry(context)
+            } else {
+                LocalAppOwnedSdkRegistry()
+            }
+        }
+    }
 }
diff --git a/privacysandbox/sdkruntime/sdkruntime-client/src/main/java/androidx/privacysandbox/sdkruntime/client/activity/LocalSdkActivityStarter.kt b/privacysandbox/sdkruntime/sdkruntime-client/src/main/java/androidx/privacysandbox/sdkruntime/client/activity/LocalSdkActivityStarter.kt
index 2eca2e6..10adab0 100644
--- a/privacysandbox/sdkruntime/sdkruntime-client/src/main/java/androidx/privacysandbox/sdkruntime/client/activity/LocalSdkActivityStarter.kt
+++ b/privacysandbox/sdkruntime/sdkruntime-client/src/main/java/androidx/privacysandbox/sdkruntime/client/activity/LocalSdkActivityStarter.kt
@@ -20,7 +20,6 @@
 import android.content.Intent
 import android.os.Bundle
 import android.os.IBinder
-import androidx.core.os.BundleCompat
 
 /**
  * Singleton helper object to start [SdkActivity].
@@ -50,7 +49,7 @@
         val intent = Intent(fromActivity, SdkActivity::class.java)
 
         val params = Bundle()
-        BundleCompat.putBinder(params, EXTRA_ACTIVITY_TOKEN, token)
+        params.putBinder(EXTRA_ACTIVITY_TOKEN, token)
         intent.putExtras(params)
 
         fromActivity.startActivity(intent)
@@ -64,7 +63,6 @@
      * @return token or null if [EXTRA_ACTIVITY_TOKEN] param is missing in [Intent.getExtras]
      */
     fun getTokenFromSdkActivityStartIntent(intent: Intent): IBinder? {
-        val params = intent.extras ?: return null
-        return BundleCompat.getBinder(params, EXTRA_ACTIVITY_TOKEN)
+        return intent.extras?.getBinder(EXTRA_ACTIVITY_TOKEN)
     }
 }
diff --git a/privacysandbox/sdkruntime/sdkruntime-client/src/main/java/androidx/privacysandbox/sdkruntime/client/controller/impl/PlatformAppOwnedSdkRegistry.kt b/privacysandbox/sdkruntime/sdkruntime-client/src/main/java/androidx/privacysandbox/sdkruntime/client/controller/impl/PlatformAppOwnedSdkRegistry.kt
index 1510a87..5d96134 100644
--- a/privacysandbox/sdkruntime/sdkruntime-client/src/main/java/androidx/privacysandbox/sdkruntime/client/controller/impl/PlatformAppOwnedSdkRegistry.kt
+++ b/privacysandbox/sdkruntime/sdkruntime-client/src/main/java/androidx/privacysandbox/sdkruntime/client/controller/impl/PlatformAppOwnedSdkRegistry.kt
@@ -16,20 +16,19 @@
 
 package androidx.privacysandbox.sdkruntime.client.controller.impl
 
-import android.annotation.SuppressLint
 import android.app.sdksandbox.SdkSandboxManager
 import android.content.Context
+import android.os.ext.SdkExtensions
 import androidx.annotation.RequiresApi
+import androidx.annotation.RequiresExtension
 import androidx.privacysandbox.sdkruntime.client.controller.AppOwnedSdkRegistry
-import androidx.privacysandbox.sdkruntime.core.AppOwnedInterfaceConverter
 import androidx.privacysandbox.sdkruntime.core.AppOwnedSdkSandboxInterfaceCompat
 
 /**
- * Implementation for Developer Preview builds backed by [SdkSandboxManager].
- * Using reflection to call public methods / constructors.
- * TODO(b/281397807) Replace reflection for method calls when new prebuilt will be available.
+ * Implementation backed by [SdkSandboxManager].
  */
-@RequiresApi(33) // will be available later via mainline update
+@RequiresApi(33)
+@RequiresExtension(extension = SdkExtensions.AD_SERVICES, version = 8)
 internal class PlatformAppOwnedSdkRegistry(
     context: Context
 ) : AppOwnedSdkRegistry {
@@ -38,48 +37,19 @@
         SdkSandboxManager::class.java
     )
 
-    private val appOwnedSdkInterfaceClass = Class.forName(
-        "android.app.sdksandbox.AppOwnedSdkSandboxInterface"
-    )
-
-    private val registerAppOwnedSdkMethod = sdkSandboxManager.javaClass.getMethod(
-        "registerAppOwnedSdkSandboxInterface",
-        /* parameter1 */ appOwnedSdkInterfaceClass
-    )
-
-    private val unregisterAppOwnedSdkMethod = sdkSandboxManager.javaClass.getMethod(
-        "unregisterAppOwnedSdkSandboxInterface",
-        /* parameter1 */ String::class.java
-    )
-
-    private val getAppOwnedSdksMethod = sdkSandboxManager.javaClass.getMethod(
-        "getAppOwnedSdkSandboxInterfaces"
-    )
-
-    private val converter: AppOwnedInterfaceConverter = AppOwnedInterfaceConverter()
-
-    @SuppressLint("BanUncheckedReflection") // calling public non restricted methods
     override fun registerAppOwnedSdkSandboxInterface(
         appOwnedSdk: AppOwnedSdkSandboxInterfaceCompat
     ) {
-        val platformObj = converter.toPlatform(appOwnedSdk)
-        registerAppOwnedSdkMethod.invoke(
-            sdkSandboxManager,
-            /* parameter1 */ platformObj
-        )
+        val platformObj = appOwnedSdk.toAppOwnedSdkSandboxInterface()
+        sdkSandboxManager.registerAppOwnedSdkSandboxInterface(platformObj)
     }
 
-    @SuppressLint("BanUncheckedReflection") // calling public non restricted methods
     override fun unregisterAppOwnedSdkSandboxInterface(sdkName: String) {
-        unregisterAppOwnedSdkMethod.invoke(
-            sdkSandboxManager,
-            /* parameter1 */ sdkName
-        )
+        sdkSandboxManager.unregisterAppOwnedSdkSandboxInterface(sdkName)
     }
 
-    @SuppressLint("BanUncheckedReflection") // calling public non restricted methods
     override fun getAppOwnedSdkSandboxInterfaces(): List<AppOwnedSdkSandboxInterfaceCompat> {
-        val apiResult = getAppOwnedSdksMethod.invoke(sdkSandboxManager) as List<*>
-        return apiResult.map { converter.toCompat(it!!) }
+        val apiResult = sdkSandboxManager.getAppOwnedSdkSandboxInterfaces()
+        return apiResult.map { AppOwnedSdkSandboxInterfaceCompat(it) }
     }
 }
diff --git a/privacysandbox/sdkruntime/sdkruntime-client/src/main/java/androidx/privacysandbox/sdkruntime/client/loader/storage/CachedLocalSdkStorage.kt b/privacysandbox/sdkruntime/sdkruntime-client/src/main/java/androidx/privacysandbox/sdkruntime/client/loader/storage/CachedLocalSdkStorage.kt
index 6ed726b..41c6c21 100644
--- a/privacysandbox/sdkruntime/sdkruntime-client/src/main/java/androidx/privacysandbox/sdkruntime/client/loader/storage/CachedLocalSdkStorage.kt
+++ b/privacysandbox/sdkruntime/sdkruntime-client/src/main/java/androidx/privacysandbox/sdkruntime/client/loader/storage/CachedLocalSdkStorage.kt
@@ -17,13 +17,9 @@
 package androidx.privacysandbox.sdkruntime.client.loader.storage
 
 import android.content.Context
-import android.os.Build
-import android.os.Build.VERSION_CODES.JELLY_BEAN_MR2
 import android.os.Environment
 import android.os.StatFs
 import android.util.Log
-import androidx.annotation.DoNotInline
-import androidx.annotation.RequiresApi
 import androidx.privacysandbox.sdkruntime.client.config.LocalSdkConfig
 import java.io.File
 
@@ -107,23 +103,7 @@
     private fun availableBytes(): Long {
         val dataDirectory = Environment.getDataDirectory()
         val statFs = StatFs(dataDirectory.path)
-        if (Build.VERSION.SDK_INT >= JELLY_BEAN_MR2) {
-            return Api18Impl.availableBytes(statFs)
-        }
-
-        @Suppress("DEPRECATION")
-        val blockSize = statFs.blockSize.toLong()
-
-        @Suppress("DEPRECATION")
-        val availableBlocks = statFs.availableBlocks.toLong()
-
-        return availableBlocks * blockSize
-    }
-
-    @RequiresApi(JELLY_BEAN_MR2)
-    private object Api18Impl {
-        @DoNotInline
-        fun availableBytes(statFs: StatFs) = statFs.availableBytes
+        return statFs.availableBytes
     }
 
     companion object {
diff --git a/privacysandbox/sdkruntime/sdkruntime-core/src/androidTest/java/androidx/privacysandbox/sdkruntime/core/AppOwnedSdkSandboxInterfaceCompatTest.kt b/privacysandbox/sdkruntime/sdkruntime-core/src/androidTest/java/androidx/privacysandbox/sdkruntime/core/AppOwnedSdkSandboxInterfaceCompatTest.kt
index 968a3c4..1d712a8 100644
--- a/privacysandbox/sdkruntime/sdkruntime-core/src/androidTest/java/androidx/privacysandbox/sdkruntime/core/AppOwnedSdkSandboxInterfaceCompatTest.kt
+++ b/privacysandbox/sdkruntime/sdkruntime-core/src/androidTest/java/androidx/privacysandbox/sdkruntime/core/AppOwnedSdkSandboxInterfaceCompatTest.kt
@@ -16,37 +16,60 @@
 
 package androidx.privacysandbox.sdkruntime.core
 
+import android.app.sdksandbox.AppOwnedSdkSandboxInterface
 import android.os.Binder
+import android.os.Build
+import android.os.ext.SdkExtensions
+import androidx.annotation.RequiresExtension
+import androidx.core.os.BuildCompat
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SdkSuppress
 import androidx.test.filters.SmallTest
 import com.google.common.truth.Truth.assertThat
+import org.junit.Assume.assumeTrue
+import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
 
 @SmallTest
 @RunWith(AndroidJUnit4::class)
-@SdkSuppress(minSdkVersion = 35, codeName = "UpsideDownCakePrivacySandbox")
+// TODO(b/262577044) Remove RequiresExtension after extensions support in @SdkSuppress
+@RequiresExtension(extension = SdkExtensions.AD_SERVICES, version = 8)
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.TIRAMISU)
 class AppOwnedSdkSandboxInterfaceCompatTest {
 
-    @Test
-    fun converterTest() {
-        val converter = AppOwnedInterfaceConverter()
+    @Before
+    fun setUp() {
+        assumeTrue(
+            "Requires AppOwnedInterfacesApi API available",
+            BuildCompat.AD_SERVICES_EXTENSION_INT >= 8
+        )
+    }
 
+    @Test
+    fun toAppOwnedSdkSandboxInterfaceTest() {
         val compatObj = AppOwnedSdkSandboxInterfaceCompat(
             name = "SDK",
             version = 1,
             binder = Binder()
         )
 
-        val platformObj = converter.toPlatform(compatObj)
-        assertThat(platformObj.javaClass.name)
-            .isEqualTo("android.app.sdksandbox.AppOwnedSdkSandboxInterface")
+        val platformObj = compatObj.toAppOwnedSdkSandboxInterface()
 
-        val convertedCompatObj = converter.toCompat(platformObj)
+        assertThat(platformObj.getName()).isEqualTo(compatObj.getName())
+        assertThat(platformObj.getVersion()).isEqualTo(compatObj.getVersion())
+        assertThat(platformObj.getInterface()).isEqualTo(compatObj.getInterface())
+    }
 
-        assertThat(convertedCompatObj.getName()).isEqualTo(compatObj.getName())
-        assertThat(convertedCompatObj.getVersion()).isEqualTo(compatObj.getVersion())
-        assertThat(convertedCompatObj.getInterface()).isEqualTo(compatObj.getInterface())
+    @Test
+    fun fromAppOwnedSdkSandboxInterfaceTest() {
+        val platformObj = AppOwnedSdkSandboxInterface(
+            "SDK", 1, Binder()
+        )
+        val compatObj = AppOwnedSdkSandboxInterfaceCompat(platformObj)
+
+        assertThat(compatObj.getName()).isEqualTo(platformObj.getName())
+        assertThat(compatObj.getVersion()).isEqualTo(platformObj.getVersion())
+        assertThat(compatObj.getInterface()).isEqualTo(platformObj.getInterface())
     }
 }
diff --git a/privacysandbox/sdkruntime/sdkruntime-core/src/androidTest/java/androidx/privacysandbox/sdkruntime/core/controller/SdkSandboxControllerAppOwnedInterfacesTest.kt b/privacysandbox/sdkruntime/sdkruntime-core/src/androidTest/java/androidx/privacysandbox/sdkruntime/core/controller/SdkSandboxControllerAppOwnedInterfacesTest.kt
index a9c750d..c7cc743 100644
--- a/privacysandbox/sdkruntime/sdkruntime-core/src/androidTest/java/androidx/privacysandbox/sdkruntime/core/controller/SdkSandboxControllerAppOwnedInterfacesTest.kt
+++ b/privacysandbox/sdkruntime/sdkruntime-core/src/androidTest/java/androidx/privacysandbox/sdkruntime/core/controller/SdkSandboxControllerAppOwnedInterfacesTest.kt
@@ -23,8 +23,8 @@
 import android.os.Build
 import android.os.ext.SdkExtensions
 import androidx.annotation.RequiresExtension
+import androidx.core.os.BuildCompat
 import androidx.privacysandbox.sdkruntime.core.AdServicesInfo
-import androidx.privacysandbox.sdkruntime.core.AppOwnedInterfaceConverter
 import androidx.privacysandbox.sdkruntime.core.AppOwnedSdkSandboxInterfaceCompat
 import androidx.test.core.app.ApplicationProvider
 import androidx.test.filters.SdkSuppress
@@ -82,8 +82,13 @@
     }
 
     @Test
-    @SdkSuppress(minSdkVersion = 35, codeName = "UpsideDownCakePrivacySandbox")
+    @RequiresExtension(extension = SdkExtensions.AD_SERVICES, version = 8)
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.TIRAMISU)
     fun getAppOwnedSdkSandboxInterfaces_whenApiAvailable_delegateToPlatform() {
+        assumeTrue(
+            "Requires AppOwnedInterfaces API available",
+            isAppOwnedInterfacesApiAvailable()
+        )
         val context = spy(ApplicationProvider.getApplicationContext<Context>())
         val controllerMock = mock(SdkSandboxController::class.java)
         doReturn(controllerMock)
@@ -111,7 +116,7 @@
         AdServicesInfo.isAtLeastV5()
 
     private fun isAppOwnedInterfacesApiAvailable() =
-        AdServicesInfo.isDeveloperPreview()
+        BuildCompat.AD_SERVICES_EXTENSION_INT >= 8 || AdServicesInfo.isDeveloperPreview()
 
     companion object AppOwnedInterfacesApi { // to avoid fail if SdkSandboxController not present
         @SuppressLint("NewApi", "ClassVerificationFailure") // UpsideDownCakePrivacySandbox is UDC
@@ -119,14 +124,8 @@
             controllerMock: SdkSandboxController,
             result: AppOwnedSdkSandboxInterfaceCompat
         ) {
-            val getAppOwnedInterfacesMethod = SdkSandboxController::class.java.getDeclaredMethod(
-                "getAppOwnedSdkSandboxInterfaces"
-            )
-
-            val platformObj = AppOwnedInterfaceConverter().toPlatform(result)
-
-            // Mockito require method call to setup result. Reflection call works same way as normal.
-            `when`(getAppOwnedInterfacesMethod.invoke(controllerMock))
+            val platformObj = result.toAppOwnedSdkSandboxInterface()
+            `when`(controllerMock.getAppOwnedSdkSandboxInterfaces())
                 .thenReturn(listOf(platformObj))
         }
     }
diff --git a/privacysandbox/sdkruntime/sdkruntime-core/src/main/java/androidx/privacysandbox/sdkruntime/core/AppOwnedInterfaceConverter.kt b/privacysandbox/sdkruntime/sdkruntime-core/src/main/java/androidx/privacysandbox/sdkruntime/core/AppOwnedInterfaceConverter.kt
deleted file mode 100644
index 667f0cb..0000000
--- a/privacysandbox/sdkruntime/sdkruntime-core/src/main/java/androidx/privacysandbox/sdkruntime/core/AppOwnedInterfaceConverter.kt
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
- * Copyright 2023 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.privacysandbox.sdkruntime.core
-
-import android.annotation.SuppressLint
-import android.os.IBinder
-import androidx.annotation.RestrictTo
-
-/**
- * Temporary converter of [AppOwnedSdkSandboxInterfaceCompat] to platform
- * AppOwnedSdkSandboxInterface and back.
- *
- * Could be used only with Developer Preview 8 build.
- * Using reflection to call public methods / constructors.
- *
- * TODO(b/281397807) Should be removed as soon as prebuilt with new API will be available.
- */
-@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-class AppOwnedInterfaceConverter {
-
-    private val appOwnedInterfaceClass = Class.forName(
-        "android.app.sdksandbox.AppOwnedSdkSandboxInterface"
-    )
-
-    private val appOwnedInterfaceConstructor = appOwnedInterfaceClass.getConstructor(
-        /* name      */ String::class.java,
-        /* version   */ Long::class.java,
-        /* interface */ IBinder::class.java
-    )
-
-    private val getNameMethod = appOwnedInterfaceClass.getMethod("getName")
-
-    private val getVersionMethod = appOwnedInterfaceClass.getMethod("getVersion")
-
-    private val getInterfaceMethod = appOwnedInterfaceClass.getMethod("getInterface")
-
-    @SuppressLint("BanUncheckedReflection") // calling public non restricted methods
-    fun toCompat(platformObject: Any): AppOwnedSdkSandboxInterfaceCompat {
-        val name = getNameMethod.invoke(platformObject) as String
-        val version = getVersionMethod.invoke(platformObject) as Long
-        val binder = getInterfaceMethod.invoke(platformObject) as IBinder
-        return AppOwnedSdkSandboxInterfaceCompat(name, version, binder)
-    }
-
-    fun toPlatform(compatObject: AppOwnedSdkSandboxInterfaceCompat): Any {
-        return appOwnedInterfaceConstructor.newInstance(
-            compatObject.getName(),
-            compatObject.getVersion(),
-            compatObject.getInterface()
-        )
-    }
-}
diff --git a/privacysandbox/sdkruntime/sdkruntime-core/src/main/java/androidx/privacysandbox/sdkruntime/core/AppOwnedSdkSandboxInterfaceCompat.kt b/privacysandbox/sdkruntime/sdkruntime-core/src/main/java/androidx/privacysandbox/sdkruntime/core/AppOwnedSdkSandboxInterfaceCompat.kt
index 5de206f..c61ad0e 100644
--- a/privacysandbox/sdkruntime/sdkruntime-core/src/main/java/androidx/privacysandbox/sdkruntime/core/AppOwnedSdkSandboxInterfaceCompat.kt
+++ b/privacysandbox/sdkruntime/sdkruntime-core/src/main/java/androidx/privacysandbox/sdkruntime/core/AppOwnedSdkSandboxInterfaceCompat.kt
@@ -16,7 +16,12 @@
 
 package androidx.privacysandbox.sdkruntime.core
 
+import android.app.sdksandbox.AppOwnedSdkSandboxInterface
 import android.os.IBinder
+import android.os.ext.SdkExtensions.AD_SERVICES
+import androidx.annotation.RequiresExtension
+import androidx.annotation.RestrictTo
+import androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP
 
 /**
  * Represents a channel for an SDK in the sandbox process to interact with the app.
@@ -38,6 +43,19 @@
 ) {
 
     /**
+     * Creates AppOwnedSdkSandboxInterfaceCompat from existing [AppOwnedSdkSandboxInterface] object.
+     *
+     * @param appOwnedSdkInterface source platform object.
+     */
+    @RequiresExtension(extension = AD_SERVICES, version = 8)
+    @RestrictTo(LIBRARY_GROUP)
+    constructor(appOwnedSdkInterface: AppOwnedSdkSandboxInterface) : this(
+        name = appOwnedSdkInterface.getName(),
+        version = appOwnedSdkInterface.getVersion(),
+        binder = appOwnedSdkInterface.getInterface(),
+    )
+
+    /**
      * Returns the name used to register the [AppOwnedSdkSandboxInterfaceCompat].
      *
      * App can register only one interface of given name.
@@ -62,4 +80,13 @@
      * the agreed upon interface before using it.
      */
     fun getInterface(): IBinder = binder
+
+    /**
+     * Create [AppOwnedSdkSandboxInterface] from compat object.
+     *
+     * @return Platform AppOwnedSdkSandboxInterface
+     */
+    @RequiresExtension(extension = AD_SERVICES, version = 8)
+    @RestrictTo(LIBRARY_GROUP)
+    fun toAppOwnedSdkSandboxInterface() = AppOwnedSdkSandboxInterface(name, version, binder)
 }
diff --git a/privacysandbox/sdkruntime/sdkruntime-core/src/main/java/androidx/privacysandbox/sdkruntime/core/controller/impl/AppOwnedSdkProvider.kt b/privacysandbox/sdkruntime/sdkruntime-core/src/main/java/androidx/privacysandbox/sdkruntime/core/controller/impl/AppOwnedSdkProvider.kt
index 313db26..11dedfa 100644
--- a/privacysandbox/sdkruntime/sdkruntime-core/src/main/java/androidx/privacysandbox/sdkruntime/core/controller/impl/AppOwnedSdkProvider.kt
+++ b/privacysandbox/sdkruntime/sdkruntime-core/src/main/java/androidx/privacysandbox/sdkruntime/core/controller/impl/AppOwnedSdkProvider.kt
@@ -18,8 +18,10 @@
 
 import android.annotation.SuppressLint
 import android.app.sdksandbox.sdkprovider.SdkSandboxController
+import android.os.ext.SdkExtensions
+import androidx.annotation.RequiresExtension
+import androidx.core.os.BuildCompat
 import androidx.privacysandbox.sdkruntime.core.AdServicesInfo
-import androidx.privacysandbox.sdkruntime.core.AppOwnedInterfaceConverter
 import androidx.privacysandbox.sdkruntime.core.AppOwnedSdkSandboxInterfaceCompat
 
 /**
@@ -46,31 +48,25 @@
     }
 
     /**
-     * Implementation for Developer Preview builds.
-     * Using reflection to call public methods / constructors.
-     * TODO(b/281397807) Replace reflection for method calls when new prebuilt will be available.
+     * Implementation for AdServices V8.
      */
-    private class DeveloperPreviewImpl(
+    @RequiresExtension(extension = SdkExtensions.AD_SERVICES, version = 8)
+    private class ApiAdServicesV8Impl(
         private val controller: SdkSandboxController
     ) : ProviderImpl {
-
-        private val getAppOwnedSdkSandboxInterfacesMethod = controller.javaClass.getMethod(
-            "getAppOwnedSdkSandboxInterfaces",
-        )
-
-        private val converter: AppOwnedInterfaceConverter = AppOwnedInterfaceConverter()
-
-        @SuppressLint("BanUncheckedReflection") // calling public non restricted methods
         override fun getAppOwnedSdkSandboxInterfaces(): List<AppOwnedSdkSandboxInterfaceCompat> {
-            val apiResult = getAppOwnedSdkSandboxInterfacesMethod.invoke(controller) as List<*>
-            return apiResult.map { converter.toCompat(it!!) }
+            val apiResult = controller.getAppOwnedSdkSandboxInterfaces()
+            return apiResult.map { AppOwnedSdkSandboxInterfaceCompat(it) }
         }
     }
 
     companion object {
+        @SuppressLint("NewApi", "ClassVerificationFailure") // For supporting DP Builds
         fun create(controller: SdkSandboxController): AppOwnedSdkProvider {
-            return if (AdServicesInfo.isDeveloperPreview()) {
-                AppOwnedSdkProvider(DeveloperPreviewImpl(controller))
+            return if (BuildCompat.AD_SERVICES_EXTENSION_INT >= 8 ||
+                AdServicesInfo.isDeveloperPreview()
+            ) {
+                AppOwnedSdkProvider(ApiAdServicesV8Impl(controller))
             } else {
                 AppOwnedSdkProvider(NoOpImpl())
             }
diff --git a/privacysandbox/ui/ui-client/src/androidTest/java/androidx/privacysandbox/ui/client/test/SandboxedSdkViewTest.kt b/privacysandbox/ui/ui-client/src/androidTest/java/androidx/privacysandbox/ui/client/test/SandboxedSdkViewTest.kt
index ba44adb..264ce53 100644
--- a/privacysandbox/ui/ui-client/src/androidTest/java/androidx/privacysandbox/ui/client/test/SandboxedSdkViewTest.kt
+++ b/privacysandbox/ui/ui-client/src/androidTest/java/androidx/privacysandbox/ui/client/test/SandboxedSdkViewTest.kt
@@ -53,6 +53,7 @@
 import org.junit.Assert.assertTrue
 import org.junit.Assume.assumeTrue
 import org.junit.Before
+import org.junit.Ignore
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -548,6 +549,7 @@
         assertThat(latch.await(TIMEOUT, TimeUnit.MILLISECONDS)).isFalse()
     }
 
+    @Ignore("b/307829956")
     @Test
     fun requestSizeWithMeasureSpecAtMost_withinParentBounds() {
         view.layoutParams = LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT)
diff --git a/privacysandbox/ui/ui-client/src/main/java/androidx/privacysandbox/ui/client/SdkActivityLaunchers.kt b/privacysandbox/ui/ui-client/src/main/java/androidx/privacysandbox/ui/client/SdkActivityLaunchers.kt
index 5197cfd..478e4a8 100644
--- a/privacysandbox/ui/ui-client/src/main/java/androidx/privacysandbox/ui/client/SdkActivityLaunchers.kt
+++ b/privacysandbox/ui/ui-client/src/main/java/androidx/privacysandbox/ui/client/SdkActivityLaunchers.kt
@@ -23,7 +23,6 @@
 import android.app.Activity
 import android.os.Bundle
 import android.os.IBinder
-import androidx.core.os.BundleCompat
 import androidx.lifecycle.LifecycleOwner
 import androidx.lifecycle.lifecycleScope
 import androidx.privacysandbox.sdkruntime.client.SdkSandboxManagerCompat
@@ -78,7 +77,7 @@
 fun SdkActivityLauncher.toLauncherInfo(): Bundle {
     val binderDelegate = SdkActivityLauncherBinderDelegate(this)
     return Bundle().also { bundle ->
-        BundleCompat.putBinder(bundle, sdkActivityLauncherBinderKey, binderDelegate)
+        bundle.putBinder(sdkActivityLauncherBinderKey, binderDelegate)
     }
 }
 
diff --git a/privacysandbox/ui/ui-provider/src/main/java/androidx/privacysandbox/ui/provider/SdkActivityLauncherFactory.kt b/privacysandbox/ui/ui-provider/src/main/java/androidx/privacysandbox/ui/provider/SdkActivityLauncherFactory.kt
index 409bd36..bb0e41b 100644
--- a/privacysandbox/ui/ui-provider/src/main/java/androidx/privacysandbox/ui/provider/SdkActivityLauncherFactory.kt
+++ b/privacysandbox/ui/ui-provider/src/main/java/androidx/privacysandbox/ui/provider/SdkActivityLauncherFactory.kt
@@ -21,7 +21,6 @@
 
 import android.os.Bundle
 import android.os.IBinder
-import androidx.core.os.BundleCompat
 import androidx.privacysandbox.ui.core.ISdkActivityLauncher
 import androidx.privacysandbox.ui.core.ISdkActivityLauncherCallback
 import androidx.privacysandbox.ui.core.ProtocolConstants.sdkActivityLauncherBinderKey
@@ -46,7 +45,7 @@
     @JvmStatic
     fun fromLauncherInfo(launcherInfo: Bundle): SdkActivityLauncher {
         val remote: ISdkActivityLauncher? = ISdkActivityLauncher.Stub.asInterface(
-            BundleCompat.getBinder(launcherInfo, sdkActivityLauncherBinderKey)
+            launcherInfo.getBinder(sdkActivityLauncherBinderKey)
         )
         requireNotNull(remote) { "Invalid SdkActivityLauncher info bundle." }
         return SdkActivityLauncherProxy(remote)
diff --git a/privacysandbox/ui/ui-tests/src/androidTest/java/androidx/privacysandbox/ui/tests/endtoend/IntegrationTests.kt b/privacysandbox/ui/ui-tests/src/androidTest/java/androidx/privacysandbox/ui/tests/endtoend/IntegrationTests.kt
index 368f62d..ec0dc24 100644
--- a/privacysandbox/ui/ui-tests/src/androidTest/java/androidx/privacysandbox/ui/tests/endtoend/IntegrationTests.kt
+++ b/privacysandbox/ui/ui-tests/src/androidTest/java/androidx/privacysandbox/ui/tests/endtoend/IntegrationTests.kt
@@ -177,6 +177,7 @@
      * Tests that the provider receives Z-order change updates.
      */
     @Test
+    @Ignore("b/302090927")
     fun testZOrderChanged() {
         val adapter = createAdapterAndEstablishSession()
 
@@ -209,6 +210,7 @@
     }
 
     @Test
+    @Ignore("b/302006586")
     fun testHostCanSetZOrderBelowBeforeOpeningSession() {
         // TODO(b/300396631): Skip for backward compat
         assumeTrue(!invokeBackwardsCompatFlow)
diff --git a/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/RecyclerViewSmoothScrollToPositionTest.kt b/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/RecyclerViewSmoothScrollToPositionTest.kt
index c10f74d..2918322 100644
--- a/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/RecyclerViewSmoothScrollToPositionTest.kt
+++ b/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/RecyclerViewSmoothScrollToPositionTest.kt
@@ -27,6 +27,7 @@
 import org.hamcrest.CoreMatchers.`is`
 import org.hamcrest.MatcherAssert.assertThat
 import org.junit.Assert
+import org.junit.Ignore
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -87,6 +88,7 @@
         assertThat(log[2], `is`(RecyclerView.SCROLL_STATE_IDLE))
     }
 
+    @Ignore("b/291327689")
     @Test
     @Throws(Throwable::class)
     fun smoothScroll_whenSmoothScrollerStops_destinationReached() {
diff --git a/security/security-crypto-ktx/api/current.txt b/security/security-crypto-ktx/api/current.txt
index e532c81..60a7508 100644
--- a/security/security-crypto-ktx/api/current.txt
+++ b/security/security-crypto-ktx/api/current.txt
@@ -2,15 +2,15 @@
 package androidx.security.crypto {
 
   public final class EncryptedFileKt {
-    method public static androidx.security.crypto.EncryptedFile EncryptedFile(android.content.Context context, java.io.File file, androidx.security.crypto.MasterKey masterKey, optional androidx.security.crypto.EncryptedFile.FileEncryptionScheme fileEncryptionScheme, optional String? keysetPrefName, optional String? keysetAlias);
+    method @Deprecated public static androidx.security.crypto.EncryptedFile EncryptedFile(android.content.Context context, java.io.File file, androidx.security.crypto.MasterKey masterKey, optional androidx.security.crypto.EncryptedFile.FileEncryptionScheme fileEncryptionScheme, optional String? keysetPrefName, optional String? keysetAlias);
   }
 
   public final class EncryptedSharedPreferencesKt {
-    method public static android.content.SharedPreferences EncryptedSharedPreferences(android.content.Context context, String fileName, androidx.security.crypto.MasterKey masterKey, optional androidx.security.crypto.EncryptedSharedPreferences.PrefKeyEncryptionScheme prefKeyEncryptionScheme, optional androidx.security.crypto.EncryptedSharedPreferences.PrefValueEncryptionScheme prefValueEncryptionScheme);
+    method @Deprecated public static android.content.SharedPreferences EncryptedSharedPreferences(android.content.Context context, String fileName, androidx.security.crypto.MasterKey masterKey, optional androidx.security.crypto.EncryptedSharedPreferences.PrefKeyEncryptionScheme prefKeyEncryptionScheme, optional androidx.security.crypto.EncryptedSharedPreferences.PrefValueEncryptionScheme prefValueEncryptionScheme);
   }
 
   public final class MasterKeyKt {
-    method public static androidx.security.crypto.MasterKey MasterKey(android.content.Context context, optional String keyAlias, optional androidx.security.crypto.MasterKey.KeyScheme keyScheme, optional boolean authenticationRequired, optional int userAuthenticationValidityDurationSeconds, optional boolean requestStrongBoxBacked);
+    method @Deprecated public static androidx.security.crypto.MasterKey MasterKey(android.content.Context context, optional String keyAlias, optional androidx.security.crypto.MasterKey.KeyScheme keyScheme, optional boolean authenticationRequired, optional int userAuthenticationValidityDurationSeconds, optional boolean requestStrongBoxBacked);
   }
 
 }
diff --git a/security/security-crypto-ktx/api/restricted_current.txt b/security/security-crypto-ktx/api/restricted_current.txt
index e532c81..60a7508 100644
--- a/security/security-crypto-ktx/api/restricted_current.txt
+++ b/security/security-crypto-ktx/api/restricted_current.txt
@@ -2,15 +2,15 @@
 package androidx.security.crypto {
 
   public final class EncryptedFileKt {
-    method public static androidx.security.crypto.EncryptedFile EncryptedFile(android.content.Context context, java.io.File file, androidx.security.crypto.MasterKey masterKey, optional androidx.security.crypto.EncryptedFile.FileEncryptionScheme fileEncryptionScheme, optional String? keysetPrefName, optional String? keysetAlias);
+    method @Deprecated public static androidx.security.crypto.EncryptedFile EncryptedFile(android.content.Context context, java.io.File file, androidx.security.crypto.MasterKey masterKey, optional androidx.security.crypto.EncryptedFile.FileEncryptionScheme fileEncryptionScheme, optional String? keysetPrefName, optional String? keysetAlias);
   }
 
   public final class EncryptedSharedPreferencesKt {
-    method public static android.content.SharedPreferences EncryptedSharedPreferences(android.content.Context context, String fileName, androidx.security.crypto.MasterKey masterKey, optional androidx.security.crypto.EncryptedSharedPreferences.PrefKeyEncryptionScheme prefKeyEncryptionScheme, optional androidx.security.crypto.EncryptedSharedPreferences.PrefValueEncryptionScheme prefValueEncryptionScheme);
+    method @Deprecated public static android.content.SharedPreferences EncryptedSharedPreferences(android.content.Context context, String fileName, androidx.security.crypto.MasterKey masterKey, optional androidx.security.crypto.EncryptedSharedPreferences.PrefKeyEncryptionScheme prefKeyEncryptionScheme, optional androidx.security.crypto.EncryptedSharedPreferences.PrefValueEncryptionScheme prefValueEncryptionScheme);
   }
 
   public final class MasterKeyKt {
-    method public static androidx.security.crypto.MasterKey MasterKey(android.content.Context context, optional String keyAlias, optional androidx.security.crypto.MasterKey.KeyScheme keyScheme, optional boolean authenticationRequired, optional int userAuthenticationValidityDurationSeconds, optional boolean requestStrongBoxBacked);
+    method @Deprecated public static androidx.security.crypto.MasterKey MasterKey(android.content.Context context, optional String keyAlias, optional androidx.security.crypto.MasterKey.KeyScheme keyScheme, optional boolean authenticationRequired, optional int userAuthenticationValidityDurationSeconds, optional boolean requestStrongBoxBacked);
   }
 
 }
diff --git a/security/security-crypto-ktx/src/androidTest/java/androidx/security/crypto/KtxTests.kt b/security/security-crypto-ktx/src/androidTest/java/androidx/security/crypto/KtxTests.kt
index 5e2e079..450d4ae 100644
--- a/security/security-crypto-ktx/src/androidTest/java/androidx/security/crypto/KtxTests.kt
+++ b/security/security-crypto-ktx/src/androidTest/java/androidx/security/crypto/KtxTests.kt
@@ -14,6 +14,8 @@
  * limitations under the License.
  */
 
+@file:Suppress("deprecation")
+
 package androidx.security.crypto
 
 import android.content.Context
diff --git a/security/security-crypto-ktx/src/main/java/androidx/security/crypto/EncryptedFile.kt b/security/security-crypto-ktx/src/main/java/androidx/security/crypto/EncryptedFile.kt
index 77767ac..6f75117 100644
--- a/security/security-crypto-ktx/src/main/java/androidx/security/crypto/EncryptedFile.kt
+++ b/security/security-crypto-ktx/src/main/java/androidx/security/crypto/EncryptedFile.kt
@@ -14,6 +14,8 @@
  * limitations under the License.
  */
 
+@file:Suppress("deprecation")
+
 package androidx.security.crypto
 
 import android.annotation.SuppressLint
@@ -34,6 +36,7 @@
  * [EncryptedFile].
  */
 @SuppressLint("StreamFiles")
+@Deprecated("Use java.io.File instead")
 public fun EncryptedFile(
     context: Context,
     file: File,
diff --git a/security/security-crypto-ktx/src/main/java/androidx/security/crypto/EncryptedSharedPreferences.kt b/security/security-crypto-ktx/src/main/java/androidx/security/crypto/EncryptedSharedPreferences.kt
index 7fd9bd8..7db3bd6 100644
--- a/security/security-crypto-ktx/src/main/java/androidx/security/crypto/EncryptedSharedPreferences.kt
+++ b/security/security-crypto-ktx/src/main/java/androidx/security/crypto/EncryptedSharedPreferences.kt
@@ -14,6 +14,8 @@
  * limitations under the License.
  */
 
+@file:Suppress("deprecation")
+
 package androidx.security.crypto
 
 import android.content.Context
@@ -30,6 +32,7 @@
  * @param prefValueEncryptionScheme The scheme to use for encrypting values.
  * @return The SharedPreferences instance that encrypts all data.
  */
+@Deprecated("Use android.content.SharedPreferences instead")
 public fun EncryptedSharedPreferences(
     context: Context,
     fileName: String,
diff --git a/security/security-crypto-ktx/src/main/java/androidx/security/crypto/MasterKey.kt b/security/security-crypto-ktx/src/main/java/androidx/security/crypto/MasterKey.kt
index 306ec76..984affc 100644
--- a/security/security-crypto-ktx/src/main/java/androidx/security/crypto/MasterKey.kt
+++ b/security/security-crypto-ktx/src/main/java/androidx/security/crypto/MasterKey.kt
@@ -14,6 +14,8 @@
  * limitations under the License.
  */
 
+@file:Suppress("deprecation")
+
 package androidx.security.crypto
 
 import android.content.Context
@@ -30,6 +32,7 @@
  * valid for after the user has authenticated. Must be a value > 0.
  * @param requestStrongBoxBacked `true` if the key should be stored in Strong Box, if possible.
  */
+@Deprecated("Use Android Keystore directly instead")
 public fun MasterKey(
     context: Context,
     keyAlias: String = MasterKey.DEFAULT_MASTER_KEY_ALIAS,
diff --git a/security/security-crypto/api/current.txt b/security/security-crypto/api/current.txt
index 8e8b71a..76c90c0 100644
--- a/security/security-crypto/api/current.txt
+++ b/security/security-crypto/api/current.txt
@@ -1,70 +1,70 @@
 // Signature format: 4.0
 package androidx.security.crypto {
 
-  public final class EncryptedFile {
-    method public java.io.FileInputStream openFileInput() throws java.io.FileNotFoundException, java.security.GeneralSecurityException, java.io.IOException;
-    method public java.io.FileOutputStream openFileOutput() throws java.security.GeneralSecurityException, java.io.IOException;
+  @Deprecated public final class EncryptedFile {
+    method @Deprecated public java.io.FileInputStream openFileInput() throws java.io.FileNotFoundException, java.security.GeneralSecurityException, java.io.IOException;
+    method @Deprecated public java.io.FileOutputStream openFileOutput() throws java.security.GeneralSecurityException, java.io.IOException;
   }
 
-  public static final class EncryptedFile.Builder {
-    ctor public EncryptedFile.Builder(android.content.Context, java.io.File, androidx.security.crypto.MasterKey, androidx.security.crypto.EncryptedFile.FileEncryptionScheme);
+  @Deprecated public static final class EncryptedFile.Builder {
+    ctor @Deprecated public EncryptedFile.Builder(android.content.Context, java.io.File, androidx.security.crypto.MasterKey, androidx.security.crypto.EncryptedFile.FileEncryptionScheme);
     ctor @Deprecated public EncryptedFile.Builder(java.io.File, android.content.Context, String, androidx.security.crypto.EncryptedFile.FileEncryptionScheme);
-    method public androidx.security.crypto.EncryptedFile build() throws java.security.GeneralSecurityException, java.io.IOException;
-    method public androidx.security.crypto.EncryptedFile.Builder setKeysetAlias(String);
-    method public androidx.security.crypto.EncryptedFile.Builder setKeysetPrefName(String);
+    method @Deprecated public androidx.security.crypto.EncryptedFile build() throws java.security.GeneralSecurityException, java.io.IOException;
+    method @Deprecated public androidx.security.crypto.EncryptedFile.Builder setKeysetAlias(String);
+    method @Deprecated public androidx.security.crypto.EncryptedFile.Builder setKeysetPrefName(String);
   }
 
-  public enum EncryptedFile.FileEncryptionScheme {
-    enum_constant public static final androidx.security.crypto.EncryptedFile.FileEncryptionScheme AES256_GCM_HKDF_4KB;
+  @Deprecated public enum EncryptedFile.FileEncryptionScheme {
+    enum_constant @Deprecated public static final androidx.security.crypto.EncryptedFile.FileEncryptionScheme AES256_GCM_HKDF_4KB;
   }
 
-  public final class EncryptedSharedPreferences implements android.content.SharedPreferences {
-    method public boolean contains(String?);
-    method public static android.content.SharedPreferences create(android.content.Context, String, androidx.security.crypto.MasterKey, androidx.security.crypto.EncryptedSharedPreferences.PrefKeyEncryptionScheme, androidx.security.crypto.EncryptedSharedPreferences.PrefValueEncryptionScheme) throws java.security.GeneralSecurityException, java.io.IOException;
+  @Deprecated public final class EncryptedSharedPreferences implements android.content.SharedPreferences {
+    method @Deprecated public boolean contains(String?);
+    method @Deprecated public static android.content.SharedPreferences create(android.content.Context, String, androidx.security.crypto.MasterKey, androidx.security.crypto.EncryptedSharedPreferences.PrefKeyEncryptionScheme, androidx.security.crypto.EncryptedSharedPreferences.PrefValueEncryptionScheme) throws java.security.GeneralSecurityException, java.io.IOException;
     method @Deprecated public static android.content.SharedPreferences create(String, String, android.content.Context, androidx.security.crypto.EncryptedSharedPreferences.PrefKeyEncryptionScheme, androidx.security.crypto.EncryptedSharedPreferences.PrefValueEncryptionScheme) throws java.security.GeneralSecurityException, java.io.IOException;
-    method public android.content.SharedPreferences.Editor edit();
-    method public java.util.Map<java.lang.String!,?> getAll();
-    method public boolean getBoolean(String?, boolean);
-    method public float getFloat(String?, float);
-    method public int getInt(String?, int);
-    method public long getLong(String?, long);
-    method public String? getString(String?, String?);
-    method public java.util.Set<java.lang.String!>? getStringSet(String?, java.util.Set<java.lang.String!>?);
-    method public void registerOnSharedPreferenceChangeListener(android.content.SharedPreferences.OnSharedPreferenceChangeListener);
-    method public void unregisterOnSharedPreferenceChangeListener(android.content.SharedPreferences.OnSharedPreferenceChangeListener);
+    method @Deprecated public android.content.SharedPreferences.Editor edit();
+    method @Deprecated public java.util.Map<java.lang.String!,?> getAll();
+    method @Deprecated public boolean getBoolean(String?, boolean);
+    method @Deprecated public float getFloat(String?, float);
+    method @Deprecated public int getInt(String?, int);
+    method @Deprecated public long getLong(String?, long);
+    method @Deprecated public String? getString(String?, String?);
+    method @Deprecated public java.util.Set<java.lang.String!>? getStringSet(String?, java.util.Set<java.lang.String!>?);
+    method @Deprecated public void registerOnSharedPreferenceChangeListener(android.content.SharedPreferences.OnSharedPreferenceChangeListener);
+    method @Deprecated public void unregisterOnSharedPreferenceChangeListener(android.content.SharedPreferences.OnSharedPreferenceChangeListener);
   }
 
-  public enum EncryptedSharedPreferences.PrefKeyEncryptionScheme {
-    enum_constant public static final androidx.security.crypto.EncryptedSharedPreferences.PrefKeyEncryptionScheme AES256_SIV;
+  @Deprecated public enum EncryptedSharedPreferences.PrefKeyEncryptionScheme {
+    enum_constant @Deprecated public static final androidx.security.crypto.EncryptedSharedPreferences.PrefKeyEncryptionScheme AES256_SIV;
   }
 
-  public enum EncryptedSharedPreferences.PrefValueEncryptionScheme {
-    enum_constant public static final androidx.security.crypto.EncryptedSharedPreferences.PrefValueEncryptionScheme AES256_GCM;
+  @Deprecated public enum EncryptedSharedPreferences.PrefValueEncryptionScheme {
+    enum_constant @Deprecated public static final androidx.security.crypto.EncryptedSharedPreferences.PrefValueEncryptionScheme AES256_GCM;
   }
 
-  public final class MasterKey {
-    method public static int getDefaultAuthenticationValidityDurationSeconds();
-    method public int getUserAuthenticationValidityDurationSeconds();
-    method public boolean isKeyStoreBacked();
-    method public boolean isStrongBoxBacked();
-    method public boolean isUserAuthenticationRequired();
-    field public static final int DEFAULT_AES_GCM_MASTER_KEY_SIZE = 256; // 0x100
-    field public static final String DEFAULT_MASTER_KEY_ALIAS = "_androidx_security_master_key_";
+  @Deprecated public final class MasterKey {
+    method @Deprecated public static int getDefaultAuthenticationValidityDurationSeconds();
+    method @Deprecated public int getUserAuthenticationValidityDurationSeconds();
+    method @Deprecated public boolean isKeyStoreBacked();
+    method @Deprecated public boolean isStrongBoxBacked();
+    method @Deprecated public boolean isUserAuthenticationRequired();
+    field @Deprecated public static final int DEFAULT_AES_GCM_MASTER_KEY_SIZE = 256; // 0x100
+    field @Deprecated public static final String DEFAULT_MASTER_KEY_ALIAS = "_androidx_security_master_key_";
   }
 
-  public static final class MasterKey.Builder {
-    ctor public MasterKey.Builder(android.content.Context);
-    ctor public MasterKey.Builder(android.content.Context, String);
-    method public androidx.security.crypto.MasterKey build() throws java.security.GeneralSecurityException, java.io.IOException;
-    method @RequiresApi(android.os.Build.VERSION_CODES.M) public androidx.security.crypto.MasterKey.Builder setKeyGenParameterSpec(android.security.keystore.KeyGenParameterSpec);
-    method public androidx.security.crypto.MasterKey.Builder setKeyScheme(androidx.security.crypto.MasterKey.KeyScheme);
-    method public androidx.security.crypto.MasterKey.Builder setRequestStrongBoxBacked(boolean);
-    method public androidx.security.crypto.MasterKey.Builder setUserAuthenticationRequired(boolean);
-    method public androidx.security.crypto.MasterKey.Builder setUserAuthenticationRequired(boolean, @IntRange(from=1) int);
+  @Deprecated public static final class MasterKey.Builder {
+    ctor @Deprecated public MasterKey.Builder(android.content.Context);
+    ctor @Deprecated public MasterKey.Builder(android.content.Context, String);
+    method @Deprecated public androidx.security.crypto.MasterKey build() throws java.security.GeneralSecurityException, java.io.IOException;
+    method @Deprecated @RequiresApi(android.os.Build.VERSION_CODES.M) public androidx.security.crypto.MasterKey.Builder setKeyGenParameterSpec(android.security.keystore.KeyGenParameterSpec);
+    method @Deprecated public androidx.security.crypto.MasterKey.Builder setKeyScheme(androidx.security.crypto.MasterKey.KeyScheme);
+    method @Deprecated public androidx.security.crypto.MasterKey.Builder setRequestStrongBoxBacked(boolean);
+    method @Deprecated public androidx.security.crypto.MasterKey.Builder setUserAuthenticationRequired(boolean);
+    method @Deprecated public androidx.security.crypto.MasterKey.Builder setUserAuthenticationRequired(boolean, @IntRange(from=1) int);
   }
 
-  public enum MasterKey.KeyScheme {
-    enum_constant public static final androidx.security.crypto.MasterKey.KeyScheme AES256_GCM;
+  @Deprecated public enum MasterKey.KeyScheme {
+    enum_constant @Deprecated public static final androidx.security.crypto.MasterKey.KeyScheme AES256_GCM;
   }
 
   @Deprecated @RequiresApi(android.os.Build.VERSION_CODES.M) public final class MasterKeys {
diff --git a/security/security-crypto/api/restricted_current.txt b/security/security-crypto/api/restricted_current.txt
index 8e8b71a..76c90c0 100644
--- a/security/security-crypto/api/restricted_current.txt
+++ b/security/security-crypto/api/restricted_current.txt
@@ -1,70 +1,70 @@
 // Signature format: 4.0
 package androidx.security.crypto {
 
-  public final class EncryptedFile {
-    method public java.io.FileInputStream openFileInput() throws java.io.FileNotFoundException, java.security.GeneralSecurityException, java.io.IOException;
-    method public java.io.FileOutputStream openFileOutput() throws java.security.GeneralSecurityException, java.io.IOException;
+  @Deprecated public final class EncryptedFile {
+    method @Deprecated public java.io.FileInputStream openFileInput() throws java.io.FileNotFoundException, java.security.GeneralSecurityException, java.io.IOException;
+    method @Deprecated public java.io.FileOutputStream openFileOutput() throws java.security.GeneralSecurityException, java.io.IOException;
   }
 
-  public static final class EncryptedFile.Builder {
-    ctor public EncryptedFile.Builder(android.content.Context, java.io.File, androidx.security.crypto.MasterKey, androidx.security.crypto.EncryptedFile.FileEncryptionScheme);
+  @Deprecated public static final class EncryptedFile.Builder {
+    ctor @Deprecated public EncryptedFile.Builder(android.content.Context, java.io.File, androidx.security.crypto.MasterKey, androidx.security.crypto.EncryptedFile.FileEncryptionScheme);
     ctor @Deprecated public EncryptedFile.Builder(java.io.File, android.content.Context, String, androidx.security.crypto.EncryptedFile.FileEncryptionScheme);
-    method public androidx.security.crypto.EncryptedFile build() throws java.security.GeneralSecurityException, java.io.IOException;
-    method public androidx.security.crypto.EncryptedFile.Builder setKeysetAlias(String);
-    method public androidx.security.crypto.EncryptedFile.Builder setKeysetPrefName(String);
+    method @Deprecated public androidx.security.crypto.EncryptedFile build() throws java.security.GeneralSecurityException, java.io.IOException;
+    method @Deprecated public androidx.security.crypto.EncryptedFile.Builder setKeysetAlias(String);
+    method @Deprecated public androidx.security.crypto.EncryptedFile.Builder setKeysetPrefName(String);
   }
 
-  public enum EncryptedFile.FileEncryptionScheme {
-    enum_constant public static final androidx.security.crypto.EncryptedFile.FileEncryptionScheme AES256_GCM_HKDF_4KB;
+  @Deprecated public enum EncryptedFile.FileEncryptionScheme {
+    enum_constant @Deprecated public static final androidx.security.crypto.EncryptedFile.FileEncryptionScheme AES256_GCM_HKDF_4KB;
   }
 
-  public final class EncryptedSharedPreferences implements android.content.SharedPreferences {
-    method public boolean contains(String?);
-    method public static android.content.SharedPreferences create(android.content.Context, String, androidx.security.crypto.MasterKey, androidx.security.crypto.EncryptedSharedPreferences.PrefKeyEncryptionScheme, androidx.security.crypto.EncryptedSharedPreferences.PrefValueEncryptionScheme) throws java.security.GeneralSecurityException, java.io.IOException;
+  @Deprecated public final class EncryptedSharedPreferences implements android.content.SharedPreferences {
+    method @Deprecated public boolean contains(String?);
+    method @Deprecated public static android.content.SharedPreferences create(android.content.Context, String, androidx.security.crypto.MasterKey, androidx.security.crypto.EncryptedSharedPreferences.PrefKeyEncryptionScheme, androidx.security.crypto.EncryptedSharedPreferences.PrefValueEncryptionScheme) throws java.security.GeneralSecurityException, java.io.IOException;
     method @Deprecated public static android.content.SharedPreferences create(String, String, android.content.Context, androidx.security.crypto.EncryptedSharedPreferences.PrefKeyEncryptionScheme, androidx.security.crypto.EncryptedSharedPreferences.PrefValueEncryptionScheme) throws java.security.GeneralSecurityException, java.io.IOException;
-    method public android.content.SharedPreferences.Editor edit();
-    method public java.util.Map<java.lang.String!,?> getAll();
-    method public boolean getBoolean(String?, boolean);
-    method public float getFloat(String?, float);
-    method public int getInt(String?, int);
-    method public long getLong(String?, long);
-    method public String? getString(String?, String?);
-    method public java.util.Set<java.lang.String!>? getStringSet(String?, java.util.Set<java.lang.String!>?);
-    method public void registerOnSharedPreferenceChangeListener(android.content.SharedPreferences.OnSharedPreferenceChangeListener);
-    method public void unregisterOnSharedPreferenceChangeListener(android.content.SharedPreferences.OnSharedPreferenceChangeListener);
+    method @Deprecated public android.content.SharedPreferences.Editor edit();
+    method @Deprecated public java.util.Map<java.lang.String!,?> getAll();
+    method @Deprecated public boolean getBoolean(String?, boolean);
+    method @Deprecated public float getFloat(String?, float);
+    method @Deprecated public int getInt(String?, int);
+    method @Deprecated public long getLong(String?, long);
+    method @Deprecated public String? getString(String?, String?);
+    method @Deprecated public java.util.Set<java.lang.String!>? getStringSet(String?, java.util.Set<java.lang.String!>?);
+    method @Deprecated public void registerOnSharedPreferenceChangeListener(android.content.SharedPreferences.OnSharedPreferenceChangeListener);
+    method @Deprecated public void unregisterOnSharedPreferenceChangeListener(android.content.SharedPreferences.OnSharedPreferenceChangeListener);
   }
 
-  public enum EncryptedSharedPreferences.PrefKeyEncryptionScheme {
-    enum_constant public static final androidx.security.crypto.EncryptedSharedPreferences.PrefKeyEncryptionScheme AES256_SIV;
+  @Deprecated public enum EncryptedSharedPreferences.PrefKeyEncryptionScheme {
+    enum_constant @Deprecated public static final androidx.security.crypto.EncryptedSharedPreferences.PrefKeyEncryptionScheme AES256_SIV;
   }
 
-  public enum EncryptedSharedPreferences.PrefValueEncryptionScheme {
-    enum_constant public static final androidx.security.crypto.EncryptedSharedPreferences.PrefValueEncryptionScheme AES256_GCM;
+  @Deprecated public enum EncryptedSharedPreferences.PrefValueEncryptionScheme {
+    enum_constant @Deprecated public static final androidx.security.crypto.EncryptedSharedPreferences.PrefValueEncryptionScheme AES256_GCM;
   }
 
-  public final class MasterKey {
-    method public static int getDefaultAuthenticationValidityDurationSeconds();
-    method public int getUserAuthenticationValidityDurationSeconds();
-    method public boolean isKeyStoreBacked();
-    method public boolean isStrongBoxBacked();
-    method public boolean isUserAuthenticationRequired();
-    field public static final int DEFAULT_AES_GCM_MASTER_KEY_SIZE = 256; // 0x100
-    field public static final String DEFAULT_MASTER_KEY_ALIAS = "_androidx_security_master_key_";
+  @Deprecated public final class MasterKey {
+    method @Deprecated public static int getDefaultAuthenticationValidityDurationSeconds();
+    method @Deprecated public int getUserAuthenticationValidityDurationSeconds();
+    method @Deprecated public boolean isKeyStoreBacked();
+    method @Deprecated public boolean isStrongBoxBacked();
+    method @Deprecated public boolean isUserAuthenticationRequired();
+    field @Deprecated public static final int DEFAULT_AES_GCM_MASTER_KEY_SIZE = 256; // 0x100
+    field @Deprecated public static final String DEFAULT_MASTER_KEY_ALIAS = "_androidx_security_master_key_";
   }
 
-  public static final class MasterKey.Builder {
-    ctor public MasterKey.Builder(android.content.Context);
-    ctor public MasterKey.Builder(android.content.Context, String);
-    method public androidx.security.crypto.MasterKey build() throws java.security.GeneralSecurityException, java.io.IOException;
-    method @RequiresApi(android.os.Build.VERSION_CODES.M) public androidx.security.crypto.MasterKey.Builder setKeyGenParameterSpec(android.security.keystore.KeyGenParameterSpec);
-    method public androidx.security.crypto.MasterKey.Builder setKeyScheme(androidx.security.crypto.MasterKey.KeyScheme);
-    method public androidx.security.crypto.MasterKey.Builder setRequestStrongBoxBacked(boolean);
-    method public androidx.security.crypto.MasterKey.Builder setUserAuthenticationRequired(boolean);
-    method public androidx.security.crypto.MasterKey.Builder setUserAuthenticationRequired(boolean, @IntRange(from=1) int);
+  @Deprecated public static final class MasterKey.Builder {
+    ctor @Deprecated public MasterKey.Builder(android.content.Context);
+    ctor @Deprecated public MasterKey.Builder(android.content.Context, String);
+    method @Deprecated public androidx.security.crypto.MasterKey build() throws java.security.GeneralSecurityException, java.io.IOException;
+    method @Deprecated @RequiresApi(android.os.Build.VERSION_CODES.M) public androidx.security.crypto.MasterKey.Builder setKeyGenParameterSpec(android.security.keystore.KeyGenParameterSpec);
+    method @Deprecated public androidx.security.crypto.MasterKey.Builder setKeyScheme(androidx.security.crypto.MasterKey.KeyScheme);
+    method @Deprecated public androidx.security.crypto.MasterKey.Builder setRequestStrongBoxBacked(boolean);
+    method @Deprecated public androidx.security.crypto.MasterKey.Builder setUserAuthenticationRequired(boolean);
+    method @Deprecated public androidx.security.crypto.MasterKey.Builder setUserAuthenticationRequired(boolean, @IntRange(from=1) int);
   }
 
-  public enum MasterKey.KeyScheme {
-    enum_constant public static final androidx.security.crypto.MasterKey.KeyScheme AES256_GCM;
+  @Deprecated public enum MasterKey.KeyScheme {
+    enum_constant @Deprecated public static final androidx.security.crypto.MasterKey.KeyScheme AES256_GCM;
   }
 
   @Deprecated @RequiresApi(android.os.Build.VERSION_CODES.M) public final class MasterKeys {
diff --git a/security/security-crypto/src/androidTest/java/androidx/security/crypto/EncryptedFileTest.java b/security/security-crypto/src/androidTest/java/androidx/security/crypto/EncryptedFileTest.java
index a2d3fd1..844eef4 100644
--- a/security/security-crypto/src/androidTest/java/androidx/security/crypto/EncryptedFileTest.java
+++ b/security/security-crypto/src/androidTest/java/androidx/security/crypto/EncryptedFileTest.java
@@ -16,8 +16,6 @@
 
 package androidx.security.crypto;
 
-import static androidx.security.crypto.MasterKey.KEYSTORE_PATH_URI;
-
 import static org.junit.Assert.assertTrue;
 
 import static java.nio.charset.StandardCharsets.UTF_8;
@@ -54,6 +52,7 @@
 
 @MediumTest
 @RunWith(AndroidJUnit4.class)
+@SuppressWarnings("deprecation")
 public class EncryptedFileTest {
     private static final String KEYSET_ALIAS = "__androidx_security_crypto_encrypted_file_keyset__";
     private static final String PREFS_FILE = "__androidx_security_crypto_encrypted_file_pref__";
@@ -364,7 +363,7 @@
                 .withSharedPref(mContext,
                         KEYSET_ALIAS,
                         PREFS_FILE)
-                .withMasterKeyUri(KEYSTORE_PATH_URI + mMasterKey.getKeyAlias())
+                .withMasterKeyUri(MasterKey.KEYSTORE_PATH_URI + mMasterKey.getKeyAlias())
                 .build().getKeysetHandle();
 
         StreamingAead streamingAead = com.google.crypto.tink.streamingaead.StreamingAeadFactory
diff --git a/security/security-crypto/src/androidTest/java/androidx/security/crypto/EncryptedSharedPreferencesTest.java b/security/security-crypto/src/androidTest/java/androidx/security/crypto/EncryptedSharedPreferencesTest.java
index beaabc0..1065829 100644
--- a/security/security-crypto/src/androidTest/java/androidx/security/crypto/EncryptedSharedPreferencesTest.java
+++ b/security/security-crypto/src/androidTest/java/androidx/security/crypto/EncryptedSharedPreferencesTest.java
@@ -18,8 +18,6 @@
 
 import static android.content.Context.MODE_PRIVATE;
 
-import static androidx.security.crypto.MasterKey.KEYSTORE_PATH_URI;
-
 import static java.nio.charset.StandardCharsets.UTF_8;
 
 import android.content.Context;
@@ -53,6 +51,7 @@
 
 @MediumTest
 @RunWith(AndroidJUnit4.class)
+@SuppressWarnings("deprecation")
 public class EncryptedSharedPreferencesTest {
 
     private Context mContext;
@@ -367,7 +366,7 @@
                 .withKeyTemplate(AesSivKeyManager.aes256SivTemplate())
                 .withSharedPref(mContext,
                         "__androidx_security_crypto_encrypted_prefs_key_keyset__", tinkTestPrefs)
-                .withMasterKeyUri(KEYSTORE_PATH_URI + "_androidx_security_master_key_")
+                .withMasterKeyUri(MasterKey.KEYSTORE_PATH_URI + "_androidx_security_master_key_")
                 .build().getKeysetHandle();
 
         DeterministicAead deterministicAead =
@@ -385,7 +384,7 @@
                 .withKeyTemplate(AesGcmKeyManager.aes256GcmTemplate())
                 .withSharedPref(mContext,
                         "__androidx_security_crypto_encrypted_prefs_value_keyset__", tinkTestPrefs)
-                .withMasterKeyUri(KEYSTORE_PATH_URI + "_androidx_security_master_key_")
+                .withMasterKeyUri(MasterKey.KEYSTORE_PATH_URI + "_androidx_security_master_key_")
                 .build().getKeysetHandle();
         Aead aead = aeadKeysetHandle.getPrimitive(Aead.class);
         String encryptedValue = sharedPreferences.getString(encodedKey, null);
diff --git a/security/security-crypto/src/androidTest/java/androidx/security/crypto/MasterKeySecureTest.java b/security/security-crypto/src/androidTest/java/androidx/security/crypto/MasterKeySecureTest.java
index a624825..e29f1e6 100644
--- a/security/security-crypto/src/androidTest/java/androidx/security/crypto/MasterKeySecureTest.java
+++ b/security/security-crypto/src/androidTest/java/androidx/security/crypto/MasterKeySecureTest.java
@@ -23,7 +23,6 @@
 import android.content.SharedPreferences;
 import android.os.Build;
 
-import androidx.security.crypto.MasterKey.KeyScheme;
 import androidx.test.core.app.ApplicationProvider;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.MediumTest;
@@ -46,6 +45,7 @@
 @MediumTest
 @RunWith(AndroidJUnit4.class)
 @SdkSuppress(minSdkVersion = Build.VERSION_CODES.M)
+@SuppressWarnings("deprecation")
 public class MasterKeySecureTest {
     private static final String PREFS_FILE = "test_shared_prefs";
 
@@ -94,7 +94,7 @@
     public void testCreateKeyWithAuthenicationRequired() throws GeneralSecurityException,
             IOException {
         MasterKey masterKey = new MasterKey.Builder(ApplicationProvider.getApplicationContext())
-                .setKeyScheme(KeyScheme.AES256_GCM)
+                .setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
                 .setUserAuthenticationRequired(true, 10)
                 .build();
         MasterKeyTest.assertKeyExists(masterKey.getKeyAlias());
diff --git a/security/security-crypto/src/androidTest/java/androidx/security/crypto/MasterKeyTest.java b/security/security-crypto/src/androidTest/java/androidx/security/crypto/MasterKeyTest.java
index e38ca0a..773b20c 100644
--- a/security/security-crypto/src/androidTest/java/androidx/security/crypto/MasterKeyTest.java
+++ b/security/security-crypto/src/androidTest/java/androidx/security/crypto/MasterKeyTest.java
@@ -24,7 +24,6 @@
 import android.security.keystore.KeyGenParameterSpec;
 import android.security.keystore.KeyProperties;
 
-import androidx.security.crypto.MasterKey.KeyScheme;
 import androidx.test.core.app.ApplicationProvider;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.MediumTest;
@@ -43,6 +42,7 @@
 @MediumTest
 @SdkSuppress(minSdkVersion = Build.VERSION_CODES.M)
 @RunWith(AndroidJUnit4.class)
+@SuppressWarnings("deprecation")
 public class MasterKeyTest {
     private static final String PREFS_FILE = "test_shared_prefs";
     private static final int KEY_SIZE = 256;
@@ -153,7 +153,7 @@
     public void testCheckIfKeyIsKeyStoreBacked() throws GeneralSecurityException,
             IOException {
         MasterKey masterKey = new MasterKey.Builder(ApplicationProvider.getApplicationContext())
-                .setKeyScheme(KeyScheme.AES256_GCM)
+                .setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
                 .build();
 
         KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
@@ -175,7 +175,7 @@
 
         try {
             MasterKey ignored = new MasterKey.Builder(ApplicationProvider.getApplicationContext())
-                    .setKeyScheme(KeyScheme.AES256_GCM)
+                    .setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
                     .setKeyGenParameterSpec(spec)
                     .build();
             Assert.fail("Could create key with both scheme + KeyGenParameterSpec");
@@ -188,7 +188,7 @@
     public void testCheckGettersAreCallable() throws GeneralSecurityException,
             IOException {
         MasterKey masterKey = new MasterKey.Builder(ApplicationProvider.getApplicationContext())
-                .setKeyScheme(KeyScheme.AES256_GCM)
+                .setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
                 .build();
         Assert.assertFalse(masterKey.isUserAuthenticationRequired());
         Assert.assertFalse(masterKey.isStrongBoxBacked());
diff --git a/security/security-crypto/src/main/java/androidx/security/crypto/EncryptedFile.java b/security/security-crypto/src/main/java/androidx/security/crypto/EncryptedFile.java
index 251ce8a..41c8b7b 100644
--- a/security/security-crypto/src/main/java/androidx/security/crypto/EncryptedFile.java
+++ b/security/security-crypto/src/main/java/androidx/security/crypto/EncryptedFile.java
@@ -16,8 +16,6 @@
 
 package androidx.security.crypto;
 
-import static androidx.security.crypto.MasterKey.KEYSTORE_PATH_URI;
-
 import static java.nio.charset.StandardCharsets.UTF_8;
 
 import android.annotation.SuppressLint;
@@ -85,7 +83,10 @@
  *  // read the encrypted file
  *  FileInputStream encryptedInputStream = encryptedFile.openFileInput();
  * </pre>
+ * @deprecated Use {@link java.io.File} instead.
  */
+@Deprecated
+@SuppressWarnings("deprecation")
 public final class EncryptedFile {
 
     private static final String KEYSET_PREF_NAME =
@@ -111,7 +112,9 @@
 
     /**
      * The encryption scheme to encrypt files.
+     * @deprecated Use {@link java.io.File} instead.
      */
+    @Deprecated
     public enum FileEncryptionScheme {
         /**
          * The file content is encrypted using StreamingAead with AES-GCM, with the file name as
@@ -136,7 +139,9 @@
 
     /**
      * Builder class to configure EncryptedFile
+     * @deprecated Use {@link java.io.File} instead.
      */
+    @Deprecated
     public static final class Builder {
         private static final Object sLock = new Object();
 
@@ -223,7 +228,7 @@
             AndroidKeysetManager.Builder keysetManagerBuilder = new AndroidKeysetManager.Builder()
                     .withKeyTemplate(mFileEncryptionScheme.getKeyTemplate())
                     .withSharedPref(mContext, mKeysetAlias, mKeysetPrefName)
-                    .withMasterKeyUri(KEYSTORE_PATH_URI + mMasterKeyAlias);
+                    .withMasterKeyUri(MasterKey.KEYSTORE_PATH_URI + mMasterKeyAlias);
 
             // Building the keyset manager involves shared pref filesystem operations. To control
             // access to this global state in multi-threaded contexts we need to ensure mutual
diff --git a/security/security-crypto/src/main/java/androidx/security/crypto/EncryptedSharedPreferences.java b/security/security-crypto/src/main/java/androidx/security/crypto/EncryptedSharedPreferences.java
index eabaae5..b5f72f03 100644
--- a/security/security-crypto/src/main/java/androidx/security/crypto/EncryptedSharedPreferences.java
+++ b/security/security-crypto/src/main/java/androidx/security/crypto/EncryptedSharedPreferences.java
@@ -16,8 +16,6 @@
 
 package androidx.security.crypto;
 
-import static androidx.security.crypto.MasterKey.KEYSTORE_PATH_URI;
-
 import static java.nio.charset.StandardCharsets.UTF_8;
 
 import android.content.Context;
@@ -77,7 +75,10 @@
  *  // use the shared preferences and editor as you normally would
  *  SharedPreferences.Editor editor = sharedPreferences.edit();
  * </pre>
+ * @deprecated Use {@link android.content.SharedPreferences} instead.
  */
+@Deprecated
+@SuppressWarnings("deprecation")
 public final class EncryptedSharedPreferences implements SharedPreferences {
 
     private static final String KEY_KEYSET_ALIAS =
@@ -165,12 +166,12 @@
         KeysetHandle daeadKeysetHandle = new AndroidKeysetManager.Builder()
                 .withKeyTemplate(prefKeyEncryptionScheme.getKeyTemplate())
                 .withSharedPref(applicationContext, KEY_KEYSET_ALIAS, fileName)
-                .withMasterKeyUri(KEYSTORE_PATH_URI + masterKeyAlias)
+                .withMasterKeyUri(MasterKey.KEYSTORE_PATH_URI + masterKeyAlias)
                 .build().getKeysetHandle();
         KeysetHandle aeadKeysetHandle = new AndroidKeysetManager.Builder()
                 .withKeyTemplate(prefValueEncryptionScheme.getKeyTemplate())
                 .withSharedPref(applicationContext, VALUE_KEYSET_ALIAS, fileName)
-                .withMasterKeyUri(KEYSTORE_PATH_URI + masterKeyAlias)
+                .withMasterKeyUri(MasterKey.KEYSTORE_PATH_URI + masterKeyAlias)
                 .build().getKeysetHandle();
 
         DeterministicAead daead = daeadKeysetHandle.getPrimitive(DeterministicAead.class);
@@ -183,7 +184,9 @@
 
     /**
      * The encryption scheme to encrypt keys.
+     * @deprecated Use {@link android.content.SharedPreferences} instead.
      */
+    @Deprecated
     public enum PrefKeyEncryptionScheme {
         /**
          * Pref keys are encrypted deterministically with AES256-SIV-CMAC (RFC 5297).
@@ -207,7 +210,9 @@
 
     /**
      * The encryption scheme to encrypt values.
+     * @deprecated Use {@link android.content.SharedPreferences} instead.
      */
+    @Deprecated
     public enum PrefValueEncryptionScheme {
         /**
          * Pref values are encrypted with AES256-GCM. The associated data is the encrypted pref key.
diff --git a/security/security-crypto/src/main/java/androidx/security/crypto/MasterKey.java b/security/security-crypto/src/main/java/androidx/security/crypto/MasterKey.java
index b1663fb..4c1946c 100644
--- a/security/security-crypto/src/main/java/androidx/security/crypto/MasterKey.java
+++ b/security/security-crypto/src/main/java/androidx/security/crypto/MasterKey.java
@@ -44,18 +44,24 @@
  *
  * <p>On Android M (API 23) and above, this is class references a key that's stored in the
  * Android Keystore. On Android L (API 21, 22), there isn't a master key.
+ * @deprecated Use {@link javax.crypto.KeyGenerator} with AndroidKeyStore instance instead.
  */
+@Deprecated
 public final class MasterKey {
     static final String KEYSTORE_PATH_URI = "android-keystore://";
 
     /**
      * The default master key alias.
+     * @deprecated Use {@link javax.crypto.KeyGenerator} with AndroidKeyStore instance instead.
      */
+    @Deprecated
     public static final String DEFAULT_MASTER_KEY_ALIAS = "_androidx_security_master_key_";
 
     /**
      * The default and recommended size for the master key.
+     * @deprecated Use {@link javax.crypto.KeyGenerator} with AndroidKeyStore instance instead.
      */
+    @Deprecated
     public static final int DEFAULT_AES_GCM_MASTER_KEY_SIZE = 256;
 
     private static final int DEFAULT_AUTHENTICATION_VALIDITY_DURATION_SECONDS = 5 * 60;
@@ -67,7 +73,9 @@
 
     /**
      * Algorithm/Cipher choices used for the master key.
+     * @deprecated Use {@link javax.crypto.KeyGenerator} with AndroidKeyStore instance instead.
      */
+    @Deprecated
     public enum KeyScheme {
         AES256_GCM
     }
@@ -166,7 +174,9 @@
 
     /**
      * Builder for generating a {@link MasterKey}.
+     * @deprecated Use {@link javax.crypto.KeyGenerator} with AndroidKeyStore instance instead.
      */
+    @Deprecated
     public static final class Builder {
         @NonNull
         final String mKeyAlias;
diff --git a/security/security-crypto/src/main/java/androidx/security/crypto/MasterKeys.java b/security/security-crypto/src/main/java/androidx/security/crypto/MasterKeys.java
index f3ce65d..bd7edf2 100644
--- a/security/security-crypto/src/main/java/androidx/security/crypto/MasterKeys.java
+++ b/security/security-crypto/src/main/java/androidx/security/crypto/MasterKeys.java
@@ -37,7 +37,7 @@
  *
  * <p>The master keys are used to encrypt data encryption keys for encrypting files and preferences.
  *
- * @deprecated Use {@link MasterKey.Builder} to work with master keys.
+ * @deprecated Use {@link javax.crypto.KeyGenerator} with AndroidKeyStore instance instead.
  */
 @Deprecated
 @RequiresApi(Build.VERSION_CODES.M)
@@ -50,7 +50,11 @@
 
     private static final String ANDROID_KEYSTORE = "AndroidKeyStore";
 
+    /**
+     * @deprecated Use {@link android.security.keystore.KeyGenParameterSpec.Builder} instead.
+     */
     @NonNull
+    @Deprecated
     public static final KeyGenParameterSpec AES256_GCM_SPEC =
             createAES256GCMKeyGenParameterSpec(MASTER_KEY_ALIAS);
 
@@ -89,7 +93,6 @@
      * @return The key alias for the master key
      */
     @NonNull
-    @SuppressWarnings("deprecation")
     public static String getOrCreate(
             @NonNull KeyGenParameterSpec keyGenParameterSpec)
             throws GeneralSecurityException, IOException {
diff --git a/wear/compose/compose-material/api/current.txt b/wear/compose/compose-material/api/current.txt
index 2eac14a..03bbbc7 100644
--- a/wear/compose/compose-material/api/current.txt
+++ b/wear/compose/compose-material/api/current.txt
@@ -709,22 +709,6 @@
     enum_constant @Deprecated public static final androidx.wear.compose.material.SwipeToDismissValue Dismissed;
   }
 
-  @SuppressCompatibility @androidx.wear.compose.material.ExperimentalWearMaterialApi public final class SwipeToRevealAction {
-    ctor public SwipeToRevealAction(kotlin.jvm.functions.Function0<kotlin.Unit>? icon, kotlin.jvm.functions.Function0<kotlin.Unit>? label, androidx.compose.ui.Modifier modifier, int actionType, androidx.compose.foundation.interaction.MutableInteractionSource interactionSource, kotlin.jvm.functions.Function0<kotlin.Unit> onClick);
-    method public int getActionType();
-    method public kotlin.jvm.functions.Function0<kotlin.Unit>? getIcon();
-    method public androidx.compose.foundation.interaction.MutableInteractionSource getInteractionSource();
-    method public kotlin.jvm.functions.Function0<kotlin.Unit>? getLabel();
-    method public androidx.compose.ui.Modifier getModifier();
-    method public kotlin.jvm.functions.Function0<kotlin.Unit> getOnClick();
-    property public final int actionType;
-    property public final kotlin.jvm.functions.Function0<kotlin.Unit>? icon;
-    property public final androidx.compose.foundation.interaction.MutableInteractionSource interactionSource;
-    property public final kotlin.jvm.functions.Function0<kotlin.Unit>? label;
-    property public final androidx.compose.ui.Modifier modifier;
-    property public final kotlin.jvm.functions.Function0<kotlin.Unit> onClick;
-  }
-
   @SuppressCompatibility @androidx.wear.compose.material.ExperimentalWearMaterialApi public final class SwipeToRevealActionColors {
     ctor public SwipeToRevealActionColors(long primaryActionBackgroundColor, long primaryActionContentColor, long secondaryActionBackgroundColor, long secondaryActionContentColor, long undoActionBackgroundColor, long undoActionContentColor);
     method public long getPrimaryActionBackgroundColor();
@@ -746,9 +730,6 @@
     method public androidx.compose.foundation.shape.RoundedCornerShape getCardActionShape();
     method public androidx.compose.ui.graphics.vector.ImageVector getDelete();
     method public androidx.compose.ui.graphics.vector.ImageVector getMoreOptions();
-    method @androidx.compose.runtime.Composable public androidx.wear.compose.material.SwipeToRevealAction primaryAction(kotlin.jvm.functions.Function0<kotlin.Unit> icon, kotlin.jvm.functions.Function0<kotlin.Unit> label, optional androidx.compose.ui.Modifier modifier, optional int actionType, optional androidx.compose.foundation.interaction.MutableInteractionSource interactionSource, optional kotlin.jvm.functions.Function0<kotlin.Unit> onClick);
-    method @androidx.compose.runtime.Composable public androidx.wear.compose.material.SwipeToRevealAction secondaryAction(kotlin.jvm.functions.Function0<kotlin.Unit> icon, optional androidx.compose.ui.Modifier modifier, optional int actionType, optional androidx.compose.foundation.interaction.MutableInteractionSource interactionSource, optional kotlin.jvm.functions.Function0<kotlin.Unit> onClick);
-    method @androidx.compose.runtime.Composable public androidx.wear.compose.material.SwipeToRevealAction undoAction(kotlin.jvm.functions.Function0<kotlin.Unit> label, optional androidx.compose.ui.Modifier modifier, optional int actionType, optional androidx.compose.foundation.interaction.MutableInteractionSource interactionSource, optional kotlin.jvm.functions.Function0<kotlin.Unit> onClick);
     property public final androidx.compose.foundation.shape.RoundedCornerShape CardActionShape;
     property public final androidx.compose.ui.graphics.vector.ImageVector Delete;
     property public final androidx.compose.ui.graphics.vector.ImageVector MoreOptions;
@@ -756,8 +737,11 @@
   }
 
   public final class SwipeToRevealKt {
-    method @SuppressCompatibility @androidx.compose.runtime.Composable @androidx.wear.compose.material.ExperimentalWearMaterialApi public static void SwipeToRevealCard(androidx.wear.compose.material.SwipeToRevealAction primaryAction, androidx.wear.compose.foundation.RevealState revealState, optional androidx.compose.ui.Modifier modifier, optional androidx.wear.compose.material.SwipeToRevealAction? secondaryAction, optional androidx.wear.compose.material.SwipeToRevealAction? undoPrimaryAction, optional androidx.wear.compose.material.SwipeToRevealAction? undoSecondaryAction, optional androidx.wear.compose.material.SwipeToRevealActionColors colors, optional androidx.compose.ui.graphics.Shape shape, kotlin.jvm.functions.Function0<kotlin.Unit> content);
-    method @SuppressCompatibility @androidx.compose.runtime.Composable @androidx.wear.compose.material.ExperimentalWearMaterialApi public static void SwipeToRevealChip(androidx.wear.compose.material.SwipeToRevealAction primaryAction, androidx.wear.compose.foundation.RevealState revealState, optional androidx.compose.ui.Modifier modifier, optional androidx.wear.compose.material.SwipeToRevealAction? secondaryAction, optional androidx.wear.compose.material.SwipeToRevealAction? undoPrimaryAction, optional androidx.wear.compose.material.SwipeToRevealAction? undoSecondaryAction, optional androidx.wear.compose.material.SwipeToRevealActionColors colors, optional androidx.compose.ui.graphics.Shape shape, kotlin.jvm.functions.Function0<kotlin.Unit> content);
+    method @SuppressCompatibility @androidx.compose.runtime.Composable @androidx.wear.compose.material.ExperimentalWearMaterialApi public static void SwipeToRevealCard(kotlin.jvm.functions.Function1<? super androidx.wear.compose.foundation.RevealScope,kotlin.Unit> primaryAction, androidx.wear.compose.foundation.RevealState revealState, kotlin.jvm.functions.Function0<kotlin.Unit> onFullSwipe, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function1<? super androidx.wear.compose.foundation.RevealScope,kotlin.Unit>? secondaryAction, optional kotlin.jvm.functions.Function1<? super androidx.wear.compose.foundation.RevealScope,kotlin.Unit>? undoPrimaryAction, optional kotlin.jvm.functions.Function1<? super androidx.wear.compose.foundation.RevealScope,kotlin.Unit>? undoSecondaryAction, optional androidx.wear.compose.material.SwipeToRevealActionColors colors, optional androidx.compose.ui.graphics.Shape shape, kotlin.jvm.functions.Function0<kotlin.Unit> content);
+    method @SuppressCompatibility @androidx.compose.runtime.Composable @androidx.wear.compose.material.ExperimentalWearMaterialApi public static void SwipeToRevealChip(kotlin.jvm.functions.Function1<? super androidx.wear.compose.foundation.RevealScope,kotlin.Unit> primaryAction, androidx.wear.compose.foundation.RevealState revealState, kotlin.jvm.functions.Function0<kotlin.Unit> onFullSwipe, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function1<? super androidx.wear.compose.foundation.RevealScope,kotlin.Unit>? secondaryAction, optional kotlin.jvm.functions.Function1<? super androidx.wear.compose.foundation.RevealScope,kotlin.Unit>? undoPrimaryAction, optional kotlin.jvm.functions.Function1<? super androidx.wear.compose.foundation.RevealScope,kotlin.Unit>? undoSecondaryAction, optional androidx.wear.compose.material.SwipeToRevealActionColors colors, optional androidx.compose.ui.graphics.Shape shape, kotlin.jvm.functions.Function0<kotlin.Unit> content);
+    method @SuppressCompatibility @androidx.compose.runtime.Composable @androidx.wear.compose.material.ExperimentalWearMaterialApi public static void SwipeToRevealPrimaryAction(androidx.wear.compose.foundation.RevealScope, androidx.wear.compose.foundation.RevealState revealState, kotlin.jvm.functions.Function0<kotlin.Unit> onClick, kotlin.jvm.functions.Function0<kotlin.Unit> icon, kotlin.jvm.functions.Function0<kotlin.Unit> label, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.foundation.interaction.MutableInteractionSource interactionSource);
+    method @SuppressCompatibility @androidx.compose.runtime.Composable @androidx.wear.compose.material.ExperimentalWearMaterialApi public static void SwipeToRevealSecondaryAction(androidx.wear.compose.foundation.RevealScope, androidx.wear.compose.foundation.RevealState revealState, kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.foundation.interaction.MutableInteractionSource interactionSource, kotlin.jvm.functions.Function0<kotlin.Unit> content);
+    method @SuppressCompatibility @androidx.compose.runtime.Composable @androidx.wear.compose.material.ExperimentalWearMaterialApi public static void SwipeToRevealUndoAction(androidx.wear.compose.foundation.RevealScope, androidx.wear.compose.foundation.RevealState revealState, kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.foundation.interaction.MutableInteractionSource interactionSource, optional kotlin.jvm.functions.Function0<kotlin.Unit>? icon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? label);
   }
 
   @SuppressCompatibility @androidx.wear.compose.material.ExperimentalWearMaterialApi public final class SwipeableDefaults {
diff --git a/wear/compose/compose-material/api/restricted_current.txt b/wear/compose/compose-material/api/restricted_current.txt
index 2eac14a..03bbbc7 100644
--- a/wear/compose/compose-material/api/restricted_current.txt
+++ b/wear/compose/compose-material/api/restricted_current.txt
@@ -709,22 +709,6 @@
     enum_constant @Deprecated public static final androidx.wear.compose.material.SwipeToDismissValue Dismissed;
   }
 
-  @SuppressCompatibility @androidx.wear.compose.material.ExperimentalWearMaterialApi public final class SwipeToRevealAction {
-    ctor public SwipeToRevealAction(kotlin.jvm.functions.Function0<kotlin.Unit>? icon, kotlin.jvm.functions.Function0<kotlin.Unit>? label, androidx.compose.ui.Modifier modifier, int actionType, androidx.compose.foundation.interaction.MutableInteractionSource interactionSource, kotlin.jvm.functions.Function0<kotlin.Unit> onClick);
-    method public int getActionType();
-    method public kotlin.jvm.functions.Function0<kotlin.Unit>? getIcon();
-    method public androidx.compose.foundation.interaction.MutableInteractionSource getInteractionSource();
-    method public kotlin.jvm.functions.Function0<kotlin.Unit>? getLabel();
-    method public androidx.compose.ui.Modifier getModifier();
-    method public kotlin.jvm.functions.Function0<kotlin.Unit> getOnClick();
-    property public final int actionType;
-    property public final kotlin.jvm.functions.Function0<kotlin.Unit>? icon;
-    property public final androidx.compose.foundation.interaction.MutableInteractionSource interactionSource;
-    property public final kotlin.jvm.functions.Function0<kotlin.Unit>? label;
-    property public final androidx.compose.ui.Modifier modifier;
-    property public final kotlin.jvm.functions.Function0<kotlin.Unit> onClick;
-  }
-
   @SuppressCompatibility @androidx.wear.compose.material.ExperimentalWearMaterialApi public final class SwipeToRevealActionColors {
     ctor public SwipeToRevealActionColors(long primaryActionBackgroundColor, long primaryActionContentColor, long secondaryActionBackgroundColor, long secondaryActionContentColor, long undoActionBackgroundColor, long undoActionContentColor);
     method public long getPrimaryActionBackgroundColor();
@@ -746,9 +730,6 @@
     method public androidx.compose.foundation.shape.RoundedCornerShape getCardActionShape();
     method public androidx.compose.ui.graphics.vector.ImageVector getDelete();
     method public androidx.compose.ui.graphics.vector.ImageVector getMoreOptions();
-    method @androidx.compose.runtime.Composable public androidx.wear.compose.material.SwipeToRevealAction primaryAction(kotlin.jvm.functions.Function0<kotlin.Unit> icon, kotlin.jvm.functions.Function0<kotlin.Unit> label, optional androidx.compose.ui.Modifier modifier, optional int actionType, optional androidx.compose.foundation.interaction.MutableInteractionSource interactionSource, optional kotlin.jvm.functions.Function0<kotlin.Unit> onClick);
-    method @androidx.compose.runtime.Composable public androidx.wear.compose.material.SwipeToRevealAction secondaryAction(kotlin.jvm.functions.Function0<kotlin.Unit> icon, optional androidx.compose.ui.Modifier modifier, optional int actionType, optional androidx.compose.foundation.interaction.MutableInteractionSource interactionSource, optional kotlin.jvm.functions.Function0<kotlin.Unit> onClick);
-    method @androidx.compose.runtime.Composable public androidx.wear.compose.material.SwipeToRevealAction undoAction(kotlin.jvm.functions.Function0<kotlin.Unit> label, optional androidx.compose.ui.Modifier modifier, optional int actionType, optional androidx.compose.foundation.interaction.MutableInteractionSource interactionSource, optional kotlin.jvm.functions.Function0<kotlin.Unit> onClick);
     property public final androidx.compose.foundation.shape.RoundedCornerShape CardActionShape;
     property public final androidx.compose.ui.graphics.vector.ImageVector Delete;
     property public final androidx.compose.ui.graphics.vector.ImageVector MoreOptions;
@@ -756,8 +737,11 @@
   }
 
   public final class SwipeToRevealKt {
-    method @SuppressCompatibility @androidx.compose.runtime.Composable @androidx.wear.compose.material.ExperimentalWearMaterialApi public static void SwipeToRevealCard(androidx.wear.compose.material.SwipeToRevealAction primaryAction, androidx.wear.compose.foundation.RevealState revealState, optional androidx.compose.ui.Modifier modifier, optional androidx.wear.compose.material.SwipeToRevealAction? secondaryAction, optional androidx.wear.compose.material.SwipeToRevealAction? undoPrimaryAction, optional androidx.wear.compose.material.SwipeToRevealAction? undoSecondaryAction, optional androidx.wear.compose.material.SwipeToRevealActionColors colors, optional androidx.compose.ui.graphics.Shape shape, kotlin.jvm.functions.Function0<kotlin.Unit> content);
-    method @SuppressCompatibility @androidx.compose.runtime.Composable @androidx.wear.compose.material.ExperimentalWearMaterialApi public static void SwipeToRevealChip(androidx.wear.compose.material.SwipeToRevealAction primaryAction, androidx.wear.compose.foundation.RevealState revealState, optional androidx.compose.ui.Modifier modifier, optional androidx.wear.compose.material.SwipeToRevealAction? secondaryAction, optional androidx.wear.compose.material.SwipeToRevealAction? undoPrimaryAction, optional androidx.wear.compose.material.SwipeToRevealAction? undoSecondaryAction, optional androidx.wear.compose.material.SwipeToRevealActionColors colors, optional androidx.compose.ui.graphics.Shape shape, kotlin.jvm.functions.Function0<kotlin.Unit> content);
+    method @SuppressCompatibility @androidx.compose.runtime.Composable @androidx.wear.compose.material.ExperimentalWearMaterialApi public static void SwipeToRevealCard(kotlin.jvm.functions.Function1<? super androidx.wear.compose.foundation.RevealScope,kotlin.Unit> primaryAction, androidx.wear.compose.foundation.RevealState revealState, kotlin.jvm.functions.Function0<kotlin.Unit> onFullSwipe, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function1<? super androidx.wear.compose.foundation.RevealScope,kotlin.Unit>? secondaryAction, optional kotlin.jvm.functions.Function1<? super androidx.wear.compose.foundation.RevealScope,kotlin.Unit>? undoPrimaryAction, optional kotlin.jvm.functions.Function1<? super androidx.wear.compose.foundation.RevealScope,kotlin.Unit>? undoSecondaryAction, optional androidx.wear.compose.material.SwipeToRevealActionColors colors, optional androidx.compose.ui.graphics.Shape shape, kotlin.jvm.functions.Function0<kotlin.Unit> content);
+    method @SuppressCompatibility @androidx.compose.runtime.Composable @androidx.wear.compose.material.ExperimentalWearMaterialApi public static void SwipeToRevealChip(kotlin.jvm.functions.Function1<? super androidx.wear.compose.foundation.RevealScope,kotlin.Unit> primaryAction, androidx.wear.compose.foundation.RevealState revealState, kotlin.jvm.functions.Function0<kotlin.Unit> onFullSwipe, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function1<? super androidx.wear.compose.foundation.RevealScope,kotlin.Unit>? secondaryAction, optional kotlin.jvm.functions.Function1<? super androidx.wear.compose.foundation.RevealScope,kotlin.Unit>? undoPrimaryAction, optional kotlin.jvm.functions.Function1<? super androidx.wear.compose.foundation.RevealScope,kotlin.Unit>? undoSecondaryAction, optional androidx.wear.compose.material.SwipeToRevealActionColors colors, optional androidx.compose.ui.graphics.Shape shape, kotlin.jvm.functions.Function0<kotlin.Unit> content);
+    method @SuppressCompatibility @androidx.compose.runtime.Composable @androidx.wear.compose.material.ExperimentalWearMaterialApi public static void SwipeToRevealPrimaryAction(androidx.wear.compose.foundation.RevealScope, androidx.wear.compose.foundation.RevealState revealState, kotlin.jvm.functions.Function0<kotlin.Unit> onClick, kotlin.jvm.functions.Function0<kotlin.Unit> icon, kotlin.jvm.functions.Function0<kotlin.Unit> label, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.foundation.interaction.MutableInteractionSource interactionSource);
+    method @SuppressCompatibility @androidx.compose.runtime.Composable @androidx.wear.compose.material.ExperimentalWearMaterialApi public static void SwipeToRevealSecondaryAction(androidx.wear.compose.foundation.RevealScope, androidx.wear.compose.foundation.RevealState revealState, kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.foundation.interaction.MutableInteractionSource interactionSource, kotlin.jvm.functions.Function0<kotlin.Unit> content);
+    method @SuppressCompatibility @androidx.compose.runtime.Composable @androidx.wear.compose.material.ExperimentalWearMaterialApi public static void SwipeToRevealUndoAction(androidx.wear.compose.foundation.RevealScope, androidx.wear.compose.foundation.RevealState revealState, kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.foundation.interaction.MutableInteractionSource interactionSource, optional kotlin.jvm.functions.Function0<kotlin.Unit>? icon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? label);
   }
 
   @SuppressCompatibility @androidx.wear.compose.material.ExperimentalWearMaterialApi public final class SwipeableDefaults {
diff --git a/wear/compose/compose-material/samples/src/main/java/androidx/wear/compose/material/samples/SwipeToRevealSample.kt b/wear/compose/compose-material/samples/src/main/java/androidx/wear/compose/material/samples/SwipeToRevealSample.kt
index d39af4a..7e565fd 100644
--- a/wear/compose/compose-material/samples/src/main/java/androidx/wear/compose/material/samples/SwipeToRevealSample.kt
+++ b/wear/compose/compose-material/samples/src/main/java/androidx/wear/compose/material/samples/SwipeToRevealSample.kt
@@ -40,14 +40,18 @@
 import androidx.wear.compose.material.SwipeToRevealCard
 import androidx.wear.compose.material.SwipeToRevealChip
 import androidx.wear.compose.material.SwipeToRevealDefaults
+import androidx.wear.compose.material.SwipeToRevealPrimaryAction
+import androidx.wear.compose.material.SwipeToRevealSecondaryAction
+import androidx.wear.compose.material.SwipeToRevealUndoAction
 import androidx.wear.compose.material.Text
 
 @OptIn(ExperimentalWearMaterialApi::class, ExperimentalWearFoundationApi::class)
 @Composable
 @Sampled
 fun SwipeToRevealChipSample(swipeToDismissBoxState: SwipeToDismissBoxState) {
+    val revealState = rememberRevealState()
     SwipeToRevealChip(
-        revealState = rememberRevealState(),
+        revealState = revealState,
         modifier = Modifier
             .fillMaxWidth()
             // Use edgeSwipeToDismiss to allow SwipeToDismissBox to capture swipe events
@@ -65,23 +69,37 @@
                     }
                 )
             },
-        primaryAction = SwipeToRevealDefaults.primaryAction(
-            icon = { Icon(SwipeToRevealDefaults.Delete, "Delete") },
-            label = { Text("Delete") },
-            onClick = { /* Add the click handler here */ }
-        ),
-        secondaryAction = SwipeToRevealDefaults.secondaryAction(
-            icon = { Icon(SwipeToRevealDefaults.MoreOptions, "More Options") },
-            onClick = { /* Add the click handler here */ }
-        ),
-        undoPrimaryAction = SwipeToRevealDefaults.undoAction(
-            label = { Text("Undo") },
-            onClick = { /* Add the undo handler for primary action */ }
-        ),
-        undoSecondaryAction = SwipeToRevealDefaults.undoAction(
-            label = { Text("Undo") },
-            onClick = { /* Add the undo handler for secondary action */ }
-        )
+        primaryAction = {
+            SwipeToRevealPrimaryAction(
+                revealState = revealState,
+                icon = { Icon(SwipeToRevealDefaults.Delete, "Delete") },
+                label = { Text("Delete") },
+                onClick = { /* Add the click handler here */ }
+            )
+        },
+        secondaryAction = {
+            SwipeToRevealSecondaryAction(
+                revealState = revealState,
+                onClick = { /* Add the click handler here */ }
+            ) {
+                Icon(SwipeToRevealDefaults.MoreOptions, "More Options")
+            }
+        },
+        undoPrimaryAction = {
+            SwipeToRevealUndoAction(
+                revealState = revealState,
+                label = { Text("Undo") },
+                onClick = { /* Add the undo handler for primary action */ }
+            )
+        },
+        undoSecondaryAction = {
+            SwipeToRevealUndoAction(
+                revealState = revealState,
+                label = { Text("Undo") },
+                onClick = { /* Add the undo handler for secondary action */ }
+            )
+        },
+        onFullSwipe = { /* Add the full swipe handler here */ }
     ) {
         Chip(
             modifier = Modifier.fillMaxWidth(),
@@ -98,8 +116,9 @@
 @Composable
 @Sampled
 fun SwipeToRevealCardSample(swipeToDismissBoxState: SwipeToDismissBoxState) {
+    val revealState = rememberRevealState()
     SwipeToRevealCard(
-        revealState = rememberRevealState(),
+        revealState = revealState,
         modifier = Modifier
             .fillMaxWidth()
             // Use edgeSwipeToDismiss to allow SwipeToDismissBox to capture swipe events
@@ -117,23 +136,37 @@
                     }
                 )
             },
-        primaryAction = SwipeToRevealDefaults.primaryAction(
-            icon = { Icon(SwipeToRevealDefaults.Delete, "Delete") },
-            label = { Text("Delete") },
-            onClick = { /* Add the click handler here */ }
-        ),
-        secondaryAction = SwipeToRevealDefaults.secondaryAction(
-            icon = { Icon(SwipeToRevealDefaults.MoreOptions, "More Options") },
-            onClick = { /* Add the click handler here */ }
-        ),
-        undoPrimaryAction = SwipeToRevealDefaults.undoAction(
-            label = { Text("Undo") },
-            onClick = { /* Add the undo handler for primary action */ }
-        ),
-        undoSecondaryAction = SwipeToRevealDefaults.undoAction(
-            label = { Text("Undo") },
-            onClick = { /* Add the undo handler for secondary action */ }
-        )
+        primaryAction = {
+            SwipeToRevealPrimaryAction(
+                revealState = revealState,
+                icon = { Icon(SwipeToRevealDefaults.Delete, "Delete") },
+                label = { Text("Delete") },
+                onClick = { /* Add the click handler here */ }
+            )
+        },
+        secondaryAction = {
+            SwipeToRevealSecondaryAction(
+                revealState = revealState,
+                onClick = { /* Add the click handler here */ }
+            ) {
+                Icon(SwipeToRevealDefaults.MoreOptions, "More Options")
+            }
+        },
+        undoPrimaryAction = {
+            SwipeToRevealUndoAction(
+                revealState = revealState,
+                label = { Text("Undo") },
+                onClick = { /* Add the undo handler for primary action */ }
+            )
+        },
+        undoSecondaryAction = {
+            SwipeToRevealUndoAction(
+                revealState = revealState,
+                label = { Text("Undo") },
+                onClick = { /* Add the undo handler for secondary action */ }
+            )
+        },
+        onFullSwipe = { /* Add the full swipe handler here */ }
     ) {
         AppCard(
             onClick = { /* Add the Card click handler */ },
diff --git a/wear/compose/compose-material/src/androidTest/kotlin/androidx/wear/compose/material/SwipeToRevealScreenshotTest.kt b/wear/compose/compose-material/src/androidTest/kotlin/androidx/wear/compose/material/SwipeToRevealScreenshotTest.kt
index 9622c31..68cc691 100644
--- a/wear/compose/compose-material/src/androidTest/kotlin/androidx/wear/compose/material/SwipeToRevealScreenshotTest.kt
+++ b/wear/compose/compose-material/src/androidTest/kotlin/androidx/wear/compose/material/SwipeToRevealScreenshotTest.kt
@@ -31,6 +31,7 @@
 import androidx.test.screenshot.AndroidXScreenshotTestRule
 import androidx.wear.compose.foundation.ExperimentalWearFoundationApi
 import androidx.wear.compose.foundation.RevealActionType
+import androidx.wear.compose.foundation.RevealScope
 import androidx.wear.compose.foundation.RevealState
 import androidx.wear.compose.foundation.RevealValue
 import androidx.wear.compose.foundation.rememberRevealState
@@ -163,23 +164,40 @@
     @Composable
     private fun swipeToRevealCard(
         revealState: RevealState = rememberRevealState(),
-        secondaryAction: SwipeToRevealAction? = SwipeToRevealDefaults.secondaryAction(
-            icon = { Icon(SwipeToRevealDefaults.MoreOptions, "More Options") }
-        ),
-        undoPrimaryAction: SwipeToRevealAction? = SwipeToRevealDefaults.undoAction(
-            label = { Text("Undo") }
-        ),
-        undoSecondaryAction: SwipeToRevealAction? = SwipeToRevealDefaults.undoAction(
-            label = { Text("Undo") }
-        )
+        secondaryAction: (@Composable RevealScope.() -> Unit)? = {
+            SwipeToRevealSecondaryAction(
+                revealState = revealState,
+                onClick = {},
+                content = { Icon(SwipeToRevealDefaults.MoreOptions, "More Options") }
+            )
+        },
+        undoPrimaryAction: (@Composable RevealScope.() -> Unit)? = {
+            SwipeToRevealUndoAction(
+                revealState = revealState,
+                onClick = {},
+                label = { Text("Undo") }
+            )
+        },
+        undoSecondaryAction: (@Composable RevealScope.() -> Unit)? = {
+            SwipeToRevealUndoAction(
+                revealState = revealState,
+                onClick = {},
+                label = { Text("Undo") }
+            )
+        }
     ) {
         Box(modifier = Modifier.fillMaxSize().background(MaterialTheme.colors.background)) {
             SwipeToRevealCard(
                 modifier = Modifier.testTag(TEST_TAG),
-                primaryAction = SwipeToRevealDefaults.primaryAction(
-                    icon = { Icon(SwipeToRevealDefaults.Delete, "Delete") },
-                    label = { Text("Delete") }
-                ),
+                primaryAction = {
+                    SwipeToRevealPrimaryAction(
+                        revealState = revealState,
+                        onClick = {},
+                        icon = { Icon(SwipeToRevealDefaults.Delete, "Delete") },
+                        label = { Text("Delete") }
+                    )
+                },
+                onFullSwipe = {},
                 secondaryAction = secondaryAction,
                 undoPrimaryAction = undoPrimaryAction,
                 undoSecondaryAction = undoSecondaryAction,
@@ -199,23 +217,40 @@
     @Composable
     private fun swipeToRevealChip(
         revealState: RevealState = rememberRevealState(),
-        secondaryAction: SwipeToRevealAction? = SwipeToRevealDefaults.secondaryAction(
-            icon = { Icon(SwipeToRevealDefaults.MoreOptions, "More Options") }
-        ),
-        undoPrimaryAction: SwipeToRevealAction? = SwipeToRevealDefaults.undoAction(
-            label = { Text("Undo") }
-        ),
-        undoSecondaryAction: SwipeToRevealAction? = SwipeToRevealDefaults.undoAction(
-            label = { Text("Undo") }
-        )
+        secondaryAction: (@Composable RevealScope.() -> Unit)? = {
+            SwipeToRevealSecondaryAction(
+                revealState = revealState,
+                onClick = {},
+                content = { Icon(SwipeToRevealDefaults.MoreOptions, "More Options") }
+            )
+        },
+        undoPrimaryAction: (@Composable RevealScope.() -> Unit)? = {
+            SwipeToRevealUndoAction(
+                revealState = revealState,
+                onClick = {},
+                label = { Text("Undo") }
+            )
+        },
+        undoSecondaryAction: (@Composable RevealScope.() -> Unit)? = {
+            SwipeToRevealUndoAction(
+                revealState = revealState,
+                onClick = {},
+                label = { Text("Undo") }
+            )
+        }
     ) {
         Box(modifier = Modifier.fillMaxSize().background(MaterialTheme.colors.background)) {
             SwipeToRevealChip(
                 modifier = Modifier.testTag(TEST_TAG),
-                primaryAction = SwipeToRevealDefaults.primaryAction(
-                    icon = { Icon(SwipeToRevealDefaults.Delete, "Delete") },
-                    label = { Text("Delete") }
-                ),
+                primaryAction = {
+                    SwipeToRevealPrimaryAction(
+                        revealState = revealState,
+                        onClick = {},
+                        icon = { Icon(SwipeToRevealDefaults.Delete, "Delete") },
+                        label = { Text("Delete") }
+                    )
+                },
+                onFullSwipe = {},
                 secondaryAction = secondaryAction,
                 undoPrimaryAction = undoPrimaryAction,
                 undoSecondaryAction = undoSecondaryAction,
diff --git a/wear/compose/compose-material/src/androidTest/kotlin/androidx/wear/compose/material/SwipeToRevealTest.kt b/wear/compose/compose-material/src/androidTest/kotlin/androidx/wear/compose/material/SwipeToRevealTest.kt
index 53b9f84..a6eeae6 100644
--- a/wear/compose/compose-material/src/androidTest/kotlin/androidx/wear/compose/material/SwipeToRevealTest.kt
+++ b/wear/compose/compose-material/src/androidTest/kotlin/androidx/wear/compose/material/SwipeToRevealTest.kt
@@ -34,6 +34,7 @@
 import androidx.compose.ui.unit.dp
 import androidx.wear.compose.foundation.ExperimentalWearFoundationApi
 import androidx.wear.compose.foundation.RevealActionType
+import androidx.wear.compose.foundation.RevealScope
 import androidx.wear.compose.foundation.RevealState
 import androidx.wear.compose.foundation.RevealValue
 import androidx.wear.compose.foundation.rememberRevealState
@@ -43,7 +44,7 @@
 import org.junit.Test
 
 @OptIn(ExperimentalWearFoundationApi::class, ExperimentalWearMaterialApi::class)
-class SwipeToRevealActionTest {
+class SwipeToRevealTest {
     @get:Rule
     val rule = createComposeRule()
 
@@ -177,11 +178,15 @@
     fun onPrimaryActionClick_triggersOnClick_forChip() {
         var clicked = false
         rule.setContentWithTheme {
+            val revealState = rememberRevealState(initialValue = RevealValue.Revealing)
             swipeToRevealChipDefault(
-                revealState = rememberRevealState(initialValue = RevealValue.Revealing),
-                primaryAction = createPrimaryAction(
-                    onClick = { clicked = true }
-                )
+                revealState = revealState,
+                primaryAction = {
+                    createPrimaryAction(
+                        revealState = revealState,
+                        onClick = { clicked = true }
+                    )
+                }
             )
         }
 
@@ -193,11 +198,15 @@
     fun onSecondaryActionClick_triggersOnClick_forChip() {
         var clicked = false
         rule.setContentWithTheme {
+            val revealState = rememberRevealState(initialValue = RevealValue.Revealing)
             swipeToRevealChipDefault(
-                revealState = rememberRevealState(initialValue = RevealValue.Revealing),
-                secondaryAction = createSecondaryAction(
-                    onClick = { clicked = true }
-                )
+                revealState = revealState,
+                secondaryAction = {
+                    createSecondaryAction(
+                        revealState = revealState,
+                        onClick = { clicked = true }
+                    )
+                }
             )
         }
 
@@ -212,11 +221,14 @@
             val coroutineScope = rememberCoroutineScope()
             swipeToRevealCardDefault(
                 revealState = revealState,
-                primaryAction = createPrimaryAction(
-                    onClick = {
-                        coroutineScope.launch { revealState.animateTo(RevealValue.Revealed) }
-                    }
-                ),
+                primaryAction = {
+                    createPrimaryAction(
+                        revealState = revealState,
+                        onClick = {
+                            coroutineScope.launch { revealState.animateTo(RevealValue.Revealed) }
+                        }
+                    )
+                },
                 undoSecondaryAction = null
             )
         }
@@ -236,20 +248,26 @@
             val coroutineScope = rememberCoroutineScope()
             swipeToRevealCardDefault(
                 revealState = revealState,
-                primaryAction = createPrimaryAction(
-                    onClick = {
-                        coroutineScope.launch { revealState.animateTo(RevealValue.Revealed) }
-                    }
-                ),
-                undoPrimaryAction = createUndoAction(
-                    onClick = {
-                        coroutineScope.launch {
-                            // reset state when undo is clicked
-                            revealState.animateTo(RevealValue.Covered)
-                            revealState.lastActionType = RevealActionType.None
+                primaryAction = {
+                    createPrimaryAction(
+                        revealState = revealState,
+                        onClick = {
+                            coroutineScope.launch { revealState.animateTo(RevealValue.Revealed) }
                         }
-                    }
-                ),
+                    )
+                },
+                undoPrimaryAction = {
+                    createUndoAction(
+                        revealState = revealState,
+                        onClick = {
+                            coroutineScope.launch {
+                                // reset state when undo is clicked
+                                revealState.animateTo(RevealValue.Covered)
+                                revealState.lastActionType = RevealActionType.None
+                            }
+                        }
+                    )
+                },
                 undoSecondaryAction = null
             )
         }
@@ -271,11 +289,14 @@
             val coroutineScope = rememberCoroutineScope()
             swipeToRevealCardDefault(
                 revealState = revealState,
-                secodnaryAction = createSecondaryAction(
-                    onClick = {
-                        coroutineScope.launch { revealState.animateTo(RevealValue.Revealed) }
-                    }
-                ),
+                secondaryAction = {
+                    createSecondaryAction(
+                        revealState = revealState,
+                        onClick = {
+                            coroutineScope.launch { revealState.animateTo(RevealValue.Revealed) }
+                        }
+                    )
+                },
                 undoPrimaryAction = null
             )
         }
@@ -295,23 +316,29 @@
             val coroutineScope = rememberCoroutineScope()
             swipeToRevealCardDefault(
                 revealState = revealState,
-                secodnaryAction = createSecondaryAction(
-                    onClick = {
-                        coroutineScope.launch {
-                            revealState.animateTo(RevealValue.Revealed)
+                secondaryAction = {
+                    createSecondaryAction(
+                        revealState = revealState,
+                        onClick = {
+                            coroutineScope.launch {
+                                revealState.animateTo(RevealValue.Revealed)
+                            }
                         }
-                    }
-                ),
-                undoSecondaryAction = createUndoAction(
-                    modifier = Modifier.testTag(UNDO_SECONDARY_ACTION_TAG),
-                    onClick = {
-                        coroutineScope.launch {
-                            // reset state after undo is clicked
-                            revealState.animateTo(RevealValue.Covered)
-                            revealState.lastActionType = RevealActionType.None
+                    )
+                },
+                undoSecondaryAction = {
+                    createUndoAction(
+                        revealState = revealState,
+                        modifier = Modifier.testTag(UNDO_SECONDARY_ACTION_TAG),
+                        onClick = {
+                            coroutineScope.launch {
+                                // reset state after undo is clicked
+                                revealState.animateTo(RevealValue.Covered)
+                                revealState.lastActionType = RevealActionType.None
+                            }
                         }
-                    }
-                ),
+                    )
+                },
                 undoPrimaryAction = null
             )
         }
@@ -374,17 +401,25 @@
     private fun swipeToRevealChipDefault(
         modifier: Modifier = Modifier,
         revealState: RevealState = rememberRevealState(),
-        primaryAction: SwipeToRevealAction = createPrimaryAction(),
-        secondaryAction: SwipeToRevealAction = createSecondaryAction(),
-        undoPrimaryAction: SwipeToRevealAction? = createUndoAction(),
-        undoSecondaryAction: SwipeToRevealAction? =
-            createUndoAction(modifier = Modifier.testTag(UNDO_SECONDARY_ACTION_TAG)),
+        primaryAction: @Composable RevealScope.() -> Unit = { createPrimaryAction(revealState) },
+        secondaryAction: @Composable RevealScope.() -> Unit =
+            { createSecondaryAction(revealState) },
+        undoPrimaryAction: (@Composable RevealScope.() -> Unit)? =
+            { createUndoAction(revealState) },
+        undoSecondaryAction: (@Composable RevealScope.() -> Unit)? = {
+            createUndoAction(
+                revealState,
+                modifier = Modifier.testTag(UNDO_SECONDARY_ACTION_TAG)
+            )
+        },
+        onFullSwipe: () -> Unit = {},
         colors: SwipeToRevealActionColors = SwipeToRevealDefaults.actionColors(),
         content: @Composable () -> Unit = { createContent() }
     ) {
         SwipeToRevealChip(
             modifier = modifier,
             revealState = revealState,
+            onFullSwipe = onFullSwipe,
             primaryAction = primaryAction,
             secondaryAction = secondaryAction,
             undoPrimaryAction = undoPrimaryAction,
@@ -398,19 +433,27 @@
     private fun swipeToRevealCardDefault(
         modifier: Modifier = Modifier,
         revealState: RevealState = rememberRevealState(),
-        primaryAction: SwipeToRevealAction = createPrimaryAction(),
-        secodnaryAction: SwipeToRevealAction = createSecondaryAction(),
-        undoPrimaryAction: SwipeToRevealAction? = createUndoAction(),
-        undoSecondaryAction: SwipeToRevealAction? =
-            createUndoAction(modifier = Modifier.testTag(UNDO_SECONDARY_ACTION_TAG)),
+        primaryAction: @Composable RevealScope.() -> Unit = { createPrimaryAction(revealState) },
+        secondaryAction: @Composable RevealScope.() -> Unit =
+            { createSecondaryAction(revealState) },
+        undoPrimaryAction: (@Composable RevealScope.() -> Unit)? =
+            { createUndoAction(revealState) },
+        undoSecondaryAction: (@Composable RevealScope.() -> Unit)? = {
+            createUndoAction(
+                revealState,
+                modifier = Modifier.testTag(UNDO_SECONDARY_ACTION_TAG)
+            )
+        },
+        onFullSwipe: () -> Unit = {},
         colors: SwipeToRevealActionColors = SwipeToRevealDefaults.actionColors(),
         content: @Composable () -> Unit = { createContent() }
     ) {
         SwipeToRevealCard(
             modifier = modifier,
             revealState = revealState,
+            onFullSwipe = onFullSwipe,
             primaryAction = primaryAction,
-            secondaryAction = secodnaryAction,
+            secondaryAction = secondaryAction,
             undoPrimaryAction = undoPrimaryAction,
             undoSecondaryAction = undoSecondaryAction,
             colors = colors,
@@ -419,39 +462,51 @@
     }
 
     @Composable
-    private fun createPrimaryAction(
+    private fun RevealScope.createPrimaryAction(
+        revealState: RevealState,
         icon: @Composable () -> Unit = { Icon(SwipeToRevealDefaults.Delete, "Delete") },
         label: @Composable () -> Unit = { Text("Clear") },
         modifier: Modifier = Modifier,
         onClick: () -> Unit = {},
-    ): SwipeToRevealAction = SwipeToRevealDefaults.primaryAction(
-        icon = icon,
-        label = label,
-        modifier = modifier.testTag(PRIMARY_ACTION_TAG),
-        onClick = onClick
-    )
+    ) {
+        SwipeToRevealPrimaryAction(
+            revealState = revealState,
+            icon = icon,
+            label = label,
+            modifier = modifier.testTag(PRIMARY_ACTION_TAG),
+            onClick = onClick
+        )
+    }
 
     @Composable
-    private fun createSecondaryAction(
+    private fun RevealScope.createSecondaryAction(
+        revealState: RevealState,
         icon: @Composable () -> Unit = { Icon(SwipeToRevealDefaults.MoreOptions, "More Options") },
         modifier: Modifier = Modifier,
         onClick: () -> Unit = {},
-    ): SwipeToRevealAction = SwipeToRevealDefaults.secondaryAction(
-        icon = icon,
-        modifier = modifier.testTag(SECONDARY_ACTION_TAG),
-        onClick = onClick
-    )
+    ) {
+        SwipeToRevealSecondaryAction(
+            revealState = revealState,
+            content = icon,
+            modifier = modifier.testTag(SECONDARY_ACTION_TAG),
+            onClick = onClick
+        )
+    }
 
     @Composable
-    private fun createUndoAction(
+    private fun RevealScope.createUndoAction(
+        revealState: RevealState,
         label: @Composable () -> Unit = { Text("Undo") },
         modifier: Modifier = Modifier,
         onClick: () -> Unit = {},
-    ) = SwipeToRevealDefaults.undoAction(
-        label = label,
-        modifier = modifier.testTag(UNDO_PRIMARY_ACTION_TAG),
-        onClick = onClick
-    )
+    ) {
+        SwipeToRevealUndoAction(
+            revealState = revealState,
+            label = label,
+            modifier = modifier.testTag(UNDO_PRIMARY_ACTION_TAG),
+            onClick = onClick
+        )
+    }
 
     @Composable
     private fun createContent(
diff --git a/wear/compose/compose-material/src/main/java/androidx/wear/compose/material/PositionIndicator.kt b/wear/compose/compose-material/src/main/java/androidx/wear/compose/material/PositionIndicator.kt
index 423ff64..3c8f1bf 100644
--- a/wear/compose/compose-material/src/main/java/androidx/wear/compose/material/PositionIndicator.kt
+++ b/wear/compose/compose-material/src/main/java/androidx/wear/compose/material/PositionIndicator.kt
@@ -178,10 +178,9 @@
  * To disable this animation [snap] AnimationSpec should be passed instead.
  * @param fadeOutAnimationSpec [AnimationSpec] for fade-out animation.
  * The Fade-out animation is used for hiding the [PositionIndicator] and making it invisible.
- * If true, the Fade-out animation is triggered after a delay if no changes in
- * state.positionFraction or state.sizeFraction were detected,
- * hiding the [PositionIndicator] with animation.
- * To disable this animation [snap] AnimationSpec should be passed instead.
+ * [PositionIndicator] will be hidden after a specified delay if no changes
+ * in state.positionFraction or state.sizeFraction were detected.
+ * If [fadeOutAnimationSpec] is [snap], then after a delay it will be instantly hidden.
  * @param positionAnimationSpec [AnimationSpec] for position animation.
  * The Position animation is used for animating changes between state.positionFraction
  * and state.sizeFraction of [PositionIndicatorState].
@@ -257,10 +256,9 @@
  * To disable this animation [snap] AnimationSpec should be passed instead.
  * @param fadeOutAnimationSpec [AnimationSpec] for fade-out animation.
  * The Fade-out animation is used for hiding the [PositionIndicator] and making it invisible.
- * If true, the Fade-out animation is triggered after a delay if no changes in
- * state.positionFraction or state.sizeFraction were detected,
- * hiding the [PositionIndicator] with animation.
- * To disable this animation [snap] AnimationSpec should be passed instead.
+ * [PositionIndicator] will be hidden after a specified delay if no changes
+ * in state.positionFraction or state.sizeFraction were detected.
+ * If [fadeOutAnimationSpec] is [snap], then after a delay it will be instantly hidden.
  * @param positionAnimationSpec [AnimationSpec] for position animation.
  * The Position animation is used for animating changes between state.positionFraction
  * and state.sizeFraction of [PositionIndicatorState].
@@ -374,10 +372,9 @@
  * To disable this animation [snap] AnimationSpec should be passed instead.
  * @param fadeOutAnimationSpec [AnimationSpec] for fade-out animation.
  * The Fade-out animation is used for hiding the [PositionIndicator] and making it invisible.
- * If true, the Fade-out animation is triggered after a delay if no changes in
- * state.positionFraction or state.sizeFraction were detected,
- * hiding the [PositionIndicator] with animation.
- * To disable this animation [snap] AnimationSpec should be passed instead.
+ * [PositionIndicator] will be hidden after a specified delay if no changes
+ * in state.positionFraction or state.sizeFraction were detected.
+ * If [fadeOutAnimationSpec] is [snap], then after a delay it will be instantly hidden.
  * @param positionAnimationSpec [AnimationSpec] for position animation.
  * The Position animation is used for animating changes between state.positionFraction
  * and state.sizeFraction of [PositionIndicatorState].
@@ -509,10 +506,9 @@
  * To disable this animation [snap] AnimationSpec should be passed instead.
  * @param fadeOutAnimationSpec [AnimationSpec] for fade-out animation.
  * The Fade-out animation is used for hiding the [PositionIndicator] and making it invisible.
- * If true, the Fade-out animation is triggered after a delay if no changes in
- * state.positionFraction or state.sizeFraction were detected,
- * hiding the [PositionIndicator] with animation.
- * To disable this animation [snap] AnimationSpec should be passed instead.
+ * [PositionIndicator] will be hidden after a specified delay if no changes
+ * in state.positionFraction or state.sizeFraction were detected.
+ * If [fadeOutAnimationSpec] is [snap], then after a delay it will be instantly hidden.
  * @param positionAnimationSpec [AnimationSpec] for position animation.
  * The Position animation is used for animating changes between state.positionFraction
  * and state.sizeFraction of [PositionIndicatorState].
@@ -638,10 +634,9 @@
  * To disable this animation [snap] AnimationSpec should be passed instead.
  * @param fadeOutAnimationSpec [AnimationSpec] for fade-out animation.
  * The Fade-out animation is used for hiding the [PositionIndicator] and making it invisible.
- * If true, the Fade-out animation is triggered after a delay if no changes in
- * state.positionFraction or state.sizeFraction were detected,
- * hiding the [PositionIndicator] with animation.
- * To disable this animation [snap] AnimationSpec should be passed instead.
+ * [PositionIndicator] will be hidden after a specified delay if no changes
+ * in state.positionFraction or state.sizeFraction were detected.
+ * If [fadeOutAnimationSpec] is [snap], then after a delay it will be instantly hidden.
  * @param positionAnimationSpec [AnimationSpec] for position animation.
  * The Position animation is used for animating changes between state.positionFraction
  * and state.sizeFraction of [PositionIndicatorState].
diff --git a/wear/compose/compose-material/src/main/java/androidx/wear/compose/material/SwipeToReveal.kt b/wear/compose/compose-material/src/main/java/androidx/wear/compose/material/SwipeToReveal.kt
index 7396864..5a3c2a3 100644
--- a/wear/compose/compose-material/src/main/java/androidx/wear/compose/material/SwipeToReveal.kt
+++ b/wear/compose/compose-material/src/main/java/androidx/wear/compose/material/SwipeToReveal.kt
@@ -63,7 +63,7 @@
  * additional actions on the [Chip]: a mandatory [primaryAction] and an optional
  * [secondaryAction]. These actions are initially hidden and revealed only when the [content] is
  * swiped. These additional actions can be triggered by clicking on them after they are revealed.
- * [primaryAction] can also be triggered by performing a full swipe of the [content].
+ * It is recommended to trigger [primaryAction] on full swipe of the [content].
  *
  * For actions like "Delete", consider adding [undoPrimaryAction] (displayed when the
  * [primaryAction] is activated) and/or [undoSecondaryAction] (displayed when the [secondaryAction]
@@ -72,18 +72,19 @@
  * Example of [SwipeToRevealChip] with primary and secondary actions
  * @sample androidx.wear.compose.material.samples.SwipeToRevealChipSample
  *
- * @param primaryAction A [SwipeToRevealAction] instance to describe the primary action when
- * swiping. See [SwipeToRevealDefaults.primaryAction]. The action will be triggered on click or a
- * full swipe.
+ * @param primaryAction A composable to describe the primary action when swiping. The action will
+ * be triggered on clicking the action. See [SwipeToRevealPrimaryAction].
  * @param revealState [RevealState] of the [SwipeToReveal]
+ * @param onFullSwipe A lambda which will be triggered on full swipe from either of the anchors. We
+ * recommend to keep this similar to primary action click action. This sets the
+ * [RevealState.lastActionType] to [RevealActionType.PrimaryAction].
  * @param modifier [Modifier] to be applied on the composable
- * @param secondaryAction A [SwipeToRevealAction] instance to describe the contents of secondary
- * action. See [SwipeToRevealDefaults.secondaryAction]. The action will be triggered on clicking the
- * action.
- * @param undoPrimaryAction A [SwipeToRevealAction] instance to describe the contents of undo action
- * when the primary action was triggered. See [SwipeToRevealDefaults.undoAction].
- * @param undoSecondaryAction [SwipeToRevealAction] instance to describe the contents of undo
- * action when secondary action was triggered. See [SwipeToRevealDefaults.undoAction].
+ * @param secondaryAction A composable to describe the contents of secondary action. The action
+ * will be triggered on clicking the action. See [SwipeToRevealSecondaryAction]
+ * @param undoPrimaryAction A composable to describe the contents of undo action when the primary
+ * action was triggered. See [SwipeToRevealUndoAction]
+ * @param undoSecondaryAction composable to describe the contents of undo action when secondary
+ * action was triggered. See [SwipeToRevealUndoAction]
  * @param colors An instance of [SwipeToRevealActionColors] to describe the colors of actions. See
  * [SwipeToRevealDefaults.actionColors].
  * @param shape The shape of primary and secondary action composables. Recommended shape for chips
@@ -96,12 +97,13 @@
 @OptIn(ExperimentalWearFoundationApi::class)
 @Composable
 public fun SwipeToRevealChip(
-    primaryAction: SwipeToRevealAction,
+    primaryAction: @Composable RevealScope.() -> Unit,
     revealState: RevealState,
+    onFullSwipe: () -> Unit,
     modifier: Modifier = Modifier,
-    secondaryAction: SwipeToRevealAction? = null,
-    undoPrimaryAction: SwipeToRevealAction? = null,
-    undoSecondaryAction: SwipeToRevealAction? = null,
+    secondaryAction: @Composable (RevealScope.() -> Unit)? = null,
+    undoPrimaryAction: @Composable (RevealScope.() -> Unit)? = null,
+    undoSecondaryAction: @Composable (RevealScope.() -> Unit)? = null,
     colors: SwipeToRevealActionColors = SwipeToRevealDefaults.actionColors(),
     shape: Shape = MaterialTheme.shapes.small,
     content: @Composable () -> Unit
@@ -115,6 +117,7 @@
         undoSecondaryAction = undoSecondaryAction,
         colors = colors,
         shape = shape,
+        onFullSwipe = onFullSwipe,
         content = content
     )
 }
@@ -124,7 +127,7 @@
  * additional actions on the [Card]: a mandatory [primaryAction] and an optional
  * [secondaryAction]. These actions are initially hidden and revealed only when the [content] is
  * swiped. These additional actions can be triggered by clicking on them after they are revealed.
- * [primaryAction] can also be triggered by performing a full swipe of the [content].
+ * It is recommended to trigger [primaryAction] on full swipe of the [content].
  *
  * For actions like "Delete", consider adding [undoPrimaryAction] (displayed when the
  * [primaryAction] is activated) and/or [undoSecondaryAction] (displayed when the [secondaryAction]
@@ -133,18 +136,19 @@
  * Example of [SwipeToRevealCard] with primary and secondary actions
  * @sample androidx.wear.compose.material.samples.SwipeToRevealCardSample
  *
- * @param primaryAction A [SwipeToRevealAction] instance to describe the primary action when
- * swiping. See [SwipeToRevealDefaults.primaryAction]. The action will be triggered on click or a
- * full swipe.
+ * @param primaryAction A composable to describe the primary action when swiping. The action will
+ * be triggered on clicking the action. See [SwipeToRevealPrimaryAction].
  * @param revealState [RevealState] of the [SwipeToReveal]
+ * @param onFullSwipe A lambda which will be triggered on full swipe from either of the anchors. We
+ * recommend to keep this similar to primary action click action. This sets the
+ * [RevealState.lastActionType] to [RevealActionType.PrimaryAction].
  * @param modifier [Modifier] to be applied on the composable
- * @param secondaryAction A [SwipeToRevealAction] instance to describe the contents of secondary
- * action. See [SwipeToRevealDefaults.secondaryAction]. The action will be triggered on clicking the
- * action.
- * @param undoPrimaryAction A [SwipeToRevealAction] instance to describe the contents of undo action
- * when the primary action was triggered. See [SwipeToRevealDefaults.undoAction].
- * @param undoSecondaryAction [SwipeToRevealAction] instance to describe the contents of undo
- * action when secondary action was triggered. See [SwipeToRevealDefaults.undoAction].
+ * @param secondaryAction A composable to describe the contents of secondary action.The action will
+ * be triggered on clicking the action. See [SwipeToRevealSecondaryAction]
+ * @param undoPrimaryAction A composable to describe the contents of undo action when the primary
+ * action was triggered. See [SwipeToRevealUndoAction]
+ * @param undoSecondaryAction A composable to describe the contents of undo action when secondary
+ * action was triggered. See [SwipeToRevealUndoAction]
  * @param colors An instance of [SwipeToRevealActionColors] to describe the colors of actions. See
  * [SwipeToRevealDefaults.actionColors].
  * @param shape The shape of primary and secondary action composables. Recommended shape for cards
@@ -157,12 +161,13 @@
 @OptIn(ExperimentalWearFoundationApi::class)
 @Composable
 public fun SwipeToRevealCard(
-    primaryAction: SwipeToRevealAction,
+    primaryAction: @Composable RevealScope.() -> Unit,
     revealState: RevealState,
+    onFullSwipe: () -> Unit,
     modifier: Modifier = Modifier,
-    secondaryAction: SwipeToRevealAction? = null,
-    undoPrimaryAction: SwipeToRevealAction? = null,
-    undoSecondaryAction: SwipeToRevealAction? = null,
+    secondaryAction: @Composable (RevealScope.() -> Unit)? = null,
+    undoPrimaryAction: @Composable (RevealScope.() -> Unit)? = null,
+    undoSecondaryAction: @Composable (RevealScope.() -> Unit)? = null,
     colors: SwipeToRevealActionColors = SwipeToRevealDefaults.actionColors(),
     shape: Shape = SwipeToRevealDefaults.CardActionShape,
     content: @Composable () -> Unit
@@ -176,11 +181,118 @@
         undoSecondaryAction = undoSecondaryAction,
         colors = colors,
         shape = shape,
+        onFullSwipe = onFullSwipe,
         content = content
     )
 }
 
 /**
+ * A composable which can be used for setting the primary action of material [SwipeToRevealCard]
+ * and [SwipeToRevealChip].
+ *
+ * @param revealState The [RevealState] of the [SwipeToReveal] where this action is used.
+ * @param onClick A lambda which gets triggered when the action is clicked.
+ * @param icon The icon which will be displayed initially on the action
+ * @param label The label which will be displayed on the expanded action
+ * @param modifier [Modifier] to be applied on the action
+ * @param interactionSource The [MutableInteractionSource] representing the stream of
+ * interactions with this action.
+ */
+@OptIn(ExperimentalWearFoundationApi::class)
+@ExperimentalWearMaterialApi
+@Composable
+public fun RevealScope.SwipeToRevealPrimaryAction(
+    revealState: RevealState,
+    onClick: () -> Unit,
+    icon: @Composable () -> Unit,
+    label: @Composable () -> Unit,
+    modifier: Modifier = Modifier,
+    interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
+) = ActionCommon(
+    revealState = revealState,
+    actionType = RevealActionType.PrimaryAction,
+    onClick = onClick,
+    modifier = modifier,
+    interactionSource = interactionSource,
+    icon = icon,
+    label = label
+)
+
+/**
+ * A composable which can be used for setting the secondary action of material [SwipeToRevealCard]
+ * and [SwipeToRevealChip].
+ *
+ * @param revealState The [RevealState] of the [SwipeToReveal] where this action is used.
+ * @param onClick A lambda which gets triggered when the action is clicked.
+ * @param modifier [Modifier] to be applied on the action
+ * @param interactionSource The [MutableInteractionSource] representing the stream of
+ * interactions with this action.
+ * @param content The composable which will be displayed on the action. It is recommended to keep
+ * this content as an [Icon] composable.
+ */
+@OptIn(ExperimentalWearFoundationApi::class)
+@ExperimentalWearMaterialApi
+@Composable
+public fun RevealScope.SwipeToRevealSecondaryAction(
+    revealState: RevealState,
+    onClick: () -> Unit,
+    modifier: Modifier = Modifier,
+    interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
+    content: @Composable () -> Unit,
+) = ActionCommon(
+    revealState = revealState,
+    actionType = RevealActionType.SecondaryAction,
+    onClick = onClick,
+    modifier = modifier,
+    interactionSource = interactionSource,
+    icon = content,
+    label = null
+)
+
+/**
+ * A composable which can be used for setting the undo action of material [SwipeToRevealCard]
+ * and [SwipeToRevealChip].
+ *
+ * @param revealState The [RevealState] of the [SwipeToReveal] where this action is used.
+ * @param onClick A lambda which gets triggered when the action is clicked.
+ * @param modifier [Modifier] to be applied on the action
+ * @param interactionSource The [MutableInteractionSource] representing the stream of
+ * interactions with this action.
+ * @param icon An optional icon which will be displayed on the action
+ * @param label An optional label which will be displayed on the action. We strongly recommend to
+ * set [icon] and/or [label] for the action.
+ */
+@OptIn(ExperimentalWearFoundationApi::class)
+@ExperimentalWearMaterialApi
+@Composable
+public fun RevealScope.SwipeToRevealUndoAction(
+    revealState: RevealState,
+    onClick: () -> Unit,
+    modifier: Modifier = Modifier,
+    interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
+    icon: (@Composable () -> Unit)? = null,
+    label: (@Composable () -> Unit)? = null,
+) {
+    Row(
+        modifier = modifier
+            .clickable(
+                interactionSource = interactionSource,
+                indication = rememberRipple(),
+                role = Role.Button,
+                onClick = {
+                    revealState.lastActionType = RevealActionType.UndoAction
+                    onClick()
+                }
+            ),
+        verticalAlignment = Alignment.CenterVertically
+    ) {
+        icon?.invoke()
+        Spacer(Modifier.size(5.dp))
+        label?.invoke()
+    }
+}
+
+/**
  * Defaults for Material [SwipeToReveal].
  */
 @ExperimentalWearMaterialApi
@@ -196,15 +308,15 @@
      * primary, secondary and undo actions in [SwipeToReveal].
      *
      * @param primaryActionBackgroundColor The background color (color of the shape) of the
-     * [primaryAction]
-     * @param primaryActionContentColor The content color (text and icon) of the [primaryAction]
+     * primary action
+     * @param primaryActionContentColor The content color (text and icon) of the primary action
      * @param secondaryActionBackgroundColor The background color (color of the shape) of the
-     * [secondaryAction]
+     * secondary action
      * @param secondaryActionContentColor The content color (text and icon) of the
-     * [secondaryAction]
+     * secondary action
      * @param undoActionBackgroundColor The background color (color of the shape) of the
-     * [undoAction]
-     * @param undoActionContentColor The content color (text) of the [undoAction]
+     * undo action
+     * @param undoActionContentColor The content color (text) of the undo action
      */
     @Composable
     public fun actionColors(
@@ -226,98 +338,6 @@
     }
 
     /**
-     * Creates a new [SwipeToRevealAction] instance for the primary action parameter of
-     * [SwipeToRevealChip] and [SwipeToRevealCard].
-     *
-     * @param icon The icon that will be displayed initially on the action
-     * @param label The text that will be displayed on the expanded action
-     * @param modifier [Modifier] to be applied on this action composable
-     * @param actionType The [RevealActionType] that is set in [RevealState.lastActionType] when
-     * this action is clicked.
-     * @param interactionSource The [MutableInteractionSource] representing the stream of
-     * interactions with this action.
-     * @param onClick Called when this action is clicked.
-     */
-    @Composable
-    public fun primaryAction(
-        icon: @Composable () -> Unit,
-        label: @Composable () -> Unit,
-        modifier: Modifier = Modifier,
-        actionType: RevealActionType = RevealActionType.PrimaryAction,
-        interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
-        onClick: () -> Unit = {}
-    ): SwipeToRevealAction {
-        return SwipeToRevealAction(
-            icon = icon,
-            label = label,
-            modifier = modifier,
-            actionType = actionType,
-            interactionSource = interactionSource,
-            onClick = onClick
-        )
-    }
-
-    /**
-     * Creates a new [SwipeToRevealAction] instance for the secondary action of [SwipeToRevealChip]
-     * and [SwipeToRevealCard].
-     *
-     * @param icon The icon that will be displayed initially on the screen
-     * @param modifier [Modifier] to be applied on this action composable
-     * @param actionType The [RevealActionType] that is set in [RevealState.lastActionType] when
-     * this action is clicked.
-     * @param interactionSource The [MutableInteractionSource] representing the stream of
-     * interactions with this action.
-     * @param onClick Called when this action is clicked.
-     */
-    @Composable
-    public fun secondaryAction(
-        icon: @Composable () -> Unit,
-        modifier: Modifier = Modifier,
-        actionType: RevealActionType = RevealActionType.SecondaryAction,
-        interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
-        onClick: () -> Unit = {}
-    ): SwipeToRevealAction {
-        return SwipeToRevealAction(
-            icon = icon,
-            label = null,
-            modifier = modifier,
-            actionType = actionType,
-            interactionSource = interactionSource,
-            onClick = onClick
-        )
-    }
-
-    /**
-     * Creates a new [SwipeToRevealAction] instance for the undo action of [SwipeToRevealChip] and
-     * [SwipeToRevealCard].
-     *
-     * @param label The text that will be displayed on the undo action composable
-     * @param modifier [Modifier] to be applied on this action composable
-     * @param actionType The [RevealActionType] that is set in [RevealState.lastActionType] when
-     * this action is clicked.
-     * @param interactionSource The [MutableInteractionSource] representing the stream of
-     * interactions with this action.
-     * @param onClick Called when this action is clicked.
-     */
-    @Composable
-    public fun undoAction(
-        label: @Composable () -> Unit,
-        modifier: Modifier = Modifier,
-        actionType: RevealActionType = RevealActionType.UndoAction,
-        interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
-        onClick: () -> Unit = {}
-    ): SwipeToRevealAction {
-        return SwipeToRevealAction(
-            icon = null,
-            label = label,
-            modifier = modifier,
-            actionType = actionType,
-            interactionSource = interactionSource,
-            onClick = onClick
-        )
-    }
-
-    /**
      * [ImageVector] for delete icon, often used for the primary action.
      */
     public val Delete = Icons.Outlined.Delete
@@ -380,72 +400,18 @@
     }
 }
 
-/**
- * A class containing the details required for describing the content of an action composable.
- * Both composables, [icon] and [label] are optional, however it is expected that at least one is
- * provided. See the parameters below on how these are used based on action.
- *
- * @param icon A slot for providing the icon for this [SwipeToReveal] action. This is mandatory for
- * primary and secondary action. It is recommended to not use this for undo action.
- * @param label A slot for providing a text label for this [SwipeToRevealAction] action. The
- * content provided here will be used in different perspective based on the action type
- * (primary action or undo action). It is recommended to not use this for secondary action.
- * @param modifier The [Modifier] to be applied on the action.
- * @param actionType The [RevealActionType] that gets applied to [RevealState.lastActionType] when
- * this action is clicked.
- * @param interactionSource The [MutableInteractionSource] representing the stream of
- * interactions with this action.
- * @param onClick Called when the user clicks the action.
- */
-@ExperimentalWearMaterialApi
-@OptIn(ExperimentalWearFoundationApi::class)
-public class SwipeToRevealAction constructor(
-    val icon: (@Composable () -> Unit)?,
-    val label: (@Composable () -> Unit)?,
-    val modifier: Modifier,
-    val actionType: RevealActionType,
-    val interactionSource: MutableInteractionSource,
-    val onClick: () -> Unit
-) {
-    override fun equals(other: Any?): Boolean {
-        if (this === other) return true
-        if (other == null) return false
-        if (this::class != other::class) return false
-
-        other as SwipeToRevealAction
-
-        if (icon != other.icon) return false
-        if (label != other.label) return false
-        if (modifier != other.modifier) return false
-        if (actionType != other.actionType) return false
-        if (interactionSource != other.interactionSource) return false
-        if (onClick != other.onClick) return false
-
-        return true
-    }
-
-    override fun hashCode(): Int {
-        var result = icon.hashCode()
-        result = 31 * result + label.hashCode()
-        result = 31 * result + modifier.hashCode()
-        result = 31 * result + actionType.hashCode()
-        result = 31 * result + interactionSource.hashCode()
-        result = 31 * result + onClick.hashCode()
-        return result
-    }
-}
-
 @OptIn(ExperimentalWearMaterialApi::class, ExperimentalWearFoundationApi::class)
 @Composable
 private fun SwipeToRevealComponent(
-    primaryAction: SwipeToRevealAction,
+    primaryAction: @Composable RevealScope.() -> Unit,
     revealState: RevealState,
     modifier: Modifier,
-    secondaryAction: SwipeToRevealAction?,
-    undoPrimaryAction: SwipeToRevealAction?,
-    undoSecondaryAction: SwipeToRevealAction?,
+    secondaryAction: @Composable (RevealScope.() -> Unit)?,
+    undoPrimaryAction: @Composable (RevealScope.() -> Unit)?,
+    undoSecondaryAction: @Composable (RevealScope.() -> Unit)?,
     colors: SwipeToRevealActionColors,
     shape: Shape,
+    onFullSwipe: () -> Unit,
     content: @Composable () -> Unit
 ) {
     SwipeToReveal(
@@ -455,26 +421,25 @@
             // Full swipe triggers the main action, but does not set the click type.
             // Explicitly set the click type as main action when full swipe occurs.
             revealState.lastActionType = RevealActionType.PrimaryAction
-            primaryAction.onClick()
+            onFullSwipe()
         },
         primaryAction = {
-            SwipeToRevealAction(
+            ActionWrapper(
                 revealState = revealState,
-                action = primaryAction,
                 backgroundColor = colors.primaryActionBackgroundColor,
                 contentColor = colors.primaryActionContentColor,
                 shape = shape,
+                content = primaryAction
             )
         },
-        secondaryAction =
-        secondaryAction?.let {
+        secondaryAction = secondaryAction?.let {
             {
-                SwipeToRevealAction(
+                ActionWrapper(
                     revealState = revealState,
-                    action = secondaryAction,
                     backgroundColor = colors.secondaryActionBackgroundColor,
                     contentColor = colors.secondaryActionContentColor,
                     shape = shape,
+                    content = secondaryAction
                 )
             }
         },
@@ -482,20 +447,18 @@
         when (revealState.lastActionType) {
             RevealActionType.SecondaryAction -> undoSecondaryAction?.let {
                 {
-                    UndoAction(
-                        revealState = revealState,
-                        undoAction = undoSecondaryAction,
-                        colors = colors
+                    UndoActionWrapper(
+                        colors = colors,
+                        content = undoSecondaryAction
                     )
                 }
             }
             // With manual swiping the last click action type will be none, show undo action
             RevealActionType.PrimaryAction, RevealActionType.None -> undoPrimaryAction?.let {
                 {
-                    UndoAction(
-                        revealState = revealState,
-                        undoAction = undoPrimaryAction,
-                        colors = colors
+                    UndoActionWrapper(
+                        colors = colors,
+                        content = undoPrimaryAction
                     )
                 }
             }
@@ -510,12 +473,12 @@
  */
 @OptIn(ExperimentalWearFoundationApi::class, ExperimentalWearMaterialApi::class)
 @Composable
-private fun RevealScope.SwipeToRevealAction(
+private fun RevealScope.ActionWrapper(
     revealState: RevealState,
-    action: SwipeToRevealAction,
     backgroundColor: Color,
     contentColor: Color,
     shape: Shape,
+    content: @Composable RevealScope.() -> Unit,
 ) {
     // Change opacity of shape from 0% to 100% between 10% and 20% of the progress
     val shapeAlpha =
@@ -523,79 +486,87 @@
             ((-revealState.offset - revealOffset * 0.1f) / (0.1f * revealOffset))
                 .coerceIn(0.0f, 1.0f)
         else 1f
-    Row(
-        modifier = action.modifier
+    Box(
+        modifier = Modifier
             .graphicsLayer { alpha = shapeAlpha }
             .background(backgroundColor, shape)
             // Limit the incoming constraints to max height
             .heightIn(min = 0.dp, max = SwipeToRevealDefaults.ActionMaxHeight)
             // Then, fill the max height based on incoming constraints
             .fillMaxSize()
-            .clip(shape)
-            .clickable(
-                interactionSource = action.interactionSource,
-                indication = rememberRipple(),
-                role = Role.Button,
-                onClick = {
-                    revealState.lastActionType = action.actionType
-                    action.onClick()
-                }
-            ),
-        horizontalArrangement = Arrangement.Center,
-        verticalAlignment = Alignment.CenterVertically
+            .clip(shape),
+        contentAlignment = Alignment.Center
     ) {
         CompositionLocalProvider(
             LocalContentColor provides contentColor
         ) {
-            if (action.icon != null) {
-                ActionIcon(
-                    revealState = revealState,
-                    content = action.icon
-                )
-            }
-            if (action.label != null) {
-                ActionLabel(
-                    revealState = revealState,
-                    content = action.label
-                )
-            }
+            content()
         }
     }
 }
 
 @OptIn(ExperimentalWearFoundationApi::class, ExperimentalWearMaterialApi::class)
 @Composable
-private fun UndoAction(
-    revealState: RevealState,
-    undoAction: SwipeToRevealAction,
-    colors: SwipeToRevealActionColors
+private fun RevealScope.UndoActionWrapper(
+    colors: SwipeToRevealActionColors,
+    content: @Composable RevealScope.() -> Unit
 ) {
-    Row(
-        modifier = undoAction.modifier
+    Box(
+        modifier = Modifier
             .clip(MaterialTheme.shapes.small)
             .defaultMinSize(minHeight = 52.dp)
             .background(color = colors.undoActionBackgroundColor)
             .padding(
                 horizontal = SwipeToRevealDefaults.UndoButtonHorizontalPadding,
                 vertical = SwipeToRevealDefaults.UndoButtonVerticalPadding
-            )
-            .clickable(
-                interactionSource = undoAction.interactionSource,
-                indication = rememberRipple(),
-                role = Role.Button,
-                onClick = {
-                    revealState.lastActionType = undoAction.actionType
-                    undoAction.onClick()
-                }
             ),
-        verticalAlignment = Alignment.CenterVertically
+        contentAlignment = Alignment.Center
     ) {
         CompositionLocalProvider(
             LocalContentColor provides colors.undoActionContentColor
         ) {
-            undoAction.icon?.invoke()
-            Spacer(Modifier.size(5.dp))
-            undoAction.label?.invoke()
+            content()
+        }
+    }
+}
+
+@OptIn(ExperimentalWearFoundationApi::class)
+@Composable
+private fun RevealScope.ActionCommon(
+    revealState: RevealState,
+    onClick: () -> Unit,
+    modifier: Modifier = Modifier,
+    actionType: RevealActionType = RevealActionType.UndoAction,
+    interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
+    icon: (@Composable () -> Unit)? = null,
+    label: (@Composable () -> Unit)? = null,
+) {
+    Row(
+        modifier = modifier
+            .fillMaxSize()
+            .clickable(
+                interactionSource = interactionSource,
+                indication = rememberRipple(),
+                role = Role.Button,
+                onClick = {
+                    revealState.lastActionType = actionType
+                    onClick()
+                }
+            ),
+        horizontalArrangement = Arrangement.Center,
+        verticalAlignment = Alignment.CenterVertically
+    ) {
+        if (icon != null) {
+            ActionIcon(
+                revealState = revealState,
+                content = icon
+            )
+        }
+        if (label != null) {
+            ActionLabel(
+                revealState = revealState,
+                content = label
+            )
         }
     }
 }
diff --git a/wear/compose/compose-ui-tooling/build.gradle b/wear/compose/compose-ui-tooling/build.gradle
index 8c4de3f..c31d84f 100644
--- a/wear/compose/compose-ui-tooling/build.gradle
+++ b/wear/compose/compose-ui-tooling/build.gradle
@@ -28,7 +28,7 @@
 
     implementation(libs.kotlinStdlibCommon)
     implementation(project(":compose:ui:ui-tooling-preview"))
-    implementation("androidx.wear:wear-tooling-preview:1.0.0-alpha01")
+    implementation("androidx.wear:wear-tooling-preview:1.0.0-beta01")
 
     samples(project(":wear:compose:compose-material-samples"))
 }
diff --git a/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/SwipeToRevealDemo.kt b/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/SwipeToRevealDemo.kt
index 5a6ec03..677e050 100644
--- a/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/SwipeToRevealDemo.kt
+++ b/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/SwipeToRevealDemo.kt
@@ -58,6 +58,9 @@
 import androidx.wear.compose.material.SwipeToRevealCard
 import androidx.wear.compose.material.SwipeToRevealChip
 import androidx.wear.compose.material.SwipeToRevealDefaults
+import androidx.wear.compose.material.SwipeToRevealPrimaryAction
+import androidx.wear.compose.material.SwipeToRevealSecondaryAction
+import androidx.wear.compose.material.SwipeToRevealUndoAction
 import androidx.wear.compose.material.Text
 import androidx.wear.compose.material.dialog.Alert
 import androidx.wear.compose.material.dialog.Dialog
@@ -127,43 +130,55 @@
                                 )
                             },
                         revealState = revealState,
-                        primaryAction = SwipeToRevealDefaults.primaryAction(
-                            icon = {
-                                Icon(
-                                    SwipeToRevealDefaults.Delete,
-                                    contentDescription = "Delete"
-                                )
-                            },
-                            label = { Text(text = "Delete") }
-                        ) {
-                            // Remove the item
-                            deleteItem()
+                        onFullSwipe = { deleteItem() },
+                        primaryAction = {
+                            SwipeToRevealPrimaryAction(
+                                revealState = revealState,
+                                icon = {
+                                    Icon(
+                                        SwipeToRevealDefaults.Delete,
+                                        contentDescription = "Delete"
+                                    )
+                                },
+                                label = { Text(text = "Delete") },
+                                onClick = { deleteItem() },
+                            )
                         },
-                        secondaryAction = SwipeToRevealDefaults.secondaryAction(
-                            icon = {
-                                Icon(Icons.Outlined.Add, contentDescription = "Duplicate")
-                            },
-                        ) {
-                            addItem()
+                        secondaryAction = {
+                            SwipeToRevealSecondaryAction(
+                                revealState = revealState,
+                                content = {
+                                    Icon(Icons.Outlined.Add, contentDescription = "Duplicate")
+                                },
+                                onClick = { addItem() }
+                            )
                         },
-                        undoPrimaryAction = SwipeToRevealDefaults.undoAction(
-                            label = { Text("Undo Primary Action") }
-                        ) {
-                            coroutineScope.launch {
-                                // reset the state when undo is clicked
-                                revealState.animateTo(RevealValue.Covered)
-                                revealState.lastActionType = RevealActionType.None
-                            }
+                        undoPrimaryAction = {
+                            SwipeToRevealUndoAction(
+                                revealState = revealState,
+                                label = { Text("Undo Primary Action") },
+                                onClick = {
+                                    coroutineScope.launch {
+                                        // reset the state when undo is clicked
+                                        revealState.animateTo(RevealValue.Covered)
+                                        revealState.lastActionType = RevealActionType.None
+                                    }
+                                }
+                            )
                         },
-                        undoSecondaryAction = SwipeToRevealDefaults.undoAction(
-                            label = { Text("Undo Secondary Action") }
-                        ) {
-                            coroutineScope.launch {
-                                itemCount--
-                                // reset the state when undo is clicked
-                                revealState.animateTo(RevealValue.Covered)
-                                revealState.lastActionType = RevealActionType.None
-                            }
+                        undoSecondaryAction = {
+                            SwipeToRevealUndoAction(
+                                revealState = revealState,
+                                label = { Text("Undo Secondary Action") },
+                                onClick = {
+                                    coroutineScope.launch {
+                                        itemCount--
+                                        // reset the state when undo is clicked
+                                        revealState.animateTo(RevealValue.Covered)
+                                        revealState.lastActionType = RevealActionType.None
+                                    }
+                                }
+                            )
                         }
                     ) {
                         Chip(
@@ -231,11 +246,11 @@
     email: String,
     modifier: Modifier = Modifier
 ) {
-    val state = rememberRevealState()
+    val revealState = rememberRevealState()
     val coroutineScope = rememberCoroutineScope()
     var showDialog by remember { mutableStateOf(false) }
-    LaunchedEffect(state.currentValue) {
-        if (state.currentValue == RevealValue.Revealed) {
+    LaunchedEffect(revealState.currentValue) {
+        if (revealState.currentValue == RevealValue.Revealed) {
             delay(2000)
             expandableState.expanded = false
         }
@@ -243,7 +258,7 @@
     LaunchedEffect(showDialog) {
         if (!showDialog) {
             delay(500)
-            state.animateTo(RevealValue.Covered)
+            revealState.animateTo(RevealValue.Covered)
         }
     }
     ShowDialog(
@@ -256,7 +271,7 @@
             customActions = listOf(
                 CustomAccessibilityAction("Delete") {
                     coroutineScope.launch {
-                        state.animateTo(RevealValue.Revealed)
+                        revealState.animateTo(RevealValue.Revealed)
                     }
                     true
                 },
@@ -266,31 +281,50 @@
                 }
             )
         },
-        revealState = state,
-        primaryAction = SwipeToRevealDefaults.primaryAction(
-            icon = { Icon(SwipeToRevealDefaults.Delete, contentDescription = "Delete") },
-            label = { Text(text = "Delete") },
-            onClick = {
-                coroutineScope.launch {
-                    state.animateTo(RevealValue.Revealed)
-                }
+        revealState = revealState,
+        onFullSwipe = {
+            coroutineScope.launch {
+                revealState.animateTo(RevealValue.Revealed)
             }
-        ),
-        secondaryAction = SwipeToRevealDefaults.secondaryAction(
-            icon = { Icon(SwipeToRevealDefaults.MoreOptions, contentDescription = "More Options") },
-            actionType = RevealActionType.None, // reset click type since there is no undo for this
-            onClick = { showDialog = true }
-        ),
-        undoPrimaryAction = SwipeToRevealDefaults.undoAction(
-            label = { Text(text = "Undo") },
-            onClick = {
-                coroutineScope.launch {
-                    // reset the state when undo is clicked
-                    state.animateTo(RevealValue.Covered)
-                    state.lastActionType = RevealActionType.None
+        },
+        primaryAction = {
+            SwipeToRevealPrimaryAction(
+                revealState = revealState,
+                icon = { Icon(SwipeToRevealDefaults.Delete, contentDescription = "Delete") },
+                label = { Text(text = "Delete") },
+                onClick = {
+                    coroutineScope.launch {
+                        revealState.animateTo(RevealValue.Revealed)
+                    }
                 }
-            }
-        ),
+            )
+        },
+        secondaryAction = {
+            SwipeToRevealSecondaryAction(
+                revealState = revealState,
+                content = {
+                    Icon(SwipeToRevealDefaults.MoreOptions, contentDescription = "More Options")
+                },
+                onClick = {
+                    showDialog = true
+                    // reset click type since there is no undo for this
+                    revealState.lastActionType = RevealActionType.None
+                }
+            )
+        },
+        undoPrimaryAction = {
+            SwipeToRevealUndoAction(
+                revealState = revealState,
+                label = { Text(text = "Undo") },
+                onClick = {
+                    coroutineScope.launch {
+                        // reset the state when undo is clicked
+                        revealState.animateTo(RevealValue.Covered)
+                        revealState.lastActionType = RevealActionType.None
+                    }
+                }
+            )
+        },
     ) {
         AppCard(
             onClick = {},
diff --git a/wear/protolayout/protolayout-expression/src/main/java/androidx/wear/protolayout/expression/PlatformHealthSources.java b/wear/protolayout/protolayout-expression/src/main/java/androidx/wear/protolayout/expression/PlatformHealthSources.java
index 2df95fe..f5b767b 100644
--- a/wear/protolayout/protolayout-expression/src/main/java/androidx/wear/protolayout/expression/PlatformHealthSources.java
+++ b/wear/protolayout/protolayout-expression/src/main/java/androidx/wear/protolayout/expression/PlatformHealthSources.java
@@ -137,10 +137,7 @@
     @RequiresPermission(Manifest.permission.BODY_SENSORS)
     @NonNull
     public static DynamicFloat heartRateBpm() {
-        return new PlatformInt32Source.Builder()
-                .setSourceType(PLATFORM_INT32_SOURCE_TYPE_CURRENT_HEART_RATE)
-                .build()
-                .asFloat();
+        return DynamicFloat.from(Keys.HEART_RATE_BPM);
     }
 
     /**
@@ -171,9 +168,7 @@
     @RequiresPermission(Manifest.permission.ACTIVITY_RECOGNITION)
     @NonNull
     public static DynamicInt32 dailySteps() {
-        return new PlatformInt32Source.Builder()
-                .setSourceType(PLATFORM_INT32_SOURCE_TYPE_DAILY_STEP_COUNT)
-                .build();
+        return DynamicInt32.from(Keys.DAILY_STEPS);
     }
 
     /**
diff --git a/wear/protolayout/protolayout/api/current.txt b/wear/protolayout/protolayout/api/current.txt
index aaaddce..69123be5 100644
--- a/wear/protolayout/protolayout/api/current.txt
+++ b/wear/protolayout/protolayout/api/current.txt
@@ -124,7 +124,7 @@
   public static final class ColorBuilders.ColorStop {
     ctor public ColorBuilders.ColorStop(androidx.wear.protolayout.ColorBuilders.ColorProp);
     ctor public ColorBuilders.ColorStop(androidx.wear.protolayout.ColorBuilders.ColorProp, androidx.wear.protolayout.TypeBuilders.FloatProp);
-    method public androidx.wear.protolayout.ColorBuilders.ColorProp? getColor();
+    method public androidx.wear.protolayout.ColorBuilders.ColorProp getColor();
     method public androidx.wear.protolayout.TypeBuilders.FloatProp? getOffset();
   }
 
diff --git a/wear/protolayout/protolayout/api/restricted_current.txt b/wear/protolayout/protolayout/api/restricted_current.txt
index aaaddce..69123be5 100644
--- a/wear/protolayout/protolayout/api/restricted_current.txt
+++ b/wear/protolayout/protolayout/api/restricted_current.txt
@@ -124,7 +124,7 @@
   public static final class ColorBuilders.ColorStop {
     ctor public ColorBuilders.ColorStop(androidx.wear.protolayout.ColorBuilders.ColorProp);
     ctor public ColorBuilders.ColorStop(androidx.wear.protolayout.ColorBuilders.ColorProp, androidx.wear.protolayout.TypeBuilders.FloatProp);
-    method public androidx.wear.protolayout.ColorBuilders.ColorProp? getColor();
+    method public androidx.wear.protolayout.ColorBuilders.ColorProp getColor();
     method public androidx.wear.protolayout.TypeBuilders.FloatProp? getOffset();
   }
 
diff --git a/wear/protolayout/protolayout/src/main/java/androidx/wear/protolayout/ColorBuilders.java b/wear/protolayout/protolayout/src/main/java/androidx/wear/protolayout/ColorBuilders.java
index 4eac549..f08e26b 100644
--- a/wear/protolayout/protolayout/src/main/java/androidx/wear/protolayout/ColorBuilders.java
+++ b/wear/protolayout/protolayout/src/main/java/androidx/wear/protolayout/ColorBuilders.java
@@ -214,13 +214,9 @@
          *
          * @since 1.3
          */
-        @Nullable
+        @NonNull
         public ColorProp getColor() {
-            if (mImpl.hasColor()) {
-                return ColorProp.fromProto(mImpl.getColor());
-            } else {
-                return null;
-            }
+            return ColorProp.fromProto(mImpl.getColor());
         }
 
         /**