Merge changes I1eb2d81e,I3f39e521 into androidx-main

* changes:
  Add @JvmStatic to companion and object class functions.
  Move util functions in XProcessingStep to be extension functions.
diff --git a/.github/workflows/presubmit.yml b/.github/workflows/presubmit.yml
index da39dab..2bd8dca 100644
--- a/.github/workflows/presubmit.yml
+++ b/.github/workflows/presubmit.yml
@@ -84,12 +84,6 @@
         id: changed-files
         uses: androidx/changed-files-action@main
 
-      - name: Check invalid suppressions
-        run: |
-          set -x
-          echo ${{ steps.changed-files.outputs.files }}
-          ./development/checkInvalidSuppress.py -f ${{ steps.changed-files.outputs.files }}
-
       - name: "Warn on missing updateApi"
         run: |
           set -x
diff --git a/PREUPLOAD.cfg b/PREUPLOAD.cfg
index 89fe9cb..2b320a9 100644
--- a/PREUPLOAD.cfg
+++ b/PREUPLOAD.cfg
@@ -1,7 +1,6 @@
 [Hook Scripts]
 checkstyle_hook = ${REPO_ROOT}/prebuilts/checkstyle/checkstyle.py --sha ${PREUPLOAD_COMMIT} -c ${REPO_ROOT}/frameworks/support/development/checkstyle/config/support-lib.xml -p development/checkstyle/prebuilt/com.android.support.checkstyle.jar
 ktlint_hook = ${REPO_ROOT}/frameworks/support/gradlew -q -p ${REPO_ROOT}/frameworks/support --continue :ktlintCheckFile --file=${PREUPLOAD_FILES_PREFIXED}
-invalid_suppress_hook = ${REPO_ROOT}/frameworks/support/development/checkInvalidSuppress.py ${PREUPLOAD_FILES}
 warn_check_api = ${REPO_ROOT}/frameworks/support/development/apilint.py -f ${PREUPLOAD_FILES}
 
 [Builtin Hooks]
diff --git a/activity/activity-compose/src/main/java/androidx/activity/compose/ActivityResultRegistry.kt b/activity/activity-compose/src/main/java/androidx/activity/compose/ActivityResultRegistry.kt
index b53189a..6055636 100644
--- a/activity/activity-compose/src/main/java/androidx/activity/compose/ActivityResultRegistry.kt
+++ b/activity/activity-compose/src/main/java/androidx/activity/compose/ActivityResultRegistry.kt
@@ -150,7 +150,7 @@
  * This launcher does not support the [unregister] function. Attempting to use [unregister] will
  * result in an [IllegalStateException].
  *
- * @param <I> type of the input required to launch
+ * @param I type of the input required to launch
  */
 public class ManagedActivityResultLauncher<I, O> internal constructor(
     private val launcher: ActivityResultLauncherHolder<I>,
diff --git a/activity/activity/api/current.txt b/activity/activity/api/current.txt
index 9e49deb..4d4be33 100644
--- a/activity/activity/api/current.txt
+++ b/activity/activity/api/current.txt
@@ -160,7 +160,7 @@
     method public final Boolean parseResult(int, android.content.Intent?);
   }
 
-  public static class ActivityResultContracts.CreateDocument extends androidx.activity.result.contract.ActivityResultContract<java.lang.String,android.net.Uri> {
+  @RequiresApi(19) public static class ActivityResultContracts.CreateDocument extends androidx.activity.result.contract.ActivityResultContract<java.lang.String,android.net.Uri> {
     ctor public ActivityResultContracts.CreateDocument();
     method @CallSuper public android.content.Intent createIntent(android.content.Context, String);
     method public final androidx.activity.result.contract.ActivityResultContract.SynchronousResult<android.net.Uri!>? getSynchronousResult(android.content.Context, String);
@@ -174,28 +174,28 @@
     method public final android.net.Uri? parseResult(int, android.content.Intent?);
   }
 
-  public static class ActivityResultContracts.GetMultipleContents extends androidx.activity.result.contract.ActivityResultContract<java.lang.String,java.util.List<android.net.Uri>> {
+  @RequiresApi(18) public static class ActivityResultContracts.GetMultipleContents extends androidx.activity.result.contract.ActivityResultContract<java.lang.String,java.util.List<android.net.Uri>> {
     ctor public ActivityResultContracts.GetMultipleContents();
     method @CallSuper public android.content.Intent createIntent(android.content.Context, String);
     method public final androidx.activity.result.contract.ActivityResultContract.SynchronousResult<java.util.List<android.net.Uri!>!>? getSynchronousResult(android.content.Context, String);
     method public final java.util.List<android.net.Uri!> parseResult(int, android.content.Intent?);
   }
 
-  public static class ActivityResultContracts.OpenDocument extends androidx.activity.result.contract.ActivityResultContract<java.lang.String[],android.net.Uri> {
+  @RequiresApi(19) public static class ActivityResultContracts.OpenDocument extends androidx.activity.result.contract.ActivityResultContract<java.lang.String[],android.net.Uri> {
     ctor public ActivityResultContracts.OpenDocument();
     method @CallSuper public android.content.Intent createIntent(android.content.Context, String![]);
     method public final androidx.activity.result.contract.ActivityResultContract.SynchronousResult<android.net.Uri!>? getSynchronousResult(android.content.Context, String![]);
     method public final android.net.Uri? parseResult(int, android.content.Intent?);
   }
 
-  public static class ActivityResultContracts.OpenDocumentTree extends androidx.activity.result.contract.ActivityResultContract<android.net.Uri,android.net.Uri> {
+  @RequiresApi(21) public static class ActivityResultContracts.OpenDocumentTree extends androidx.activity.result.contract.ActivityResultContract<android.net.Uri,android.net.Uri> {
     ctor public ActivityResultContracts.OpenDocumentTree();
     method @CallSuper public android.content.Intent createIntent(android.content.Context, android.net.Uri?);
     method public final androidx.activity.result.contract.ActivityResultContract.SynchronousResult<android.net.Uri!>? getSynchronousResult(android.content.Context, android.net.Uri?);
     method public final android.net.Uri? parseResult(int, android.content.Intent?);
   }
 
-  public static class ActivityResultContracts.OpenMultipleDocuments extends androidx.activity.result.contract.ActivityResultContract<java.lang.String[],java.util.List<android.net.Uri>> {
+  @RequiresApi(19) public static class ActivityResultContracts.OpenMultipleDocuments extends androidx.activity.result.contract.ActivityResultContract<java.lang.String[],java.util.List<android.net.Uri>> {
     ctor public ActivityResultContracts.OpenMultipleDocuments();
     method @CallSuper public android.content.Intent createIntent(android.content.Context, String![]);
     method public final androidx.activity.result.contract.ActivityResultContract.SynchronousResult<java.util.List<android.net.Uri!>!>? getSynchronousResult(android.content.Context, String![]);
diff --git a/activity/activity/api/public_plus_experimental_current.txt b/activity/activity/api/public_plus_experimental_current.txt
index 40c43ad..5ae7716 100644
--- a/activity/activity/api/public_plus_experimental_current.txt
+++ b/activity/activity/api/public_plus_experimental_current.txt
@@ -159,7 +159,7 @@
     method public final Boolean parseResult(int, android.content.Intent?);
   }
 
-  public static class ActivityResultContracts.CreateDocument extends androidx.activity.result.contract.ActivityResultContract<java.lang.String,android.net.Uri> {
+  @RequiresApi(19) public static class ActivityResultContracts.CreateDocument extends androidx.activity.result.contract.ActivityResultContract<java.lang.String,android.net.Uri> {
     ctor public ActivityResultContracts.CreateDocument();
     method @CallSuper public android.content.Intent createIntent(android.content.Context, String);
     method public final androidx.activity.result.contract.ActivityResultContract.SynchronousResult<android.net.Uri!>? getSynchronousResult(android.content.Context, String);
@@ -173,28 +173,28 @@
     method public final android.net.Uri? parseResult(int, android.content.Intent?);
   }
 
-  public static class ActivityResultContracts.GetMultipleContents extends androidx.activity.result.contract.ActivityResultContract<java.lang.String,java.util.List<android.net.Uri>> {
+  @RequiresApi(18) public static class ActivityResultContracts.GetMultipleContents extends androidx.activity.result.contract.ActivityResultContract<java.lang.String,java.util.List<android.net.Uri>> {
     ctor public ActivityResultContracts.GetMultipleContents();
     method @CallSuper public android.content.Intent createIntent(android.content.Context, String);
     method public final androidx.activity.result.contract.ActivityResultContract.SynchronousResult<java.util.List<android.net.Uri!>!>? getSynchronousResult(android.content.Context, String);
     method public final java.util.List<android.net.Uri!> parseResult(int, android.content.Intent?);
   }
 
-  public static class ActivityResultContracts.OpenDocument extends androidx.activity.result.contract.ActivityResultContract<java.lang.String[],android.net.Uri> {
+  @RequiresApi(19) public static class ActivityResultContracts.OpenDocument extends androidx.activity.result.contract.ActivityResultContract<java.lang.String[],android.net.Uri> {
     ctor public ActivityResultContracts.OpenDocument();
     method @CallSuper public android.content.Intent createIntent(android.content.Context, String![]);
     method public final androidx.activity.result.contract.ActivityResultContract.SynchronousResult<android.net.Uri!>? getSynchronousResult(android.content.Context, String![]);
     method public final android.net.Uri? parseResult(int, android.content.Intent?);
   }
 
-  public static class ActivityResultContracts.OpenDocumentTree extends androidx.activity.result.contract.ActivityResultContract<android.net.Uri,android.net.Uri> {
+  @RequiresApi(21) public static class ActivityResultContracts.OpenDocumentTree extends androidx.activity.result.contract.ActivityResultContract<android.net.Uri,android.net.Uri> {
     ctor public ActivityResultContracts.OpenDocumentTree();
     method @CallSuper public android.content.Intent createIntent(android.content.Context, android.net.Uri?);
     method public final androidx.activity.result.contract.ActivityResultContract.SynchronousResult<android.net.Uri!>? getSynchronousResult(android.content.Context, android.net.Uri?);
     method public final android.net.Uri? parseResult(int, android.content.Intent?);
   }
 
-  public static class ActivityResultContracts.OpenMultipleDocuments extends androidx.activity.result.contract.ActivityResultContract<java.lang.String[],java.util.List<android.net.Uri>> {
+  @RequiresApi(19) public static class ActivityResultContracts.OpenMultipleDocuments extends androidx.activity.result.contract.ActivityResultContract<java.lang.String[],java.util.List<android.net.Uri>> {
     ctor public ActivityResultContracts.OpenMultipleDocuments();
     method @CallSuper public android.content.Intent createIntent(android.content.Context, String![]);
     method public final androidx.activity.result.contract.ActivityResultContract.SynchronousResult<java.util.List<android.net.Uri!>!>? getSynchronousResult(android.content.Context, String![]);
diff --git a/activity/activity/api/restricted_current.txt b/activity/activity/api/restricted_current.txt
index 40c43ad..5ae7716 100644
--- a/activity/activity/api/restricted_current.txt
+++ b/activity/activity/api/restricted_current.txt
@@ -159,7 +159,7 @@
     method public final Boolean parseResult(int, android.content.Intent?);
   }
 
-  public static class ActivityResultContracts.CreateDocument extends androidx.activity.result.contract.ActivityResultContract<java.lang.String,android.net.Uri> {
+  @RequiresApi(19) public static class ActivityResultContracts.CreateDocument extends androidx.activity.result.contract.ActivityResultContract<java.lang.String,android.net.Uri> {
     ctor public ActivityResultContracts.CreateDocument();
     method @CallSuper public android.content.Intent createIntent(android.content.Context, String);
     method public final androidx.activity.result.contract.ActivityResultContract.SynchronousResult<android.net.Uri!>? getSynchronousResult(android.content.Context, String);
@@ -173,28 +173,28 @@
     method public final android.net.Uri? parseResult(int, android.content.Intent?);
   }
 
-  public static class ActivityResultContracts.GetMultipleContents extends androidx.activity.result.contract.ActivityResultContract<java.lang.String,java.util.List<android.net.Uri>> {
+  @RequiresApi(18) public static class ActivityResultContracts.GetMultipleContents extends androidx.activity.result.contract.ActivityResultContract<java.lang.String,java.util.List<android.net.Uri>> {
     ctor public ActivityResultContracts.GetMultipleContents();
     method @CallSuper public android.content.Intent createIntent(android.content.Context, String);
     method public final androidx.activity.result.contract.ActivityResultContract.SynchronousResult<java.util.List<android.net.Uri!>!>? getSynchronousResult(android.content.Context, String);
     method public final java.util.List<android.net.Uri!> parseResult(int, android.content.Intent?);
   }
 
-  public static class ActivityResultContracts.OpenDocument extends androidx.activity.result.contract.ActivityResultContract<java.lang.String[],android.net.Uri> {
+  @RequiresApi(19) public static class ActivityResultContracts.OpenDocument extends androidx.activity.result.contract.ActivityResultContract<java.lang.String[],android.net.Uri> {
     ctor public ActivityResultContracts.OpenDocument();
     method @CallSuper public android.content.Intent createIntent(android.content.Context, String![]);
     method public final androidx.activity.result.contract.ActivityResultContract.SynchronousResult<android.net.Uri!>? getSynchronousResult(android.content.Context, String![]);
     method public final android.net.Uri? parseResult(int, android.content.Intent?);
   }
 
-  public static class ActivityResultContracts.OpenDocumentTree extends androidx.activity.result.contract.ActivityResultContract<android.net.Uri,android.net.Uri> {
+  @RequiresApi(21) public static class ActivityResultContracts.OpenDocumentTree extends androidx.activity.result.contract.ActivityResultContract<android.net.Uri,android.net.Uri> {
     ctor public ActivityResultContracts.OpenDocumentTree();
     method @CallSuper public android.content.Intent createIntent(android.content.Context, android.net.Uri?);
     method public final androidx.activity.result.contract.ActivityResultContract.SynchronousResult<android.net.Uri!>? getSynchronousResult(android.content.Context, android.net.Uri?);
     method public final android.net.Uri? parseResult(int, android.content.Intent?);
   }
 
-  public static class ActivityResultContracts.OpenMultipleDocuments extends androidx.activity.result.contract.ActivityResultContract<java.lang.String[],java.util.List<android.net.Uri>> {
+  @RequiresApi(19) public static class ActivityResultContracts.OpenMultipleDocuments extends androidx.activity.result.contract.ActivityResultContract<java.lang.String[],java.util.List<android.net.Uri>> {
     ctor public ActivityResultContracts.OpenMultipleDocuments();
     method @CallSuper public android.content.Intent createIntent(android.content.Context, String![]);
     method public final androidx.activity.result.contract.ActivityResultContract.SynchronousResult<java.util.List<android.net.Uri!>!>? getSynchronousResult(android.content.Context, String![]);
diff --git a/activity/activity/lint-baseline.xml b/activity/activity/lint-baseline.xml
deleted file mode 100644
index 420b07e..0000000
--- a/activity/activity/lint-baseline.xml
+++ /dev/null
@@ -1,81 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-    <issue
-        id="BanTargetApiAnnotation"
-        message="Uses @TargetApi annotation"
-        errorLine1="    @TargetApi(18)"
-        errorLine2="    ~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/activity/result/contract/ActivityResultContracts.java"
-            line="459"
-            column="5"/>
-    </issue>
-
-    <issue
-        id="BanTargetApiAnnotation"
-        message="Uses @TargetApi annotation"
-        errorLine1="    @TargetApi(19)"
-        errorLine2="    ~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/activity/result/contract/ActivityResultContracts.java"
-            line="523"
-            column="5"/>
-    </issue>
-
-    <issue
-        id="BanTargetApiAnnotation"
-        message="Uses @TargetApi annotation"
-        errorLine1="    @TargetApi(19)"
-        errorLine2="    ~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/activity/result/contract/ActivityResultContracts.java"
-            line="561"
-            column="5"/>
-    </issue>
-
-    <issue
-        id="BanTargetApiAnnotation"
-        message="Uses @TargetApi annotation"
-        errorLine1="    @TargetApi(21)"
-        errorLine2="    ~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/activity/result/contract/ActivityResultContracts.java"
-            line="603"
-            column="5"/>
-    </issue>
-
-    <issue
-        id="BanTargetApiAnnotation"
-        message="Uses @TargetApi annotation"
-        errorLine1="    @TargetApi(19)"
-        errorLine2="    ~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/activity/result/contract/ActivityResultContracts.java"
-            line="642"
-            column="5"/>
-    </issue>
-
-    <issue
-        id="ClassVerificationFailure"
-        message="This call references a method added in API level 16; however, the containing class androidx.activity.result.contract.ActivityResultContracts.GetMultipleContents is reachable from earlier API levels and will fail run-time class verification."
-        errorLine1="            ClipData clipData = intent.getClipData();"
-        errorLine2="                                       ~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/activity/result/contract/ActivityResultContracts.java"
-            line="497"
-            column="40"/>
-    </issue>
-
-    <issue
-        id="ClassVerificationFailure"
-        message="This call references a method added in API level 19; however, the containing class null is reachable from earlier API levels and will fail run-time class verification."
-        errorLine1="                            decor.cancelPendingInputEvents();"
-        errorLine2="                                  ~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/activity/ComponentActivity.java"
-            line="236"
-            column="35"/>
-    </issue>
-
-</issues>
diff --git a/activity/activity/src/main/java/androidx/activity/ComponentActivity.java b/activity/activity/src/main/java/androidx/activity/ComponentActivity.java
index 3e81c8c..a0a6e13 100644
--- a/activity/activity/src/main/java/androidx/activity/ComponentActivity.java
+++ b/activity/activity/src/main/java/androidx/activity/ComponentActivity.java
@@ -61,6 +61,7 @@
 import androidx.annotation.MainThread;
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
+import androidx.annotation.RequiresApi;
 import androidx.core.app.ActivityCompat;
 import androidx.core.app.ActivityOptionsCompat;
 import androidx.core.content.ContextCompat;
@@ -233,7 +234,7 @@
                         Window window = getWindow();
                         final View decor = window != null ? window.peekDecorView() : null;
                         if (decor != null) {
-                            decor.cancelPendingInputEvents();
+                            Api19Impl.cancelPendingInputEvents(decor);
                         }
                     }
                 }
@@ -703,4 +704,14 @@
             Trace.endSection();
         }
     }
+
+    @RequiresApi(19)
+    static class Api19Impl {
+        private Api19Impl() { }
+
+        static void cancelPendingInputEvents(View view) {
+            view.cancelPendingInputEvents();
+        }
+
+    }
 }
diff --git a/activity/activity/src/main/java/androidx/activity/result/contract/ActivityResultContracts.java b/activity/activity/src/main/java/androidx/activity/result/contract/ActivityResultContracts.java
index 27d34cf..c488640 100644
--- a/activity/activity/src/main/java/androidx/activity/result/contract/ActivityResultContracts.java
+++ b/activity/activity/src/main/java/androidx/activity/result/contract/ActivityResultContracts.java
@@ -20,7 +20,6 @@
 
 import static java.util.Collections.emptyMap;
 
-import android.annotation.TargetApi;
 import android.app.Activity;
 import android.content.ClipData;
 import android.content.Context;
@@ -40,6 +39,7 @@
 import androidx.annotation.CallSuper;
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
+import androidx.annotation.RequiresApi;
 import androidx.collection.ArrayMap;
 import androidx.core.app.ActivityOptionsCompat;
 import androidx.core.content.ContextCompat;
@@ -494,7 +494,7 @@
      * This can be extended to override {@link #createIntent} if you wish to pass additional
      * extras to the Intent created by {@code super.createIntent()}.
      */
-    @TargetApi(18)
+    @RequiresApi(18)
     public static class GetMultipleContents extends ActivityResultContract<String, List<Uri>> {
 
         @CallSuper
@@ -558,7 +558,7 @@
      *
      * @see DocumentsContract
      */
-    @TargetApi(19)
+    @RequiresApi(19)
     public static class OpenDocument extends ActivityResultContract<String[], Uri> {
 
         @CallSuper
@@ -596,7 +596,7 @@
      *
      * @see DocumentsContract
      */
-    @TargetApi(19)
+    @RequiresApi(19)
     public static class OpenMultipleDocuments extends ActivityResultContract<String[], List<Uri>> {
 
         @CallSuper
@@ -638,7 +638,7 @@
      * @see DocumentsContract#buildDocumentUriUsingTree
      * @see DocumentsContract#buildChildDocumentsUriUsingTree
      */
-    @TargetApi(21)
+    @RequiresApi(21)
     public static class OpenDocumentTree extends ActivityResultContract<Uri, Uri> {
 
         @CallSuper
@@ -677,7 +677,7 @@
      * This can be extended to override {@link #createIntent} if you wish to pass additional
      * extras to the Intent created by {@code super.createIntent()}.
      */
-    @TargetApi(19)
+    @RequiresApi(19)
     public static class CreateDocument extends ActivityResultContract<String, Uri> {
 
         @CallSuper
diff --git a/activity/integration-tests/testapp/lint-baseline.xml b/activity/integration-tests/testapp/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/activity/integration-tests/testapp/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/activity/integration-tests/testapp/src/main/java/androidx/activity/integration/testapp/MainActivity.kt b/activity/integration-tests/testapp/src/main/java/androidx/activity/integration/testapp/MainActivity.kt
index 09bc3e9..29a4375 100644
--- a/activity/integration-tests/testapp/src/main/java/androidx/activity/integration/testapp/MainActivity.kt
+++ b/activity/integration-tests/testapp/src/main/java/androidx/activity/integration/testapp/MainActivity.kt
@@ -33,6 +33,7 @@
 import android.widget.LinearLayout.VERTICAL
 import android.widget.Toast
 import androidx.activity.ComponentActivity
+import androidx.activity.result.ActivityResultLauncher
 import androidx.activity.result.IntentSenderRequest
 import androidx.activity.result.contract.ActivityResultContracts
 import androidx.activity.result.contract.ActivityResultContracts.CaptureVideo
@@ -70,13 +71,7 @@
         toast("Got image: $uri")
     }
 
-    val openDocuments = registerForActivityResult(OpenMultipleDocuments()) { uris ->
-        var docs = ""
-        uris.forEach {
-            docs += "uri: $it \n"
-        }
-        toast("Got documents: $docs")
-    }
+    lateinit var openDocuments: ActivityResultLauncher<Array<String>>
 
     val intentSender = registerForActivityResult(
         ActivityResultContracts
@@ -88,6 +83,16 @@
     override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
 
+        if (android.os.Build.VERSION.SDK_INT >= 19) {
+            openDocuments = registerForActivityResult(OpenMultipleDocuments()) { uris ->
+                var docs = ""
+                uris.forEach {
+                    docs += "uri: $it \n"
+                }
+                toast("Got documents: $docs")
+            }
+        }
+
         setContentView {
             add(::LinearLayout) {
                 orientation = VERTICAL
@@ -111,8 +116,10 @@
                 button("Pick an image") {
                     getContent.launch("image/*")
                 }
-                button("Open documents") {
-                    openDocuments.launch(arrayOf("*/*"))
+                if (android.os.Build.VERSION.SDK_INT >= 19) {
+                    button("Open documents") {
+                        openDocuments.launch(arrayOf("*/*"))
+                    }
                 }
                 button("Start IntentSender") {
                     val request = IntentSenderRequest.Builder(
diff --git a/ads/ads-identifier-benchmark/lint-baseline.xml b/ads/ads-identifier-benchmark/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/ads/ads-identifier-benchmark/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/ads/ads-identifier-provider/lint-baseline.xml b/ads/ads-identifier-provider/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/ads/ads-identifier-provider/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/ads/ads-identifier-testing/lint-baseline.xml b/ads/ads-identifier-testing/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/ads/ads-identifier-testing/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/ads/ads-identifier/lint-baseline.xml b/ads/ads-identifier/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/ads/ads-identifier/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/appcompat/appcompat-benchmark/lint-baseline.xml b/appcompat/appcompat-benchmark/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/appcompat/appcompat-benchmark/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/appcompat/appcompat-lint/lint-baseline.xml b/appcompat/appcompat-lint/lint-baseline.xml
deleted file mode 100644
index 8794ae8..0000000
--- a/appcompat/appcompat-lint/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" version="4.2.0-beta06">
-
-</issues>
diff --git a/appcompat/appcompat/build.gradle b/appcompat/appcompat/build.gradle
index 4cfa8d5..546496f 100644
--- a/appcompat/appcompat/build.gradle
+++ b/appcompat/appcompat/build.gradle
@@ -18,7 +18,7 @@
     implementation("androidx.collection:collection:1.0.0")
     api("androidx.cursoradapter:cursoradapter:1.0.0")
     api("androidx.activity:activity:1.2.3")
-    api("androidx.fragment:fragment:1.3.3")
+    api("androidx.fragment:fragment:1.3.4")
     api(project(":appcompat:appcompat-resources"))
     api("androidx.drawerlayout:drawerlayout:1.0.0")
     implementation("androidx.lifecycle:lifecycle-runtime:2.3.1")
diff --git a/appcompat/appcompat/src/main/java/androidx/appcompat/app/AppCompatDelegateImpl.java b/appcompat/appcompat/src/main/java/androidx/appcompat/app/AppCompatDelegateImpl.java
index cfe1931..08f06a8 100644
--- a/appcompat/appcompat/src/main/java/androidx/appcompat/app/AppCompatDelegateImpl.java
+++ b/appcompat/appcompat/src/main/java/androidx/appcompat/app/AppCompatDelegateImpl.java
@@ -592,16 +592,17 @@
         if (ab != null) {
             ab.onDestroy();
         }
+        mActionBar = null;
 
         if (toolbar != null) {
             final ToolbarActionBar tbab = new ToolbarActionBar(toolbar, getTitle(),
-                    mWindow.getCallback());
+                    mAppCompatWindowCallback);
             mActionBar = tbab;
-            mWindow.setCallback(tbab.getWrappedWindowCallback());
+            // Set the nested action bar window callback so that it receive menu events
+            mAppCompatWindowCallback.setActionBarCallback(tbab.mMenuCallback);
         } else {
-            mActionBar = null;
-            // Re-set the original window callback since we may have already set a Toolbar wrapper
-            mWindow.setCallback(mAppCompatWindowCallback);
+            // Clear the nested action bar window callback
+            mAppCompatWindowCallback.setActionBarCallback(null);
         }
 
         invalidateOptionsMenu();
@@ -3069,12 +3070,28 @@
         }
     }
 
+    /**
+     * Interface which allows ToolbarActionBar to receive menu events without
+     * needing to wrap the Window.Callback
+     */
+    interface ActionBarMenuCallback {
+        boolean onPreparePanel(int featureId);
+
+        @Nullable
+        View onCreatePanelView(int featureId);
+    }
 
     class AppCompatWindowCallback extends WindowCallbackWrapper {
+        private ActionBarMenuCallback mActionBarCallback;
+
         AppCompatWindowCallback(Window.Callback callback) {
             super(callback);
         }
 
+        void setActionBarCallback(@Nullable ActionBarMenuCallback callback) {
+            mActionBarCallback = callback;
+        }
+
         @Override
         public boolean dispatchKeyEvent(KeyEvent event) {
             return AppCompatDelegateImpl.this.dispatchKeyEvent(event)
@@ -3098,6 +3115,17 @@
         }
 
         @Override
+        public View onCreatePanelView(int featureId) {
+            if (mActionBarCallback != null) {
+                View created = mActionBarCallback.onCreatePanelView(featureId);
+                if (created != null) {
+                    return created;
+                }
+            }
+            return super.onCreatePanelView(featureId);
+        }
+
+        @Override
         public void onContentChanged() {
             // We purposely do not propagate this call as this is called when we install
             // our sub-decor rather than the user's content
@@ -3121,7 +3149,13 @@
                 mb.setOverrideVisibleItems(true);
             }
 
-            final boolean handled = super.onPreparePanel(featureId, view, menu);
+            boolean handled = false;
+            if (mActionBarCallback != null && mActionBarCallback.onPreparePanel(featureId)) {
+                handled = true;
+            }
+            if (!handled) {
+                handled = super.onPreparePanel(featureId, view, menu);
+            }
 
             if (mb != null) {
                 mb.setOverrideVisibleItems(false);
diff --git a/appcompat/appcompat/src/main/java/androidx/appcompat/app/ToolbarActionBar.java b/appcompat/appcompat/src/main/java/androidx/appcompat/app/ToolbarActionBar.java
index f898efc..d2565a5 100644
--- a/appcompat/appcompat/src/main/java/androidx/appcompat/app/ToolbarActionBar.java
+++ b/appcompat/appcompat/src/main/java/androidx/appcompat/app/ToolbarActionBar.java
@@ -32,20 +32,21 @@
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
-import androidx.appcompat.view.WindowCallbackWrapper;
 import androidx.appcompat.view.menu.MenuBuilder;
 import androidx.appcompat.view.menu.MenuPresenter;
 import androidx.appcompat.widget.DecorToolbar;
 import androidx.appcompat.widget.Toolbar;
 import androidx.appcompat.widget.ToolbarWidgetWrapper;
+import androidx.core.util.Preconditions;
 import androidx.core.view.ViewCompat;
 
 import java.util.ArrayList;
 
 class ToolbarActionBar extends ActionBar {
-    DecorToolbar mDecorToolbar;
+    final DecorToolbar mDecorToolbar;
+    final Window.Callback mWindowCallback;
+    final AppCompatDelegateImpl.ActionBarMenuCallback mMenuCallback;
     boolean mToolbarMenuPrepared;
-    Window.Callback mWindowCallback;
     private boolean mMenuCallbackSet;
 
     private boolean mLastMenuVisibility;
@@ -66,16 +67,17 @@
                 }
             };
 
-    ToolbarActionBar(Toolbar toolbar, CharSequence title, Window.Callback windowCallback) {
+    ToolbarActionBar(@NonNull Toolbar toolbar, @Nullable CharSequence title,
+            @NonNull Window.Callback windowCallback) {
+        Preconditions.checkNotNull(toolbar);
         mDecorToolbar = new ToolbarWidgetWrapper(toolbar, false);
-        mWindowCallback = new ToolbarCallbackWrapper(windowCallback);
-        mDecorToolbar.setWindowCallback(mWindowCallback);
+
+        mWindowCallback = Preconditions.checkNotNull(windowCallback);
+        mDecorToolbar.setWindowCallback(windowCallback);
         toolbar.setOnMenuItemClickListener(mMenuClicker);
         mDecorToolbar.setWindowTitle(title);
-    }
 
-    public Window.Callback getWrappedWindowCallback() {
-        return mWindowCallback;
+        mMenuCallback = new ToolbarMenuCallback();
     }
 
     @Override
@@ -513,19 +515,18 @@
         }
     }
 
-    private class ToolbarCallbackWrapper extends WindowCallbackWrapper {
-        public ToolbarCallbackWrapper(Window.Callback wrapped) {
-            super(wrapped);
-        }
+    private class ToolbarMenuCallback implements AppCompatDelegateImpl.ActionBarMenuCallback {
+        ToolbarMenuCallback() {}
 
         @Override
-        public boolean onPreparePanel(int featureId, View view, Menu menu) {
-            final boolean result = super.onPreparePanel(featureId, view, menu);
-            if (result && !mToolbarMenuPrepared) {
+        public boolean onPreparePanel(int featureId) {
+            if (featureId == Window.FEATURE_OPTIONS_PANEL && !mToolbarMenuPrepared) {
                 mDecorToolbar.setMenuPrepared();
                 mToolbarMenuPrepared = true;
+                // We don't want the stop the AppCompat/Window receiving the event, so don't
+                // return true
             }
-            return result;
+            return false;
         }
 
         @Override
@@ -536,7 +537,7 @@
                 // preparing a default one.
                 return new View(mDecorToolbar.getContext());
             }
-            return super.onCreatePanelView(featureId);
+            return null;
         }
     }
 
@@ -557,11 +558,8 @@
 
         @Override
         public boolean onOpenSubMenu(@NonNull MenuBuilder subMenu) {
-            if (mWindowCallback != null) {
-                mWindowCallback.onMenuOpened(AppCompatDelegate.FEATURE_SUPPORT_ACTION_BAR, subMenu);
-                return true;
-            }
-            return false;
+            mWindowCallback.onMenuOpened(AppCompatDelegate.FEATURE_SUPPORT_ACTION_BAR, subMenu);
+            return true;
         }
 
         @Override
@@ -572,9 +570,7 @@
 
             mClosingActionMenu = true;
             mDecorToolbar.dismissPopupMenus();
-            if (mWindowCallback != null) {
-                mWindowCallback.onPanelClosed(AppCompatDelegate.FEATURE_SUPPORT_ACTION_BAR, menu);
-            }
+            mWindowCallback.onPanelClosed(AppCompatDelegate.FEATURE_SUPPORT_ACTION_BAR, menu);
             mClosingActionMenu = false;
         }
     }
@@ -591,13 +587,10 @@
 
         @Override
         public void onMenuModeChange(@NonNull MenuBuilder menu) {
-            if (mWindowCallback != null) {
-                if (mDecorToolbar.isOverflowMenuShowing()) {
-                    mWindowCallback.onPanelClosed(AppCompatDelegate.FEATURE_SUPPORT_ACTION_BAR, menu);
-                } else if (mWindowCallback.onPreparePanel(Window.FEATURE_OPTIONS_PANEL,
-                        null, menu)) {
-                    mWindowCallback.onMenuOpened(AppCompatDelegate.FEATURE_SUPPORT_ACTION_BAR, menu);
-                }
+            if (mDecorToolbar.isOverflowMenuShowing()) {
+                mWindowCallback.onPanelClosed(AppCompatDelegate.FEATURE_SUPPORT_ACTION_BAR, menu);
+            } else if (mWindowCallback.onPreparePanel(Window.FEATURE_OPTIONS_PANEL, null, menu)) {
+                mWindowCallback.onMenuOpened(AppCompatDelegate.FEATURE_SUPPORT_ACTION_BAR, menu);
             }
         }
     }
diff --git a/appcompat/integration-tests/receive-content-testapp/lint-baseline.xml b/appcompat/integration-tests/receive-content-testapp/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/appcompat/integration-tests/receive-content-testapp/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/appsearch/appsearch/lint-baseline.xml b/appsearch/appsearch/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/appsearch/appsearch/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/appsearch/appsearch/src/androidTest/java/androidx/appsearch/app/cts/GenericDocumentCtsTest.java b/appsearch/appsearch/src/androidTest/java/androidx/appsearch/app/cts/GenericDocumentCtsTest.java
index 164adad..4fa033a 100644
--- a/appsearch/appsearch/src/androidTest/java/androidx/appsearch/app/cts/GenericDocumentCtsTest.java
+++ b/appsearch/appsearch/src/androidTest/java/androidx/appsearch/app/cts/GenericDocumentCtsTest.java
@@ -22,6 +22,7 @@
 
 import androidx.appsearch.app.GenericDocument;
 
+import org.junit.Ignore;
 import org.junit.Test;
 
 public class GenericDocumentCtsTest {
@@ -174,6 +175,7 @@
                 .containsExactly(sDocumentProperties1, sDocumentProperties2);
     }
 
+    @Ignore
     @Test
     public void testDocument_toString() {
         GenericDocument document = new GenericDocument.Builder<>("uri1", "schemaType1")
diff --git a/appsearch/compiler/build.gradle b/appsearch/compiler/build.gradle
index 7d67114..6a5f3d9 100644
--- a/appsearch/compiler/build.gradle
+++ b/appsearch/compiler/build.gradle
@@ -36,7 +36,7 @@
     testImplementation(GOOGLE_COMPILE_TESTING)
 }
 
-tasks.findByName('compileJava').dependsOn(":appsearch:appsearch:jarDebug")
+tasks.findByName('compileJava').dependsOn(":appsearch:appsearch:jarRelease")
 
 androidx {
     name = 'AndroidX AppSearch Compiler'
diff --git a/appsearch/local-storage/lint-baseline.xml b/appsearch/local-storage/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/appsearch/local-storage/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/asynclayoutinflater/asynclayoutinflater/lint-baseline.xml b/asynclayoutinflater/asynclayoutinflater/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/asynclayoutinflater/asynclayoutinflater/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/benchmark/gradle-plugin/lint-baseline.xml b/benchmark/gradle-plugin/lint-baseline.xml
deleted file mode 100644
index 8794ae8..0000000
--- a/benchmark/gradle-plugin/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" version="4.2.0-beta06">
-
-</issues>
diff --git a/benchmark/integration-tests/crystalball-experiment/lint-baseline.xml b/benchmark/integration-tests/crystalball-experiment/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/benchmark/integration-tests/crystalball-experiment/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/benchmark/integration-tests/dry-run-benchmark/lint-baseline.xml b/benchmark/integration-tests/dry-run-benchmark/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/benchmark/integration-tests/dry-run-benchmark/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/benchmark/integration-tests/macrobenchmark/build.gradle b/benchmark/integration-tests/macrobenchmark/build.gradle
index 7ce9cba..3067b8a 100644
--- a/benchmark/integration-tests/macrobenchmark/build.gradle
+++ b/benchmark/integration-tests/macrobenchmark/build.gradle
@@ -37,6 +37,7 @@
 dependencies {
     androidTestImplementation(project(":benchmark:benchmark-junit4"))
     androidTestImplementation(project(":benchmark:benchmark-macro-junit4"))
+    androidTestImplementation(project(":internal-testutils-macrobenchmark"))
     androidTestImplementation(ANDROIDX_TEST_RULES)
     androidTestImplementation(ANDROIDX_TEST_EXT_JUNIT)
     androidTestImplementation(ANDROIDX_TEST_CORE)
diff --git a/benchmark/integration-tests/macrobenchmark/src/androidTest/java/androidx/benchmark/integration/macrobenchmark/MacrobenchUtils.kt b/benchmark/integration-tests/macrobenchmark/src/androidTest/java/androidx/benchmark/integration/macrobenchmark/MacrobenchUtils.kt
deleted file mode 100644
index 8a3b2aa..0000000
--- a/benchmark/integration-tests/macrobenchmark/src/androidTest/java/androidx/benchmark/integration/macrobenchmark/MacrobenchUtils.kt
+++ /dev/null
@@ -1,80 +0,0 @@
-/*
- * Copyright 2020 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.benchmark.integration.macrobenchmark
-
-import android.content.Intent
-import androidx.benchmark.macro.CompilationMode
-import androidx.benchmark.macro.StartupMode
-import androidx.benchmark.macro.StartupTimingMetric
-import androidx.benchmark.macro.isSupportedWithVmSettings
-import androidx.benchmark.macro.junit4.MacrobenchmarkRule
-
-const val TARGET_PACKAGE = "androidx.benchmark.integration.macrobenchmark.target"
-
-fun MacrobenchmarkRule.measureStartup(
-    compilationMode: CompilationMode,
-    startupMode: StartupMode,
-    iterations: Int = 3,
-    setupIntent: Intent.() -> Unit = {}
-) = measureRepeated(
-    packageName = TARGET_PACKAGE,
-    metrics = listOf(StartupTimingMetric()),
-    compilationMode = compilationMode,
-    iterations = iterations,
-    startupMode = startupMode
-) {
-    pressHome()
-    val intent = Intent()
-    intent.setPackage(TARGET_PACKAGE)
-    setupIntent(intent)
-    startActivityAndWait(intent)
-}
-
-fun createStartupCompilationParams(
-    startupModes: List<StartupMode> = listOf(StartupMode.HOT, StartupMode.WARM, StartupMode.COLD),
-    compilationModes: List<CompilationMode> = listOf(
-        CompilationMode.None,
-        CompilationMode.Interpreted,
-        CompilationMode.SpeedProfile()
-    )
-): List<Array<Any>> = mutableListOf<Array<Any>>().apply {
-    for (startupMode in startupModes) {
-        for (compilationMode in compilationModes) {
-            // Skip configs that can't run, so they don't clutter Studio benchmark
-            // output with AssumptionViolatedException dumps
-            if (compilationMode.isSupportedWithVmSettings()) {
-                add(arrayOf(startupMode, compilationMode))
-            }
-        }
-    }
-}
-
-fun createCompilationParams(
-    compilationModes: List<CompilationMode> = listOf(
-        CompilationMode.None,
-        CompilationMode.Interpreted,
-        CompilationMode.SpeedProfile()
-    )
-): List<Array<Any>> = mutableListOf<Array<Any>>().apply {
-    for (compilationMode in compilationModes) {
-        // Skip configs that can't run, so they don't clutter Studio benchmark
-        // output with AssumptionViolatedException dumps
-        if (compilationMode.isSupportedWithVmSettings()) {
-            add(arrayOf(compilationMode))
-        }
-    }
-}
\ No newline at end of file
diff --git a/benchmark/integration-tests/macrobenchmark/src/androidTest/java/androidx/benchmark/integration/macrobenchmark/SmallListStartupBenchmark.kt b/benchmark/integration-tests/macrobenchmark/src/androidTest/java/androidx/benchmark/integration/macrobenchmark/SmallListStartupBenchmark.kt
index 553c577..98b8cfb20 100644
--- a/benchmark/integration-tests/macrobenchmark/src/androidTest/java/androidx/benchmark/integration/macrobenchmark/SmallListStartupBenchmark.kt
+++ b/benchmark/integration-tests/macrobenchmark/src/androidTest/java/androidx/benchmark/integration/macrobenchmark/SmallListStartupBenchmark.kt
@@ -20,6 +20,8 @@
 import androidx.benchmark.macro.StartupMode
 import androidx.benchmark.macro.junit4.MacrobenchmarkRule
 import androidx.test.filters.LargeTest
+import androidx.testutils.createStartupCompilationParams
+import androidx.testutils.measureStartup
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -37,7 +39,8 @@
     @Test
     fun startup() = benchmarkRule.measureStartup(
         compilationMode = compilationMode,
-        startupMode = startupMode
+        startupMode = startupMode,
+        packageName = "androidx.benchmark.integration.macrobenchmark.target"
     ) {
         action = "androidx.benchmark.integration.macrobenchmark.target.RECYCLER_VIEW"
         putExtra("ITEM_COUNT", 5)
diff --git a/benchmark/integration-tests/macrobenchmark/src/androidTest/java/androidx/benchmark/integration/macrobenchmark/TrivialListScrollBenchmark.kt b/benchmark/integration-tests/macrobenchmark/src/androidTest/java/androidx/benchmark/integration/macrobenchmark/TrivialListScrollBenchmark.kt
index 62ede92..39efe29 100644
--- a/benchmark/integration-tests/macrobenchmark/src/androidTest/java/androidx/benchmark/integration/macrobenchmark/TrivialListScrollBenchmark.kt
+++ b/benchmark/integration-tests/macrobenchmark/src/androidTest/java/androidx/benchmark/integration/macrobenchmark/TrivialListScrollBenchmark.kt
@@ -26,6 +26,7 @@
 import androidx.test.platform.app.InstrumentationRegistry
 import androidx.test.uiautomator.By
 import androidx.test.uiautomator.UiDevice
+import androidx.testutils.createCompilationParams
 import org.junit.Before
 import org.junit.Rule
 import org.junit.Test
diff --git a/benchmark/integration-tests/macrobenchmark/src/androidTest/java/androidx/benchmark/integration/macrobenchmark/TrivialStartupBenchmark.kt b/benchmark/integration-tests/macrobenchmark/src/androidTest/java/androidx/benchmark/integration/macrobenchmark/TrivialStartupBenchmark.kt
index 29d9be3..856badf 100644
--- a/benchmark/integration-tests/macrobenchmark/src/androidTest/java/androidx/benchmark/integration/macrobenchmark/TrivialStartupBenchmark.kt
+++ b/benchmark/integration-tests/macrobenchmark/src/androidTest/java/androidx/benchmark/integration/macrobenchmark/TrivialStartupBenchmark.kt
@@ -20,6 +20,8 @@
 import androidx.benchmark.macro.StartupMode
 import androidx.benchmark.macro.junit4.MacrobenchmarkRule
 import androidx.test.filters.LargeTest
+import androidx.testutils.createStartupCompilationParams
+import androidx.testutils.measureStartup
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -37,7 +39,8 @@
     @Test
     fun startup() = benchmarkRule.measureStartup(
         compilationMode = compilationMode,
-        startupMode = startupMode
+        startupMode = startupMode,
+        packageName = "androidx.benchmark.integration.macrobenchmark.target"
     ) {
         action = "androidx.benchmark.integration.macrobenchmark.target.TRIVIAL_STARTUP_ACTIVITY"
     }
diff --git a/benchmark/integration-tests/startup-benchmark/lint-baseline.xml b/benchmark/integration-tests/startup-benchmark/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/benchmark/integration-tests/startup-benchmark/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/benchmark/macro/src/main/java/androidx/benchmark/macro/perfetto/UiState.kt b/benchmark/macro/src/main/java/androidx/benchmark/macro/perfetto/UiState.kt
index 9aa1837..9c583ae 100644
--- a/benchmark/macro/src/main/java/androidx/benchmark/macro/perfetto/UiState.kt
+++ b/benchmark/macro/src/main/java/androidx/benchmark/macro/perfetto/UiState.kt
@@ -16,11 +16,9 @@
 
 package androidx.benchmark.macro.perfetto
 
-import android.util.Log
-import androidx.benchmark.macro.TAG
-import perfetto.protos.UiState
 import perfetto.protos.Trace
 import perfetto.protos.TracePacket
+import perfetto.protos.UiState
 import java.io.File
 
 /**
@@ -40,10 +38,7 @@
 )
 
 internal fun File.appendUiState(state: UiState) {
-    val origBytes = readBytes()
-    val trace = Trace.ADAPTER.decode(origBytes)
+    val trace = Trace.ADAPTER.decode(inputStream())
     val appendedTrace = Trace(packet = trace.packet + listOf(TracePacket(ui_state = state)))
-    val modifiedBytes = Trace.ADAPTER.encode(appendedTrace)
-    writeBytes(modifiedBytes)
-    Log.d(TAG, "Appended UiState $this, from ${origBytes.size} -> ${modifiedBytes.size} bytes")
+    Trace.ADAPTER.encode(outputStream(), appendedTrace)
 }
diff --git a/biometric/biometric-ktx/lint-baseline.xml b/biometric/biometric-ktx/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/biometric/biometric-ktx/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/biometric/biometric/lint-baseline.xml b/biometric/biometric/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/biometric/biometric/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/biometric/integration-tests/testapp/lint-baseline.xml b/biometric/integration-tests/testapp/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/biometric/integration-tests/testapp/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/buildSrc-tests/lint-baseline.xml b/buildSrc-tests/lint-baseline.xml
deleted file mode 100644
index 8794ae8..0000000
--- a/buildSrc-tests/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" version="4.2.0-beta06">
-
-</issues>
diff --git a/buildSrc/src/main/kotlin/androidx/build/AndroidXRootPlugin.kt b/buildSrc/src/main/kotlin/androidx/build/AndroidXRootPlugin.kt
index d7144494..243bf52 100644
--- a/buildSrc/src/main/kotlin/androidx/build/AndroidXRootPlugin.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/AndroidXRootPlugin.kt
@@ -53,7 +53,6 @@
         tasks.register("listAndroidXProperties", ListAndroidXPropertiesTask::class.java)
         setDependencyVersions()
         configureKtlintCheckFile()
-        configureCheckInvalidSuppress()
         tasks.register(CheckExternalDependencyLicensesTask.TASK_NAME)
 
         val buildOnServerTask = tasks.create(
@@ -112,7 +111,7 @@
                 )
             )
             project.plugins.withType(AndroidBasePlugin::class.java) {
-                buildOnServerTask.dependsOn("${project.path}:assembleDebug")
+                buildOnServerTask.dependsOn("${project.path}:assembleRelease")
                 if (!project.usingMaxDepVersions()) {
                     project.afterEvaluate {
                         project.agpVariants.all { variant ->
@@ -159,7 +158,7 @@
             buildOnServerTask.dependsOn(Jacoco.createUberJarTask(this))
         }
 
-        val zipTestConfigsWithApks = project.tasks.register(
+        project.tasks.register(
             ZIP_TEST_CONFIGS_WITH_APKS_TASK, Zip::class.java
         ) {
             it.destinationDirectory.set(project.getDistributionDirectory())
@@ -168,7 +167,7 @@
             // We're mostly zipping a bunch of .apk files that are already compressed
             it.entryCompression = ZipEntryCompression.STORED
         }
-        val zipConstrainedTestConfigsWithApks = project.tasks.register(
+        project.tasks.register(
             ZIP_CONSTRAINED_TEST_CONFIGS_WITH_APKS_TASK, Zip::class.java
         ) {
             it.destinationDirectory.set(project.getDistributionDirectory())
@@ -177,8 +176,6 @@
             // We're mostly zipping a bunch of .apk files that are already compressed
             it.entryCompression = ZipEntryCompression.STORED
         }
-        buildOnServerTask.dependsOn(zipTestConfigsWithApks)
-        buildOnServerTask.dependsOn(zipConstrainedTestConfigsWithApks)
 
         AffectedModuleDetector.configure(gradle, this)
 
diff --git a/buildSrc/src/main/kotlin/androidx/build/AndroidXUiPlugin.kt b/buildSrc/src/main/kotlin/androidx/build/AndroidXUiPlugin.kt
index 06ba8d8..f0f382f 100644
--- a/buildSrc/src/main/kotlin/androidx/build/AndroidXUiPlugin.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/AndroidXUiPlugin.kt
@@ -113,9 +113,16 @@
             return properties.get(COMPOSE_MPP_ENABLED)?.toString()?.toBoolean() ?: false
         }
 
+        /**
+         * @param isMultiplatformEnabled whether this module has a corresponding
+         * multiplatform configuration, or whether it is Android only
+         */
         @JvmStatic
-        fun Project.applyAndConfigureKotlinPlugin() {
-            if (isMultiplatformEnabled()) {
+        @JvmOverloads
+        fun Project.applyAndConfigureKotlinPlugin(
+            isMultiplatformEnabled: Boolean = isMultiplatformEnabled()
+        ) {
+            if (isMultiplatformEnabled) {
                 apply(plugin = "kotlin-multiplatform")
             } else {
                 apply(plugin = "org.jetbrains.kotlin.android")
@@ -123,7 +130,7 @@
 
             configureManifests()
             configureForKotlinMultiplatformSourceStructure()
-            if (isMultiplatformEnabled()) {
+            if (isMultiplatformEnabled) {
                 configureForMultiplatform()
             }
 
diff --git a/buildSrc/src/main/kotlin/androidx/build/ErrorProneConfiguration.kt b/buildSrc/src/main/kotlin/androidx/build/ErrorProneConfiguration.kt
index 4e99f61..c42f8b5 100644
--- a/buildSrc/src/main/kotlin/androidx/build/ErrorProneConfiguration.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/ErrorProneConfiguration.kt
@@ -65,7 +65,7 @@
     val errorProneConfiguration = createErrorProneConfiguration()
     variants.all { variant ->
         // Using getName() instead of name due to b/150427408
-        if (variant.buildType.getName() == BuilderConstants.DEBUG) {
+        if (variant.buildType.getName() == BuilderConstants.RELEASE) {
             val task = variant.javaCompileProvider
             (variant as BaseVariant).annotationProcessorConfiguration.extendsFrom(
                 errorProneConfiguration
diff --git a/buildSrc/src/main/kotlin/androidx/build/LibraryGroups.kt b/buildSrc/src/main/kotlin/androidx/build/LibraryGroups.kt
index 9f63528..4ce2920 100644
--- a/buildSrc/src/main/kotlin/androidx/build/LibraryGroups.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/LibraryGroups.kt
@@ -53,6 +53,7 @@
     val EXIFINTERFACE = LibraryGroup("androidx.exifinterface", LibraryVersions.EXIFINTERFACE)
     val FRAGMENT = LibraryGroup("androidx.fragment", LibraryVersions.FRAGMENT)
     val GRIDLAYOUT = LibraryGroup("androidx.gridlayout", LibraryVersions.GRIDLAYOUT)
+    val HEALTH = LibraryGroup("androidx.health", null)
     val HEIFWRITER = LibraryGroup("androidx.heifwriter", LibraryVersions.HEIFWRITER)
     val HILT = LibraryGroup("androidx.hilt", null)
     val INSPECTION = LibraryGroup("androidx.inspection", LibraryVersions.INSPECTION)
diff --git a/buildSrc/src/main/kotlin/androidx/build/LibraryVersions.kt b/buildSrc/src/main/kotlin/androidx/build/LibraryVersions.kt
index 0b8fffe..b238e0c 100644
--- a/buildSrc/src/main/kotlin/androidx/build/LibraryVersions.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/LibraryVersions.kt
@@ -52,7 +52,7 @@
     val CORE_ANIMATION = Version("1.0.0-alpha03")
     val CORE_ANIMATION_TESTING = Version("1.0.0-alpha03")
     val CORE_APPDIGEST = Version("1.0.0-alpha01")
-    val CORE_GOOGLE_SHORTCUTS = Version("1.0.0-alpha04")
+    val CORE_GOOGLE_SHORTCUTS = Version("1.0.0-beta01")
     val CORE_ROLE = Version("1.1.0-alpha02")
     val CURSORADAPTER = Version("1.1.0-alpha01")
     val CUSTOMVIEW = Version("1.2.0-alpha01")
@@ -68,8 +68,9 @@
     val FRAGMENT = Version("1.4.0-alpha01")
     val FUTURES = Version("1.2.0-alpha01")
     val GRIDLAYOUT = Version("1.1.0-alpha01")
+    val HEALTH_SERVICES_CLIENT = Version("1.0.0-alpha01")
     val HEIFWRITER = Version("1.1.0-alpha02")
-    val HILT = Version("1.0.0-beta01")
+    val HILT = Version("1.1.0-alpha01")
     val HILT_NAVIGATION_COMPOSE = Version("1.0.0-alpha02")
     val INSPECTION = Version("1.0.0")
     val INTERPOLATOR = Version("1.1.0-alpha01")
@@ -81,7 +82,7 @@
     val LEGACY = Version("1.1.0-alpha01")
     val LOCALBROADCASTMANAGER = Version("1.1.0-alpha02")
     val LIFECYCLE = Version("2.4.0-alpha01")
-    val LIFECYCLE_VIEWMODEL_COMPOSE = Version("1.0.0-alpha04")
+    val LIFECYCLE_VIEWMODEL_COMPOSE = Version("1.0.0-alpha05")
     val LIFECYCLE_EXTENSIONS = Version("2.2.0")
     val LOADER = Version("1.2.0-alpha01")
     val MEDIA = Version("1.4.0-alpha02")
diff --git a/buildSrc/src/main/kotlin/androidx/build/LintConfiguration.kt b/buildSrc/src/main/kotlin/androidx/build/LintConfiguration.kt
index bf9cff9..08e2e98 100644
--- a/buildSrc/src/main/kotlin/androidx/build/LintConfiguration.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/LintConfiguration.kt
@@ -195,22 +195,41 @@
             // be able to burn down existing violations. That's hard to enforce, though, so we'll
             // generally allow teams to update their baseline files with a publicly-known flag.
             if (updateLintBaseline) {
-                // Continue generating baselines regardless of errors
+                // Continue generating baselines regardless of errors.
                 isAbortOnError = false
-                // Avoid printing every single lint error to the terminal
+                // Avoid printing every single lint error to the terminal.
                 textReport = false
-                val lintDebugTask = tasks.named("lintDebug")
-                lintDebugTask.configure {
-                    it.doFirst {
-                        lintBaseline.delete()
+
+                listOf(
+                    tasks.named("lintDebug"),
+                    tasks.named("lint"),
+                ).forEach { task ->
+                    task.configure {
+                        // Delete any existing baseline so that we clear old obsolete entries.
+                        it.doFirst {
+                            lintBaseline.delete()
+                        }
+
+                        // Delete empty generated baselines because they are annoying.
+                        it.doLast {
+                            if (lintBaseline.exists()) {
+                                val hasAnyIssues = lintBaseline.reader().useLines { lines ->
+                                    lines.any { line ->
+                                        line.endsWith("<issue")
+                                    }
+                                }
+                                if (!hasAnyIssues) {
+                                    // Using println is consistent with lint's own output.
+                                    println(
+                                        "Removed empty baseline file ${lintBaseline.absolutePath}"
+                                    )
+                                    lintBaseline.delete()
+                                }
+                            }
+                        }
                     }
                 }
-                val lintTask = tasks.named("lint")
-                lintTask.configure {
-                    it.doFirst {
-                        lintBaseline.delete()
-                    }
-                }
+
                 // Continue running after errors or after creating a new, blank baseline file.
                 System.setProperty(LINT_BASELINE_CONTINUE, "true")
             }
diff --git a/buildSrc/src/main/kotlin/androidx/build/checkInvalidSuppress.kt b/buildSrc/src/main/kotlin/androidx/build/checkInvalidSuppress.kt
deleted file mode 100644
index bb46d82..0000000
--- a/buildSrc/src/main/kotlin/androidx/build/checkInvalidSuppress.kt
+++ /dev/null
@@ -1,121 +0,0 @@
-/*
- * Copyright 2019 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.DefaultTask
-import org.gradle.api.GradleException
-import org.gradle.api.Project
-import org.gradle.api.tasks.Internal
-import org.gradle.api.tasks.TaskAction
-import org.gradle.api.tasks.options.Option
-import java.io.File
-
-// first level line filter - we ignore lines that don't contain the following for speed
-const val ROOT_MATCH = "noinspection"
-// Map of invalid pattern to correct replacement
-// These could be regex for fancy whitespace matching, but don't seem to need in practice
-val MATCHERS = mapOf(
-    "//noinspection deprecation" to "@SuppressWarnings(\"deprecation\")",
-    "//noinspection unchecked" to "@SuppressWarnings(\"unchecked\")"
-)
-
-open class CheckInvalidSuppressTask : DefaultTask() {
-
-    @Option(option = "files", description = "List of files to check")
-    @Internal
-    lateinit var filenamesOption: String
-
-    @Option(
-        option = "format",
-        description = "Use --format to auto-correct invalid suppressions"
-    )
-    @Internal
-    var autocorrectOption = false
-
-    // Check a line for any invalid suppressions, and return it if found
-    fun getInvalidSuppression(line: String): String {
-        MATCHERS.keys.forEach { bad ->
-            if (line.trimStart().startsWith(bad)) {
-                return bad
-            }
-        }
-        return ""
-    }
-
-    // Get report for line with invalid suppression
-    fun getReportForLine(filename: String, i: Int, lines: List<String>, good: String): String {
-        var context = ""
-        for (index in 0..2) {
-            if (index + i >= lines.size) break
-            context += lines[index + i] + "\n"
-        }
-        return "\n%s:%d:\nError: unsupported comment suppression\n%sInstead, use: %s\n"
-            .format(
-                filename, i, context, good
-            )
-    }
-
-    @TaskAction
-    fun checkForInvalidSuppression() {
-        val filenames = filenamesOption.split(" ")
-        var report = ""
-
-        for (filename in filenames) {
-            // suppress comments ignored in kotlin, but may as well block there too
-            if (!filename.endsWith(".java") && !filename.endsWith(".kt"))
-                continue
-
-            // check file exists
-            val file = File(filename)
-            if (!file.exists())
-                continue
-
-            val lines = file.readLines(Charsets.UTF_8).toMutableList()
-            for ((i, line) in lines.withIndex()) {
-                if (line.contains(ROOT_MATCH)) {
-                    val bad = getInvalidSuppression(line)
-                    if (bad != "") {
-                        if (autocorrectOption) {
-                            if (line.trimStart() == bad) {
-                                lines[i] = line.replaceFirst(bad, MATCHERS[bad]!!)
-                            } else {
-                                lines[i] = line.replaceFirst(bad, MATCHERS[bad]!! + " // ")
-                            }
-                            file.writeText(lines.joinToString(System.lineSeparator()))
-                        } else {
-                            report += getReportForLine(filename, i, lines, MATCHERS[bad]!!)
-                        }
-                    }
-                }
-            }
-        }
-        if (report != "") {
-            throw GradleException(
-                "Invalid, IDEA-specific warning suppression found. These cause " +
-                    "warnings during compilation." + "\n" + report
-            )
-        }
-    }
-}
-
-fun Project.configureCheckInvalidSuppress() {
-    tasks.register("checkInvalidSuppress", CheckInvalidSuppressTask::class.java) { task ->
-        task.description = "Task used to find IDEA-specific warning suppressions that do not " +
-            "work for command line builds."
-        task.group = "Verification"
-    }
-}
diff --git a/buildSrc/src/main/kotlin/androidx/build/dependencyTracker/AffectedModuleDetector.kt b/buildSrc/src/main/kotlin/androidx/build/dependencyTracker/AffectedModuleDetector.kt
index 1173c37..273ff17 100644
--- a/buildSrc/src/main/kotlin/androidx/build/dependencyTracker/AffectedModuleDetector.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/dependencyTracker/AffectedModuleDetector.kt
@@ -477,7 +477,15 @@
             setOf(
                 ":compose:integration-tests:macrobenchmark",
                 ":compose:integration-tests:macrobenchmark-target"
-            ), // link compose's macrobenchmark and its target
+            ),
+            setOf(
+                ":emoji2:integration-tests:init-disabled-macrobenchmark",
+                ":emoji2:integration-tests:init-disabled-macrobenchmark-target",
+            ),
+            setOf(
+                ":emoji2:integration-tests:init-enabled-macrobenchmark",
+                ":emoji2:integration-tests:init-enabled-macrobenchmark-target",
+            ),
         )
     }
 }
diff --git a/buildSrc/src/main/kotlin/androidx/build/docs/AndroidXDocsPlugin.kt b/buildSrc/src/main/kotlin/androidx/build/docs/AndroidXDocsPlugin.kt
index 9d33f6f..9b6e95d 100644
--- a/buildSrc/src/main/kotlin/androidx/build/docs/AndroidXDocsPlugin.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/docs/AndroidXDocsPlugin.kt
@@ -562,7 +562,7 @@
     }
 }
 
-private const val DACKKA_DEPENDENCY = "com.google.devsite:dackka:0.0.3"
+private const val DACKKA_DEPENDENCY = "com.google.devsite:dackka:0.0.4"
 private const val DOCLAVA_DEPENDENCY = "com.android:doclava:1.0.6"
 
 // Allowlist for directories that should be processed by Dackka
diff --git a/buildSrc/src/main/kotlin/androidx/build/testConfiguration/GenerateTestConfigurationTask.kt b/buildSrc/src/main/kotlin/androidx/build/testConfiguration/GenerateTestConfigurationTask.kt
index bc5cd67..17c29c3 100644
--- a/buildSrc/src/main/kotlin/androidx/build/testConfiguration/GenerateTestConfigurationTask.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/testConfiguration/GenerateTestConfigurationTask.kt
@@ -128,8 +128,6 @@
             configBuilder.isBenchmark(true)
             if (configBuilder.isPostsubmit) {
                 configBuilder.tag("microbenchmarks")
-            } else {
-                configBuilder.tag("microbenchmarks_presubmit")
             }
         } else if (testProjectPath.get().endsWith("macrobenchmark")) {
             configBuilder.tag("macrobenchmarks")
diff --git a/buildSrc/src/main/kotlin/androidx/build/uptodatedness/TaskUpToDateValidator.kt b/buildSrc/src/main/kotlin/androidx/build/uptodatedness/TaskUpToDateValidator.kt
index 53863ec..79c3b66 100644
--- a/buildSrc/src/main/kotlin/androidx/build/uptodatedness/TaskUpToDateValidator.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/uptodatedness/TaskUpToDateValidator.kt
@@ -37,8 +37,8 @@
 const val RECORD_FLAG_NAME = VERIFY_UP_TO_DATE
 
 // Temporary set of exempt tasks that are known to still be out-of-date after running once
-// Entries in this set may be task names (like assembleDebug) or task paths
-// (like :core:core:assembleDebug)
+// Entries in this set may be task names (like assembleRelease) or task paths
+// (like :core:core:assembleRelease)
 // Entries in this set do still get rerun because they might produce files that are needed by
 // subsequent tasks
 val ALLOW_RERUNNING_TASKS = setOf(
diff --git a/busytown/androidx_max_dep_versions.sh b/busytown/androidx_max_dep_versions.sh
index 65717ed..72c6665 100755
--- a/busytown/androidx_max_dep_versions.sh
+++ b/busytown/androidx_max_dep_versions.sh
@@ -5,7 +5,7 @@
 
 cd "$(dirname $0)"
 
-impl/build.sh assembleDebug assembleAndroidTest \
+impl/build.sh assembleRelease assembleAndroidTest \
     -Pandroidx.useMaxDepVersions \
     "$@"
 
diff --git a/busytown/impl/build.sh b/busytown/impl/build.sh
index 5da0420..34d8330 100755
--- a/busytown/impl/build.sh
+++ b/busytown/impl/build.sh
@@ -3,6 +3,12 @@
 
 # This script runs frameworks/support/gradlew
 
+function showDiskStats() {
+  echo "df -h"
+  df -h
+}
+showDiskStats
+
 # find script
 SCRIPT_DIR="$(cd $(dirname $0) && pwd)"
 
@@ -38,6 +44,7 @@
     # Put each argument on its own line because some arguments may be long.
     # Also put "\" at the end of non-final lines so the command can be copy-pasted
     echo "$*" | sed 's/ / \\\n/g' | sed 's/^/    /' >&2
+    showDiskStats
     return 1
   fi
 }
diff --git a/camera/camera-camera2-pipe-integration/build.gradle b/camera/camera-camera2-pipe-integration/build.gradle
index 2c57aeb..e14d471 100644
--- a/camera/camera-camera2-pipe-integration/build.gradle
+++ b/camera/camera-camera2-pipe-integration/build.gradle
@@ -38,6 +38,7 @@
 )
 
 dependencies {
+    implementation("androidx.core:core:1.1.0")
     implementation("androidx.concurrent:concurrent-listenablefuture-callback:1.0.0-beta01")
     bundleInside(project(path: ":camera:camera-camera2-pipe", configuration: "exportRelease"))
 
diff --git a/camera/camera-camera2-pipe-integration/lint-baseline.xml b/camera/camera-camera2-pipe-integration/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/camera/camera-camera2-pipe-integration/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/MeteringRepeating.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/MeteringRepeating.kt
new file mode 100644
index 0000000..d038a85
--- /dev/null
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/MeteringRepeating.kt
@@ -0,0 +1,174 @@
+/*
+ * 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.camera.camera2.pipe.integration.impl
+
+import android.graphics.ImageFormat
+import android.graphics.SurfaceTexture
+import android.hardware.camera2.CameraCharacteristics
+import android.hardware.camera2.CameraDevice
+import android.os.Build
+import android.util.Size
+import android.view.Surface
+import androidx.camera.camera2.pipe.core.Log.error
+import androidx.camera.camera2.pipe.integration.adapter.CameraUseCaseAdapter
+import androidx.camera.core.CameraSelector
+import androidx.camera.core.UseCase
+import androidx.camera.core.impl.CaptureConfig
+import androidx.camera.core.impl.Config
+import androidx.camera.core.impl.DeferrableSurface
+import androidx.camera.core.impl.ImageFormatConstants
+import androidx.camera.core.impl.ImageInputConfig
+import androidx.camera.core.impl.ImmediateSurface
+import androidx.camera.core.impl.MutableOptionsBundle
+import androidx.camera.core.impl.SessionConfig
+import androidx.camera.core.impl.UseCaseConfig
+import androidx.camera.core.impl.UseCaseConfig.OPTION_SESSION_CONFIG_UNPACKER
+import androidx.camera.core.impl.UseCaseConfigFactory
+import androidx.camera.core.impl.utils.executor.CameraXExecutors
+import androidx.core.util.Consumer
+
+private val DEFAULT_PREVIEW_SIZE = Size(0, 0)
+
+/**
+ * A [UseCase] used to issue repeating requests when only [androidx.camera.core.ImageCapture] is
+ * enabled, since taking a picture may require a repeating surface to perform pre-capture checks,
+ * mainly around 3A.
+ */
+class MeteringRepeating(
+    private val cameraProperties: CameraProperties,
+    config: MeteringRepeatingConfig,
+) : UseCase(config) {
+
+    private val meteringSurfaceSize = cameraProperties.getMinimumPreviewSize()
+    private var deferrableSurface: DeferrableSurface? = null
+
+    override fun getDefaultConfig(applyDefaultConfig: Boolean, factory: UseCaseConfigFactory) =
+        Builder(cameraProperties).useCaseConfig
+
+    override fun getUseCaseConfigBuilder(config: Config) = Builder(cameraProperties)
+
+    override fun onSuggestedResolutionUpdated(suggestedResolution: Size): Size {
+        updateSessionConfig(createPipeline().build())
+        notifyActive()
+        return meteringSurfaceSize
+    }
+
+    override fun onDetached() {
+        deferrableSurface?.close()
+        deferrableSurface = null
+    }
+
+    private fun createPipeline(): SessionConfig.Builder {
+        val surfaceTexture = SurfaceTexture(0).apply {
+            setDefaultBufferSize(meteringSurfaceSize.width, meteringSurfaceSize.height)
+        }
+        val surface = Surface(surfaceTexture)
+
+        deferrableSurface?.close()
+        deferrableSurface = ImmediateSurface(surface)
+        deferrableSurface!!.terminationFuture
+            .addListener(
+                {
+                    surface.release()
+                    surfaceTexture.release()
+                },
+                CameraXExecutors.directExecutor()
+            )
+
+        return SessionConfig.Builder
+            .createFrom(MeteringRepeatingConfig())
+            .apply {
+                setTemplateType(CameraDevice.TEMPLATE_PREVIEW)
+                addSurface(deferrableSurface!!)
+                addErrorListener { _, _ ->
+                    updateSessionConfig(createPipeline().build())
+                    notifyReset()
+                }
+            }
+    }
+
+    private fun CameraProperties.getMinimumPreviewSize(): Size {
+        val map = metadata[CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP]
+        if (map == null) {
+            error { "Can not retrieve SCALER_STREAM_CONFIGURATION_MAP." }
+            return DEFAULT_PREVIEW_SIZE
+        }
+
+        val outputSizes = if (Build.VERSION.SDK_INT < 23) {
+            // ImageFormat.PRIVATE is only public after Android level 23. Therefore, use
+            // SurfaceTexture.class to get the supported output sizes before Android level 23.
+            map.getOutputSizes(SurfaceTexture::class.java)
+        } else {
+            map.getOutputSizes(ImageFormat.PRIVATE)
+        }
+
+        if (outputSizes == null) {
+            error { "Can not get output size list." }
+            return DEFAULT_PREVIEW_SIZE
+        }
+
+        check(outputSizes.isNotEmpty()) { "Output sizes empty" }
+        return outputSizes.minWithOrNull { size1, size2 -> size1.area().compareTo(size2.area()) }!!
+    }
+
+    class MeteringRepeatingConfig : UseCaseConfig<MeteringRepeating>, ImageInputConfig {
+        private val config = MutableOptionsBundle.create().apply {
+            insertOption(
+                OPTION_SESSION_CONFIG_UNPACKER,
+                CameraUseCaseAdapter.DefaultSessionOptionsUnpacker
+            )
+        }
+
+        override fun getConfig() = config
+
+        override fun getInputFormat() = ImageFormatConstants.INTERNAL_DEFINED_IMAGE_FORMAT_PRIVATE
+    }
+
+    class Builder(private val cameraProperties: CameraProperties) :
+        UseCaseConfig.Builder<MeteringRepeating, MeteringRepeatingConfig, Builder> {
+
+        override fun getMutableConfig() = MutableOptionsBundle.create()
+
+        override fun getUseCaseConfig() = MeteringRepeatingConfig()
+
+        override fun setTargetClass(targetClass: Class<MeteringRepeating>) = this
+
+        override fun setTargetName(targetName: String) = this
+
+        override fun setUseCaseEventCallback(eventCallback: EventCallback) = this
+
+        override fun setDefaultSessionConfig(sessionConfig: SessionConfig) = this
+
+        override fun setDefaultCaptureConfig(captureConfig: CaptureConfig) = this
+
+        override fun setSessionOptionUnpacker(optionUnpacker: SessionConfig.OptionUnpacker) = this
+
+        override fun setCaptureOptionUnpacker(optionUnpacker: CaptureConfig.OptionUnpacker) = this
+
+        override fun setSurfaceOccupancyPriority(priority: Int) = this
+
+        override fun setCameraSelector(cameraSelector: CameraSelector) = this
+
+        override fun setAttachedUseCasesUpdateListener(
+            attachedUseCasesUpdateListener: Consumer<MutableCollection<UseCase>>
+        ): Builder = this
+
+        override fun build(): MeteringRepeating {
+            return MeteringRepeating(cameraProperties, useCaseConfig)
+        }
+    }
+}
diff --git a/camera/camera-camera2-pipe-testing/lint-baseline.xml b/camera/camera-camera2-pipe-testing/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/camera/camera-camera2-pipe-testing/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/CameraFilter.java b/camera/camera-core/src/main/java/androidx/camera/core/CameraFilter.java
index 960447f..c2c7f15 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/CameraFilter.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/CameraFilter.java
@@ -17,6 +17,11 @@
 package androidx.camera.core;
 
 import androidx.annotation.NonNull;
+import androidx.annotation.RestrictTo;
+import androidx.camera.core.impl.CameraConfig;
+import androidx.camera.core.impl.ExtendedCameraConfigProviderStore;
+
+import com.google.auto.value.AutoValue;
 
 import java.util.List;
 
@@ -43,4 +48,47 @@
      */
     @NonNull
     List<CameraInfo> filter(@NonNull List<CameraInfo> cameraInfos);
+
+    /**
+     * Returns the id of this camera filter.
+     *
+     * @hide
+     */
+    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+    @NonNull
+    default Id getId() {
+        return Id.DEFAULT;
+    }
+
+    /**
+     * An identifier for a {@link CameraFilter}.
+     *
+     * <p>A camera filter can be associated with a set of camera configuration options. This
+     * means a camera filter's {@link Id} can be mapped to a unique {@link CameraConfig}. An
+     * example of this is extension modes, where a camera filter can represent an extension mode,
+     * and each extension mode adds a set of camera configurations to the camera that supports it
+     * . {@link ExtendedCameraConfigProviderStore#getConfig(Object)} allows retrieving the
+     * {@link CameraConfig} of an extension mode given the {@link CameraFilter.Id} of its camera
+     * filter.
+     *
+     * <p>The {@link Id} class allows anything to be wrapped as an {@link Id} instance. The
+     * caller should make the input value object unique when calling the {@link #create(Object)}
+     * function. So that the {@link Id} can be recognized and used for specific purposes.
+     *
+     * @hide
+     */
+    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+    @AutoValue
+    abstract class Id {
+
+        public static final Id DEFAULT = create(new Object());
+
+        @NonNull
+        public static Id create(@NonNull Object value) {
+            return new AutoValue_CameraFilter_Id(value);
+        }
+
+        @NonNull
+        public abstract Object getValue();
+    }
 }
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/CameraX.java b/camera/camera-core/src/main/java/androidx/camera/core/CameraX.java
index 41a8fb4..77cbe6f 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/CameraX.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/CameraX.java
@@ -40,6 +40,7 @@
 import androidx.camera.core.impl.CameraThreadConfig;
 import androidx.camera.core.impl.CameraValidator;
 import androidx.camera.core.impl.UseCaseConfigFactory;
+import androidx.camera.core.impl.utils.ContextUtil;
 import androidx.camera.core.impl.utils.executor.CameraXExecutors;
 import androidx.camera.core.impl.utils.futures.FutureCallback;
 import androidx.camera.core.impl.utils.futures.FutureChain;
@@ -395,7 +396,7 @@
         } else {
             // Try to retrieve the CameraXConfig.Provider through the application's resources
             try {
-                Resources resources = getApplicationContext(context).getResources();
+                Resources resources = ContextUtil.getApplicationContext(context).getResources();
                 String defaultProviderClassName =
                         resources.getString(
                                 R.string.androidx_camera_default_config_provider);
@@ -430,7 +431,7 @@
     @Nullable
     private static Application getApplicationFromContext(@NonNull Context context) {
         Application application = null;
-        Context appContext = getApplicationContext(context);
+        Context appContext = ContextUtil.getApplicationContext(context);
         while (appContext instanceof ContextWrapper) {
             if (appContext instanceof Application) {
                 application = (Application) appContext;
@@ -443,18 +444,6 @@
     }
 
     /**
-     * Gets the application context and preserves the attribution tag.
-     */
-    private static Context getApplicationContext(@NonNull Context context) {
-        Context applicationContext = context.getApplicationContext();
-        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
-            return applicationContext.createAttributionContext(context.getAttributionTag());
-        } else {
-            return applicationContext;
-        }
-    }
-
-    /**
      * Gets the base context and preserves the attribution tag.
      */
     private static Context getBaseContext(ContextWrapper context) {
@@ -574,7 +563,7 @@
                 //  the context within the called method.
                 mAppContext = getApplicationFromContext(context);
                 if (mAppContext == null) {
-                    mAppContext = getApplicationContext(context);
+                    mAppContext = ContextUtil.getApplicationContext(context);
                 }
                 CameraFactory.Provider cameraFactoryProvider =
                         mCameraXConfig.getCameraFactoryProvider(null);
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/impl/CameraConfigProvider.java b/camera/camera-core/src/main/java/androidx/camera/core/impl/CameraConfigProvider.java
new file mode 100644
index 0000000..2007b49
--- /dev/null
+++ b/camera/camera-core/src/main/java/androidx/camera/core/impl/CameraConfigProvider.java
@@ -0,0 +1,36 @@
+/*
+ * 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.camera.core.impl;
+
+import android.content.Context;
+
+import androidx.annotation.NonNull;
+import androidx.camera.core.CameraInfo;
+
+/**
+ * Provides the ability to create a {@link CameraConfig} given a {@link CameraInfo}.
+ */
+public interface CameraConfigProvider {
+
+    CameraConfigProvider EMPTY = (cameraInfo, context) -> CameraConfigs.emptyConfig();
+
+    /**
+     * Returns a camera config according to the input camera info.
+     */
+    @NonNull
+    CameraConfig getConfig(@NonNull CameraInfo cameraInfo, @NonNull Context context);
+}
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/impl/ExtendedCameraConfigProviderStore.java b/camera/camera-core/src/main/java/androidx/camera/core/impl/ExtendedCameraConfigProviderStore.java
new file mode 100644
index 0000000..81d48a3
--- /dev/null
+++ b/camera/camera-core/src/main/java/androidx/camera/core/impl/ExtendedCameraConfigProviderStore.java
@@ -0,0 +1,67 @@
+/*
+ * 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.camera.core.impl;
+
+import androidx.annotation.GuardedBy;
+import androidx.annotation.NonNull;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Stores {@link CameraConfigProvider} instances which allow building {@link CameraConfig} using
+ * keys (extensions modes for example). The provided {@link CameraConfig}s are unique to the device.
+ */
+public final class ExtendedCameraConfigProviderStore {
+
+    private static final Object LOCK = new Object();
+
+    @GuardedBy("LOCK")
+    private static final Map<Object, CameraConfigProvider> CAMERA_CONFIG_PROVIDERS =
+            new HashMap<>();
+
+    private ExtendedCameraConfigProviderStore() {
+    }
+
+    /**
+     * Associates the specified {@link CameraConfigProvider} with the specified key and stores them.
+     */
+    public static void addConfig(@NonNull Object key, @NonNull CameraConfigProvider provider) {
+        synchronized (LOCK) {
+            CAMERA_CONFIG_PROVIDERS.put(key, provider);
+        }
+    }
+
+    /**
+     * Retrieves the {@link CameraConfigProvider} associated with the specified key.
+     *
+     * <p>A default {@link CameraConfigProvider#EMPTY} will be returned if there isn't a
+     * {@link CameraConfigProvider} associated with the key.
+     */
+    @NonNull
+    public static CameraConfigProvider getConfig(@NonNull Object key) {
+        final CameraConfigProvider provider;
+        synchronized (LOCK) {
+            provider = CAMERA_CONFIG_PROVIDERS.get(key);
+        }
+
+        if (provider == null) {
+            return CameraConfigProvider.EMPTY;
+        }
+        return provider;
+    }
+}
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/impl/utils/ContextUtil.java b/camera/camera-core/src/main/java/androidx/camera/core/impl/utils/ContextUtil.java
new file mode 100644
index 0000000..6964e31
--- /dev/null
+++ b/camera/camera-core/src/main/java/androidx/camera/core/impl/utils/ContextUtil.java
@@ -0,0 +1,43 @@
+/*
+ * 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.camera.core.impl.utils;
+
+import android.content.Context;
+import android.os.Build;
+
+import androidx.annotation.NonNull;
+
+/**
+ * Utility class for {@link Context} related operations.
+ */
+public final class ContextUtil {
+    /**
+     * Gets the application context and preserves the attribution tag.
+     */
+    @NonNull
+    public static Context getApplicationContext(@NonNull Context context) {
+        Context applicationContext = context.getApplicationContext();
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
+            return applicationContext.createAttributionContext(context.getAttributionTag());
+        } else {
+            return applicationContext;
+        }
+    }
+
+    private ContextUtil() {
+    }
+}
diff --git a/camera/camera-core/src/test/java/androidx/camera/core/impl/ExtendedCameraConfigProviderStoreTest.kt b/camera/camera-core/src/test/java/androidx/camera/core/impl/ExtendedCameraConfigProviderStoreTest.kt
new file mode 100644
index 0000000..571126b
--- /dev/null
+++ b/camera/camera-core/src/test/java/androidx/camera/core/impl/ExtendedCameraConfigProviderStoreTest.kt
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.camera.core.impl
+
+import android.os.Build
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.robolectric.RobolectricTestRunner
+import org.robolectric.annotation.Config
+import org.robolectric.annotation.internal.DoNotInstrument
+
+@RunWith(RobolectricTestRunner::class)
+@DoNotInstrument
+@Config(minSdk = Build.VERSION_CODES.LOLLIPOP)
+public class ExtendedCameraConfigProviderStoreTest {
+
+    @Test
+    public fun canRetrieveStoredCameraConfigProvider() {
+        val id = Object()
+        val cameraConfigProvider =
+            CameraConfigProvider { _, _ -> CameraConfigs.emptyConfig() }
+
+        ExtendedCameraConfigProviderStore.addConfig(id, cameraConfigProvider)
+
+        assertThat(ExtendedCameraConfigProviderStore.getConfig(id)).isEqualTo(cameraConfigProvider)
+    }
+
+    @Test
+    public fun returnDefaultEmptyCameraConfigProvider_whenNoDataStored() {
+        assertThat(ExtendedCameraConfigProviderStore.getConfig(Object())).isEqualTo(
+            CameraConfigProvider.EMPTY
+        )
+    }
+}
diff --git a/camera/camera-extensions-stub/src/main/java/androidx/camera/extensions/impl/AutoImageCaptureExtenderImpl.java b/camera/camera-extensions-stub/src/main/java/androidx/camera/extensions/impl/AutoImageCaptureExtenderImpl.java
index 112eb3e..a364d4b 100755
--- a/camera/camera-extensions-stub/src/main/java/androidx/camera/extensions/impl/AutoImageCaptureExtenderImpl.java
+++ b/camera/camera-extensions-stub/src/main/java/androidx/camera/extensions/impl/AutoImageCaptureExtenderImpl.java
@@ -18,6 +18,7 @@
 import android.content.Context;
 import android.hardware.camera2.CameraCharacteristics;
 import android.util.Pair;
+import android.util.Range;
 import android.util.Size;
 
 import androidx.annotation.NonNull;
@@ -91,4 +92,10 @@
     public List<Pair<Integer, Size[]>> getSupportedResolutions() {
         throw new RuntimeException("Stub, replace with implementation.");
     }
+
+    @Nullable
+    @Override
+    public Range<Long> getEstimatedCaptureLatencyRange(@NonNull Size captureOutputSize) {
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
 }
diff --git a/camera/camera-extensions-stub/src/main/java/androidx/camera/extensions/impl/BeautyImageCaptureExtenderImpl.java b/camera/camera-extensions-stub/src/main/java/androidx/camera/extensions/impl/BeautyImageCaptureExtenderImpl.java
index 79dbff6..d68d8eb 100755
--- a/camera/camera-extensions-stub/src/main/java/androidx/camera/extensions/impl/BeautyImageCaptureExtenderImpl.java
+++ b/camera/camera-extensions-stub/src/main/java/androidx/camera/extensions/impl/BeautyImageCaptureExtenderImpl.java
@@ -18,6 +18,7 @@
 import android.content.Context;
 import android.hardware.camera2.CameraCharacteristics;
 import android.util.Pair;
+import android.util.Range;
 import android.util.Size;
 
 import androidx.annotation.NonNull;
@@ -91,4 +92,10 @@
     public List<Pair<Integer, Size[]>> getSupportedResolutions() {
         throw new RuntimeException("Stub, replace with implementation.");
     }
+
+    @Nullable
+    @Override
+    public Range<Long> getEstimatedCaptureLatencyRange(@NonNull Size captureOutputSize) {
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
 }
diff --git a/camera/camera-extensions-stub/src/main/java/androidx/camera/extensions/impl/BokehImageCaptureExtenderImpl.java b/camera/camera-extensions-stub/src/main/java/androidx/camera/extensions/impl/BokehImageCaptureExtenderImpl.java
index dc823c1..b943e0a 100644
--- a/camera/camera-extensions-stub/src/main/java/androidx/camera/extensions/impl/BokehImageCaptureExtenderImpl.java
+++ b/camera/camera-extensions-stub/src/main/java/androidx/camera/extensions/impl/BokehImageCaptureExtenderImpl.java
@@ -18,6 +18,7 @@
 import android.content.Context;
 import android.hardware.camera2.CameraCharacteristics;
 import android.util.Pair;
+import android.util.Range;
 import android.util.Size;
 
 import androidx.annotation.NonNull;
@@ -91,4 +92,10 @@
     public List<Pair<Integer, Size[]>> getSupportedResolutions() {
         throw new RuntimeException("Stub, replace with implementation.");
     }
+
+    @Nullable
+    @Override
+    public Range<Long> getEstimatedCaptureLatencyRange(@NonNull Size captureOutputSize) {
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
 }
diff --git a/camera/camera-extensions-stub/src/main/java/androidx/camera/extensions/impl/HdrImageCaptureExtenderImpl.java b/camera/camera-extensions-stub/src/main/java/androidx/camera/extensions/impl/HdrImageCaptureExtenderImpl.java
index a277fba..66f4a50 100644
--- a/camera/camera-extensions-stub/src/main/java/androidx/camera/extensions/impl/HdrImageCaptureExtenderImpl.java
+++ b/camera/camera-extensions-stub/src/main/java/androidx/camera/extensions/impl/HdrImageCaptureExtenderImpl.java
@@ -18,6 +18,7 @@
 import android.content.Context;
 import android.hardware.camera2.CameraCharacteristics;
 import android.util.Pair;
+import android.util.Range;
 import android.util.Size;
 
 import androidx.annotation.NonNull;
@@ -91,4 +92,10 @@
     public List<Pair<Integer, Size[]>> getSupportedResolutions() {
         throw new RuntimeException("Stub, replace with implementation.");
     }
+
+    @Nullable
+    @Override
+    public Range<Long> getEstimatedCaptureLatencyRange(@NonNull Size captureOutputSize) {
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
 }
diff --git a/camera/camera-extensions-stub/src/main/java/androidx/camera/extensions/impl/ImageCaptureExtenderImpl.java b/camera/camera-extensions-stub/src/main/java/androidx/camera/extensions/impl/ImageCaptureExtenderImpl.java
index b95c047..571c2e3 100644
--- a/camera/camera-extensions-stub/src/main/java/androidx/camera/extensions/impl/ImageCaptureExtenderImpl.java
+++ b/camera/camera-extensions-stub/src/main/java/androidx/camera/extensions/impl/ImageCaptureExtenderImpl.java
@@ -19,6 +19,7 @@
 import android.graphics.ImageFormat;
 import android.hardware.camera2.CameraCharacteristics;
 import android.util.Pair;
+import android.util.Range;
 import android.util.Size;
 
 import androidx.annotation.Nullable;
@@ -80,4 +81,21 @@
      */
     @Nullable
     List<Pair<Integer, Size[]>> getSupportedResolutions();
+
+    /**
+     * Returns the estimated capture latency range in milliseconds for the target capture
+     * resolution.
+     *
+     * <p>This includes the time spent processing the multi-frame capture request along with any
+     * additional time for encoding of the processed buffer in the framework if necessary.
+     *
+     * @param captureOutputSize size of the capture output surface. If it is null or not in the
+     *                          supported output sizes, maximum capture output size is used for
+     *                          the estimation.
+     * @return the range of estimated minimal and maximal capture latency in milliseconds, or
+     * null if no capture latency info can be provided.
+     * @since 1.2
+     */
+    @Nullable
+    Range<Long> getEstimatedCaptureLatencyRange(@Nullable Size captureOutputSize);
 }
diff --git a/camera/camera-extensions-stub/src/main/java/androidx/camera/extensions/impl/NightImageCaptureExtenderImpl.java b/camera/camera-extensions-stub/src/main/java/androidx/camera/extensions/impl/NightImageCaptureExtenderImpl.java
index 8570fd3..3b39cf1 100755
--- a/camera/camera-extensions-stub/src/main/java/androidx/camera/extensions/impl/NightImageCaptureExtenderImpl.java
+++ b/camera/camera-extensions-stub/src/main/java/androidx/camera/extensions/impl/NightImageCaptureExtenderImpl.java
@@ -18,6 +18,7 @@
 import android.content.Context;
 import android.hardware.camera2.CameraCharacteristics;
 import android.util.Pair;
+import android.util.Range;
 import android.util.Size;
 
 import androidx.annotation.NonNull;
@@ -91,4 +92,10 @@
     public List<Pair<Integer, Size[]>> getSupportedResolutions() {
         throw new RuntimeException("Stub, replace with implementation.");
     }
+
+    @Nullable
+    @Override
+    public Range<Long> getEstimatedCaptureLatencyRange(@NonNull Size captureOutputSize) {
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
 }
diff --git a/camera/camera-extensions/build.gradle b/camera/camera-extensions/build.gradle
index 64d147b..9740be7 100644
--- a/camera/camera-extensions/build.gradle
+++ b/camera/camera-extensions/build.gradle
@@ -53,6 +53,7 @@
     androidTestImplementation(KOTLIN_STDLIB)
     androidTestImplementation(MOCKITO_CORE, excludes.bytebuddy) // DexMaker has its own MockMaker
     androidTestImplementation(TRUTH)
+    androidTestImplementation(project(":camera:camera-lifecycle"))
     androidTestImplementation(project(":camera:camera-testing"))
     androidTestImplementation(project(":internal-testutils-truth"))
     androidTestImplementation(project(":camera:integration-tests:camera-testlib-extensions"))
diff --git a/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/ExtensionTest.java b/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/ExtensionTest.java
index 1ebe218..e2ddb55 100644
--- a/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/ExtensionTest.java
+++ b/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/ExtensionTest.java
@@ -38,13 +38,10 @@
 import android.util.Size;
 
 import androidx.annotation.NonNull;
-import androidx.camera.camera2.Camera2Config;
 import androidx.camera.camera2.impl.Camera2ImplConfig;
 import androidx.camera.camera2.impl.CameraEventCallback;
 import androidx.camera.camera2.impl.CameraEventCallbacks;
 import androidx.camera.core.CameraSelector;
-import androidx.camera.core.CameraX;
-import androidx.camera.core.CameraXConfig;
 import androidx.camera.core.ImageCapture;
 import androidx.camera.core.ImageCaptureException;
 import androidx.camera.core.ImageProxy;
@@ -55,8 +52,10 @@
 import androidx.camera.core.internal.CameraUseCaseAdapter;
 import androidx.camera.extensions.ExtensionsManager.EffectMode;
 import androidx.camera.extensions.util.ExtensionsTestUtil;
+import androidx.camera.lifecycle.ProcessCameraProvider;
 import androidx.camera.testing.CameraUtil;
 import androidx.camera.testing.SurfaceTextureProvider;
+import androidx.camera.testing.fakes.FakeLifecycleOwner;
 import androidx.test.core.app.ApplicationProvider;
 import androidx.test.filters.LargeTest;
 import androidx.test.filters.SdkSuppress;
@@ -99,7 +98,10 @@
     @CameraSelector.LensFacing
     private final int mLensFacing;
 
-    private CameraUseCaseAdapter mCamera;
+    private ProcessCameraProvider mProcessCameraProvider = null;
+    private FakeLifecycleOwner mFakeLifecycleOwner;
+    private CameraSelector mBaseCameraSelector;
+    private CameraSelector mExtensionsCameraSelector;
 
     public ExtensionTest(EffectMode effectMode, @CameraSelector.LensFacing int lensFacing) {
         mEffectMode = effectMode;
@@ -110,31 +112,40 @@
     @Before
     public void setUp() throws Exception {
         assumeTrue(CameraUtil.deviceHasCamera());
-
-        CameraXConfig cameraXConfig = Camera2Config.defaultConfig();
-        CameraX.initialize(mContext, cameraXConfig).get();
-
         assumeTrue(CameraUtil.hasCameraWithLensFacing(mLensFacing));
+
+        mProcessCameraProvider = ProcessCameraProvider.getInstance(mContext).get(10000,
+                TimeUnit.MILLISECONDS);
         assumeTrue(ExtensionsTestUtil.initExtensions(mContext));
         assumeTrue(isTargetDeviceAvailableForExtensions(mLensFacing));
-        CameraSelector cameraSelector =
-                new CameraSelector.Builder().requireLensFacing(mLensFacing).build();
-        mCamera = CameraUtil.createCameraUseCaseAdapter(mContext, cameraSelector);
+        mBaseCameraSelector = new CameraSelector.Builder().requireLensFacing(mLensFacing).build();
         Extensions extensions = ExtensionsManager.getExtensions(mContext);
-        assumeTrue(extensions.isExtensionAvailable(mCamera, mExtensionMode));
+        assumeTrue(extensions.isExtensionAvailable(mProcessCameraProvider, mBaseCameraSelector,
+                mExtensionMode));
+
+        mExtensionsCameraSelector = extensions.getExtensionCameraSelector(mBaseCameraSelector,
+                mExtensionMode);
+
+        mFakeLifecycleOwner = new FakeLifecycleOwner();
+        mFakeLifecycleOwner.startAndResume();
     }
 
     @After
     public void cleanUp() throws InterruptedException, ExecutionException, TimeoutException {
-        CameraX.shutdown().get(10000, TimeUnit.MILLISECONDS);
-        ExtensionsManager.deinit().get();
+        if (mProcessCameraProvider != null) {
+            mProcessCameraProvider.shutdown().get(10000, TimeUnit.MILLISECONDS);
+            ExtensionsManager.deinit().get(10000, TimeUnit.MILLISECONDS);
+        }
     }
 
     @Test
-    public void testCanBindToLifeCycleAndTakePicture() {
+    public void testCanBindToLifeCycleAndTakePicture_ByExtenderAPI() {
         ImageCapture.OnImageCapturedCallback mockOnImageCapturedCallback = mock(
                 ImageCapture.OnImageCapturedCallback.class);
 
+        CameraUseCaseAdapter camera = CameraUtil.createCameraUseCaseAdapter(mContext,
+                mBaseCameraSelector);
+
         // To test bind/unbind and take picture.
         ImageCapture imageCapture = ExtensionsTestUtil.createImageCaptureWithEffect(mEffectMode,
                 mLensFacing);
@@ -161,7 +172,7 @@
                             }));
 
                     try {
-                        mCamera.addUseCases(Arrays.asList(preview, imageCapture));
+                        camera.addUseCases(Arrays.asList(preview, imageCapture));
                     } catch (CameraUseCaseAdapter.CameraException e) {
                         throw new IllegalArgumentException("Unable to bind preview and image "
                                 + "capture");
@@ -183,7 +194,7 @@
     }
 
     @Test
-    public void testEventCallbackInConfig() {
+    public void testEventCallbackInConfig_ByExtenderAPI() {
         // Verify Preview config should have related callback.
         PreviewConfig previewConfig = ExtensionsTestUtil.createPreviewConfigWithEffect(mEffectMode,
                 mLensFacing);
@@ -207,6 +218,83 @@
         assertThat(callback2.getAllItems().get(0)).isInstanceOf(CameraEventCallback.class);
     }
 
+    @Test
+    public void testCanBindToLifeCycleAndTakePicture() {
+        ImageCapture.OnImageCapturedCallback mockOnImageCapturedCallback = mock(
+                ImageCapture.OnImageCapturedCallback.class);
+
+        // To test bind/unbind and take picture.
+        ImageCapture imageCapture = new ImageCapture.Builder().build();
+        Preview preview = new Preview.Builder().build();
+
+        mInstrumentation.runOnMainSync(
+                () -> {
+                    // To set the update listener and Preview will change to active state.
+                    preview.setSurfaceProvider(createSurfaceTextureProvider(
+                            new SurfaceTextureProvider.SurfaceTextureCallback() {
+                                @Override
+                                public void onSurfaceTextureReady(
+                                        @NonNull SurfaceTexture surfaceTexture,
+                                        @NonNull Size resolution) {
+                                    // No-op.
+                                }
+
+                                @Override
+                                public void onSafeToRelease(
+                                        @NonNull SurfaceTexture surfaceTexture) {
+                                    // No-op.
+                                }
+                            }));
+
+                    mProcessCameraProvider.bindToLifecycle(mFakeLifecycleOwner,
+                            mExtensionsCameraSelector, preview, imageCapture);
+
+                    imageCapture.takePicture(CameraXExecutors.mainThreadExecutor(),
+                            mockOnImageCapturedCallback);
+                });
+
+        // Verify the image captured.
+        ArgumentCaptor<ImageProxy> imageProxy = ArgumentCaptor.forClass(ImageProxy.class);
+        verify(mockOnImageCapturedCallback, timeout(10000)).onCaptureSuccess(
+                imageProxy.capture());
+        assertNotNull(imageProxy.getValue());
+        imageProxy.getValue().close(); // Close the image after verification.
+
+        // Verify the take picture should not have any error happen.
+        verify(mockOnImageCapturedCallback, never()).onError(any(ImageCaptureException.class));
+    }
+
+    @Test
+    public void testEventCallbackInConfig() {
+        Preview preview = new Preview.Builder().build();
+        ImageCapture imageCapture = new ImageCapture.Builder().build();
+
+        mInstrumentation.runOnMainSync(
+                () -> mProcessCameraProvider.bindToLifecycle(mFakeLifecycleOwner,
+                        mExtensionsCameraSelector, preview, imageCapture));
+
+        // Verify Preview config should have related callback.
+        PreviewConfig previewConfig = (PreviewConfig) preview.getCurrentConfig();
+        assertNotNull(previewConfig.getUseCaseEventCallback());
+        CameraEventCallbacks callback1 = new Camera2ImplConfig(
+                previewConfig).getCameraEventCallback(
+                null);
+        assertNotNull(callback1);
+        assertEquals(callback1.getAllItems().size(), 1);
+        assertThat(callback1.getAllItems().get(0)).isInstanceOf(CameraEventCallback.class);
+
+        // Verify ImageCapture config should have related callback.
+        ImageCaptureConfig imageCaptureConfig =
+                (ImageCaptureConfig) imageCapture.getCurrentConfig();
+        assertNotNull(imageCaptureConfig.getUseCaseEventCallback());
+        assertNotNull(imageCaptureConfig.getCaptureBundle());
+        CameraEventCallbacks callback2 = new Camera2ImplConfig(
+                imageCaptureConfig).getCameraEventCallback(null);
+        assertNotNull(callback2);
+        assertEquals(callback2.getAllItems().size(), 1);
+        assertThat(callback2.getAllItems().get(0)).isInstanceOf(CameraEventCallback.class);
+    }
+
     /**
      * Returns whether the target camera device can support the test cases.
      *
diff --git a/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/ExtensionsErrorListenerTest.java b/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/ExtensionsErrorListenerTest.java
index 0cd4b84..55ffa9f 100644
--- a/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/ExtensionsErrorListenerTest.java
+++ b/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/ExtensionsErrorListenerTest.java
@@ -30,11 +30,8 @@
 import android.os.Build;
 
 import androidx.annotation.NonNull;
-import androidx.camera.camera2.Camera2Config;
 import androidx.camera.core.CameraInfoUnavailableException;
 import androidx.camera.core.CameraSelector;
-import androidx.camera.core.CameraX;
-import androidx.camera.core.CameraXConfig;
 import androidx.camera.core.ImageCapture;
 import androidx.camera.core.Preview;
 import androidx.camera.core.UseCase;
@@ -45,7 +42,9 @@
 import androidx.camera.extensions.impl.ImageCaptureExtenderImpl;
 import androidx.camera.extensions.impl.PreviewExtenderImpl;
 import androidx.camera.extensions.util.ExtensionsTestUtil;
+import androidx.camera.lifecycle.ProcessCameraProvider;
 import androidx.camera.testing.CameraUtil;
+import androidx.camera.testing.fakes.FakeLifecycleOwner;
 import androidx.test.core.app.ApplicationProvider;
 import androidx.test.filters.MediumTest;
 import androidx.test.filters.SdkSuppress;
@@ -78,6 +77,8 @@
     @Rule
     public TestRule mUseCamera = CameraUtil.grantCameraPermissionAndPreTest();
 
+    private static final int TIMEOUT_MILLISECONDS = 10000;
+
     private final Context mContext = ApplicationProvider.getApplicationContext();
 
     Instrumentation mInstrumentation = InstrumentationRegistry.getInstrumentation();
@@ -95,6 +96,9 @@
     @CameraSelector.LensFacing
     private int mLensFacing;
     private CountDownLatch mLatch;
+    private ProcessCameraProvider mProcessCameraProvider = null;
+    private FakeLifecycleOwner mFakeLifecycleOwner;
+    private CameraSelector mExtensionsCameraSelector;
     private CameraUseCaseAdapter mCamera;
 
     final AtomicReference<ExtensionsErrorCode> mErrorCode = new AtomicReference<>();
@@ -120,8 +124,8 @@
     public void setUp() throws InterruptedException, ExecutionException, TimeoutException {
         assumeTrue(CameraUtil.deviceHasCamera());
 
-        CameraXConfig cameraXConfig = Camera2Config.defaultConfig();
-        CameraX.initialize(mContext, cameraXConfig).get();
+        mProcessCameraProvider = ProcessCameraProvider.getInstance(mContext).get(
+                TIMEOUT_MILLISECONDS, TimeUnit.MILLISECONDS);
 
         assumeTrue(CameraUtil.hasCameraWithLensFacing(mLensFacing));
         assumeTrue(ExtensionsTestUtil.initExtensions(mContext));
@@ -129,6 +133,11 @@
 
         mExtensions = ExtensionsManager.getExtensions(mContext);
         mLatch = new CountDownLatch(1);
+
+        mFakeLifecycleOwner = new FakeLifecycleOwner();
+        mFakeLifecycleOwner.startAndResume();
+        mExtensionsCameraSelector =
+                mExtensions.getExtensionCameraSelector(mCameraSelector, mExtensionMode);
     }
 
     @After
@@ -141,8 +150,10 @@
             );
         }
 
-        CameraX.shutdown().get(10000, TimeUnit.MILLISECONDS);
-        ExtensionsManager.deinit().get();
+        if (mProcessCameraProvider != null) {
+            mProcessCameraProvider.shutdown().get(TIMEOUT_MILLISECONDS, TimeUnit.MILLISECONDS);
+            ExtensionsManager.deinit().get(TIMEOUT_MILLISECONDS, TimeUnit.MILLISECONDS);
+        }
     }
 
     @Test
@@ -280,10 +291,9 @@
 
         mErrorCode.set(null);
 
-        mCamera = CameraUtil.createCameraAndAttachUseCase(mContext, mCameraSelector,
-                imageCapture);
-
-        mInstrumentation.runOnMainSync(() -> mExtensions.setExtension(mCamera, mExtensionMode));
+        mInstrumentation.runOnMainSync(
+                () -> mProcessCameraProvider.bindToLifecycle(mFakeLifecycleOwner,
+                        mExtensionsCameraSelector, imageCapture));
 
         // Waits for one second to get error code.
         mLatch.await(1, TimeUnit.SECONDS);
@@ -298,9 +308,9 @@
 
         mErrorCode.set(null);
 
-        mCamera = CameraUtil.createCameraAndAttachUseCase(mContext, mCameraSelector, preview);
-
-        mInstrumentation.runOnMainSync(() -> mExtensions.setExtension(mCamera, mExtensionMode));
+        mInstrumentation.runOnMainSync(
+                () -> mProcessCameraProvider.bindToLifecycle(mFakeLifecycleOwner,
+                        mExtensionsCameraSelector, preview));
 
         // Waits for one second to get error code.
         mLatch.await(1, TimeUnit.SECONDS);
@@ -318,10 +328,9 @@
         ImageCapture imageCapture = new ImageCapture.Builder().build();
         Preview preview = new Preview.Builder().build();
 
-        mCamera = CameraUtil.createCameraAndAttachUseCase(mContext, mCameraSelector, imageCapture,
-                preview);
-
-        mInstrumentation.runOnMainSync(() -> mExtensions.setExtension(mCamera, mExtensionMode));
+        mInstrumentation.runOnMainSync(
+                () -> mProcessCameraProvider.bindToLifecycle(mFakeLifecycleOwner,
+                        mExtensionsCameraSelector, preview, imageCapture));
 
         // Waits for one second to get error code.
         Thread.sleep(1000);
diff --git a/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/ExtensionsTest.kt b/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/ExtensionsTest.kt
index 1b888f7..980c37c 100644
--- a/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/ExtensionsTest.kt
+++ b/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/ExtensionsTest.kt
@@ -17,17 +17,19 @@
 
 import android.content.Context
 import android.os.Build
-import androidx.camera.camera2.Camera2Config
+import androidx.camera.core.Camera
 import androidx.camera.core.CameraSelector
 import androidx.camera.core.CameraSelector.LensFacing
-import androidx.camera.core.CameraX
 import androidx.camera.extensions.Extensions.ExtensionMode
 import androidx.camera.extensions.ExtensionsManager.EffectMode
 import androidx.camera.extensions.util.ExtensionsTestUtil
+import androidx.camera.lifecycle.ProcessCameraProvider
 import androidx.camera.testing.CameraUtil
+import androidx.camera.testing.fakes.FakeLifecycleOwner
 import androidx.test.core.app.ApplicationProvider
 import androidx.test.filters.MediumTest
 import androidx.test.filters.SdkSuppress
+import androidx.test.platform.app.InstrumentationRegistry
 import androidx.testutils.assertThrows
 import com.google.common.truth.Truth.assertThat
 import org.junit.After
@@ -40,36 +42,39 @@
 import java.util.concurrent.ExecutionException
 import java.util.concurrent.TimeUnit
 import java.util.concurrent.TimeoutException
-import kotlin.jvm.Throws
 
 @MediumTest
 @RunWith(Parameterized::class)
 @SdkSuppress(minSdkVersion = Build.VERSION_CODES.M)
 class ExtensionsTest(
-    @field:ExtensionMode @param:ExtensionMode private val mExtensionMode: Int,
-    @field:LensFacing @param:LensFacing private val mLensFacing: Int
+    @field:ExtensionMode @param:ExtensionMode private val extensionMode: Int,
+    @field:LensFacing @param:LensFacing private val lensFacing: Int
 ) {
-    private val mContext =
-        ApplicationProvider.getApplicationContext<Context>()
+    private val context = ApplicationProvider.getApplicationContext<Context>()
 
-    private val mEffectMode: EffectMode =
-        ExtensionsTestUtil.extensionModeToEffectMode(mExtensionMode)
+    private val instrumentation = InstrumentationRegistry.getInstrumentation()
 
-    private lateinit var mExtensions: Extensions
+    private val effectMode: EffectMode =
+        ExtensionsTestUtil.extensionModeToEffectMode(extensionMode)
+
+    private lateinit var extensions: Extensions
+    private lateinit var cameraProvider: ProcessCameraProvider
 
     @Before
     @Throws(Exception::class)
     fun setUp() {
         assumeTrue(CameraUtil.deviceHasCamera())
-        val cameraXConfig = Camera2Config.defaultConfig()
-        CameraX.initialize(mContext, cameraXConfig).get()
+
+        cameraProvider =
+            ProcessCameraProvider.getInstance(context).get(10000, TimeUnit.MILLISECONDS)
+
         assumeTrue(
             CameraUtil.hasCameraWithLensFacing(
-                mLensFacing
+                lensFacing
             )
         )
-        assumeTrue(ExtensionsTestUtil.initExtensions(mContext))
-        mExtensions = ExtensionsManager.getExtensions(mContext)
+        assumeTrue(ExtensionsTestUtil.initExtensions(context))
+        extensions = ExtensionsManager.getExtensions(context)
     }
 
     @After
@@ -79,8 +84,8 @@
         TimeoutException::class
     )
     fun cleanUp() {
-        CameraX.shutdown()[10000, TimeUnit.MILLISECONDS]
-        ExtensionsManager.deinit().get()
+        cameraProvider.shutdown()[10000, TimeUnit.MILLISECONDS]
+        ExtensionsManager.deinit().get(10000, TimeUnit.MILLISECONDS)
     }
 
     companion object {
@@ -93,37 +98,55 @@
     // TODO: Can be removed after the Extensions class is fully implemented.
     @Test
     fun isExtensionAvailable() {
-        val cameraSelector = CameraSelector.Builder().requireLensFacing(mLensFacing).build()
-        val camera = CameraUtil.createCameraUseCaseAdapter(mContext, cameraSelector)
+        val cameraSelector = CameraSelector.Builder().requireLensFacing(lensFacing).build()
 
-        assertThat(
-            ExtensionsManager.isExtensionAvailable(mEffectMode, mLensFacing) ==
-                mExtensions.isExtensionAvailable(camera, mExtensionMode)
-        ).isTrue()
+        assertThat(ExtensionsManager.isExtensionAvailable(effectMode, lensFacing)).isEqualTo(
+            extensions.isExtensionAvailable(cameraProvider, cameraSelector, extensionMode)
+        )
     }
 
     @Test
     fun setExtensionSucceedsIfAvailable() {
-        val cameraSelector = CameraSelector.Builder().requireLensFacing(mLensFacing).build()
-        val camera = CameraUtil.createCameraUseCaseAdapter(mContext, cameraSelector)
+        val baseCameraSelector = CameraSelector.Builder().requireLensFacing(lensFacing).build()
 
-        assumeTrue(mExtensions.isExtensionAvailable(camera, mExtensionMode))
+        assumeTrue(
+            extensions.isExtensionAvailable(
+                cameraProvider,
+                baseCameraSelector,
+                extensionMode
+            )
+        )
 
-        mExtensions.setExtension(camera, mExtensionMode)
-        assertThat(mExtensions.getExtension(camera)).isEqualTo(mExtensionMode)
+        val extensionCameraSelector =
+            extensions.getExtensionCameraSelector(baseCameraSelector, extensionMode)
+
+        lateinit var camera: Camera
+        instrumentation.runOnMainSync {
+            camera = cameraProvider.bindToLifecycle(FakeLifecycleOwner(), extensionCameraSelector)
+        }
+
+        assertThat(extensions.getExtension(camera)).isEqualTo(extensionMode)
     }
 
     @Test
     fun setExtensionFailsIfNotAvailable() {
-        val cameraSelector = CameraSelector.Builder().requireLensFacing(mLensFacing).build()
-        val camera = CameraUtil.createCameraUseCaseAdapter(mContext, cameraSelector)
+        val baseCameraSelector = CameraSelector.Builder().requireLensFacing(lensFacing).build()
 
-        assumeFalse(mExtensions.isExtensionAvailable(camera, mExtensionMode))
+        assumeFalse(
+            extensions.isExtensionAvailable(
+                cameraProvider,
+                baseCameraSelector,
+                extensionMode
+            )
+        )
 
-        assertThrows<IllegalArgumentException> {
-            mExtensions.setExtension(camera, mExtensionMode)
+        val extensionCameraSelector =
+            extensions.getExtensionCameraSelector(baseCameraSelector, extensionMode)
+
+        instrumentation.runOnMainSync {
+            assertThrows<IllegalArgumentException> {
+                cameraProvider.bindToLifecycle(FakeLifecycleOwner(), extensionCameraSelector)
+            }
         }
-
-        assertThat(mExtensions.getExtension(camera)).isEqualTo(Extensions.EXTENSION_MODE_NONE)
     }
-}
\ No newline at end of file
+}
diff --git a/camera/camera-extensions/src/main/java/androidx/camera/extensions/ExtensionCameraFilter.java b/camera/camera-extensions/src/main/java/androidx/camera/extensions/ExtensionCameraFilter.java
index 06237ee..811b450 100644
--- a/camera/camera-extensions/src/main/java/androidx/camera/extensions/ExtensionCameraFilter.java
+++ b/camera/camera-extensions/src/main/java/androidx/camera/extensions/ExtensionCameraFilter.java
@@ -40,25 +40,59 @@
  */
 @OptIn(markerClass = ExperimentalCameraFilter.class)
 public final class ExtensionCameraFilter implements CameraFilter {
+    private final Id mId;
     private final PreviewExtenderImpl mPreviewExtenderImpl;
     private final ImageCaptureExtenderImpl mImageCaptureExtenderImpl;
 
+    // TODO(b/183075483): These three constructors will be removed after new Extensions APIs are
+    //  public and the old extender APIs are removed.
     ExtensionCameraFilter(@Nullable PreviewExtenderImpl previewExtenderImpl) {
+        mId = Id.DEFAULT;
         mPreviewExtenderImpl = previewExtenderImpl;
         mImageCaptureExtenderImpl = null;
     }
 
     ExtensionCameraFilter(@Nullable ImageCaptureExtenderImpl imageCaptureExtenderImpl) {
+        mId = Id.DEFAULT;
         mPreviewExtenderImpl = null;
         mImageCaptureExtenderImpl = imageCaptureExtenderImpl;
     }
 
     ExtensionCameraFilter(@Nullable PreviewExtenderImpl previewExtenderImpl,
             @Nullable ImageCaptureExtenderImpl imageCaptureExtenderImpl) {
+        mId = Id.DEFAULT;
         mPreviewExtenderImpl = previewExtenderImpl;
         mImageCaptureExtenderImpl = imageCaptureExtenderImpl;
     }
 
+    ExtensionCameraFilter(@NonNull String filterId,
+            @Nullable PreviewExtenderImpl previewExtenderImpl) {
+        mId = Id.create(filterId);
+        mPreviewExtenderImpl = previewExtenderImpl;
+        mImageCaptureExtenderImpl = null;
+    }
+
+    ExtensionCameraFilter(@NonNull String filterId,
+            @Nullable ImageCaptureExtenderImpl imageCaptureExtenderImpl) {
+        mId = Id.create(filterId);
+        mPreviewExtenderImpl = null;
+        mImageCaptureExtenderImpl = imageCaptureExtenderImpl;
+    }
+
+    ExtensionCameraFilter(@NonNull String filterId,
+            @Nullable PreviewExtenderImpl previewExtenderImpl,
+            @Nullable ImageCaptureExtenderImpl imageCaptureExtenderImpl) {
+        mId = Id.create(filterId);
+        mPreviewExtenderImpl = previewExtenderImpl;
+        mImageCaptureExtenderImpl = imageCaptureExtenderImpl;
+    }
+
+    @NonNull
+    @Override
+    public Id getId() {
+        return mId;
+    }
+
     @OptIn(markerClass = ExperimentalCamera2Interop.class)
     @NonNull
     @Override
diff --git a/camera/camera-extensions/src/main/java/androidx/camera/extensions/Extensions.java b/camera/camera-extensions/src/main/java/androidx/camera/extensions/Extensions.java
index f70b0b3..e13bc80 100644
--- a/camera/camera-extensions/src/main/java/androidx/camera/extensions/Extensions.java
+++ b/camera/camera-extensions/src/main/java/androidx/camera/extensions/Extensions.java
@@ -24,12 +24,12 @@
 import androidx.annotation.RestrictTo;
 import androidx.camera.core.Camera;
 import androidx.camera.core.CameraFilter;
-import androidx.camera.core.CameraInfo;
+import androidx.camera.core.CameraProvider;
 import androidx.camera.core.CameraSelector;
 import androidx.camera.core.ExperimentalCameraFilter;
-import androidx.camera.core.UseCase;
+import androidx.camera.core.impl.CameraConfigProvider;
 import androidx.camera.core.impl.CameraFilters;
-import androidx.camera.core.internal.CameraUseCaseAdapter;
+import androidx.camera.core.impl.ExtendedCameraConfigProviderStore;
 import androidx.camera.extensions.impl.AutoImageCaptureExtenderImpl;
 import androidx.camera.extensions.impl.AutoPreviewExtenderImpl;
 import androidx.camera.extensions.impl.BeautyImageCaptureExtenderImpl;
@@ -44,28 +44,25 @@
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
-import java.util.LinkedHashSet;
-import java.util.List;
 
 /**
- * A class for querying and controlling the extensions that are enable for individual
- * {@link Camera} instances.
+ * A class for querying extensions related information.
  *
- * <p> The typical usages is to check whether or not a Camera and/or the {@link UseCase}
- * combinations support the extension by using {@link #isExtensionAvailable(Camera, int)} and
- * {@link #checkUseCases(Camera, List, int)}. Then after it has been determined that the
- * extension can be enable then a {@link #setExtension(Camera, int)} call can be used to set the
- * specified extension on the camera.
+ * <p>The typical usages include checking whether or not a camera exists that supports an extension
+ * by using {@link #isExtensionAvailable(CameraProvider, CameraSelector, int)}. Then after it has
+ * been determined that the extension can be enabled, a
+ * {@link #getExtensionCameraSelector(CameraSelector, int)} call can be used to get the
+ * specified {@link CameraSelector} to bind use cases and enable the extension mode on the camera.
  *
- * <p> When the Camera has been set to a particular extension it might require the camera to
- * restart which can cause the preview to temporarily stop. Once the extension has been enable
+ * <p>When the Camera has been set to a particular extension it might require the camera to
+ * restart which can cause the preview to momentarily stop. Once the extension has been enabled
  * for a Camera instance then it will stay in that extension mode until the extension has been
- * disabled. Setting extension modes is separate for each camera instance.
+ * disabled.
  *
  * @hide
  */
 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-public class Extensions {
+public final class Extensions {
     private static final String TAG = "Extensions";
 
     /** Normal mode without any specific effect applied. */
@@ -93,14 +90,14 @@
      */
     public static final int EXTENSION_MODE_AUTO = 5;
 
-    private final Context mContext;
-
+    private static final String EXTENDED_CAMERA_CONFIG_PROVIDER_ID_PREFIX = ":camera:camera"
+            + "-extensions-";
     /**
      * The different extension modes that a {@link Camera} can be configured for.
      *
-     * <p>Not all devices and camera support the different extension modes. To query whether or
+     * <p>Not all devices and cameras support the different extension modes. To query whether or
      * not a specific Camera supports an extension mode use
-     * {@link Extensions#isExtensionAvailable(Camera, int)}.
+     * {@link Extensions#isExtensionAvailable(CameraProvider, CameraSelector, int)}.
      *
      * @hide
      */
@@ -112,63 +109,52 @@
     }
 
     Extensions(@NonNull Context context) {
-        mContext = context;
     }
 
     /**
-     * Sets the specified extension mode for the Camera.
+     * Returns a {@link CameraSelector} for the specific extension mode.
      *
-     * To return to a non-extensions mode Camera set the extension mode as NONE.
-     * For full list of extensions see Extension Modes
-     *
-     * @param camera The camera that the UseCases are attached to
-     * @param mode   The extension mode to set. Setting this to EXTENSION_NONE will
-     *               remove the current extension and will always succeed.
-     * @throws IllegalArgumentException if the specified extension mode can not be set on the
-     *                                  Camera. This can happen if the extension is not supported
-     *                                  on this camera. This might also
-     *                                  because the combination of UseCases attached to the
-     *                                  Camera. Use {@link
-     *                                  #checkUseCases(Camera, List, int)} to verify that the
-     *                                  Camera can support the list of
-     *                                  UseCases for the extension.
+     * @param baseCameraSelector The base {@link CameraSelector} to be applied the extension
+     *                           related configuration on.
+     *                         {@link #isExtensionAvailable(CameraProvider, CameraSelector, int)}
+     *                          can be used to check whether any camera can support the specified
+     *                          extension mode for the base camera selector.
+     * @param mode The target extension mode.
+     * @return a {@link CameraSelector} for the specific Extensions mode.
+     * @throws IllegalArgumentException if the base {@link CameraSelector} has contained
+     * extension related configuration in it.
      */
     @OptIn(markerClass = ExperimentalCameraFilter.class)
-    public void setExtension(@NonNull Camera camera, @ExtensionMode int mode) {
-        if (!isExtensionAvailable(camera, mode)) {
-            throw new IllegalArgumentException("Extension mode not supported on camera: " + mode);
+    @NonNull
+    public CameraSelector getExtensionCameraSelector(@NonNull CameraSelector baseCameraSelector,
+            @ExtensionMode int mode) {
+        // Checks whether there has been Extensions related CameraConfig set in the base
+        // CameraSelector.
+        for (CameraFilter cameraFilter : baseCameraSelector.getCameraFilterSet()) {
+            if (cameraFilter instanceof ExtensionCameraFilter) {
+                throw new IllegalArgumentException(
+                        "An extension is already applied to the base CameraSelector.");
+            }
         }
 
-        CameraSelector cameraSelector =
-                new CameraSelector.Builder().addCameraFilter(getFilter(mode)).build();
-        Camera extensionCamera =
-                cameraSelector.select(new LinkedHashSet<>(camera.getCameraInternals()));
-        CameraInfo extensionsCameraInfo = extensionCamera.getCameraInfo();
+        // Injects CameraConfigProvider for the extension mode to the
+        // ExtendedCameraConfigProviderStore.
+        injectExtensionCameraConfig(mode);
 
-        ExtensionsUseCaseConfigFactory factory = new ExtensionsUseCaseConfigFactory(mode,
-                extensionsCameraInfo, mContext);
+        CameraSelector.Builder builder = CameraSelector.Builder.fromSelector(baseCameraSelector);
 
-        // Set the Camera
-        ExtensionsConfig extensionsConfig =
-                new ExtensionsConfig.Builder()
-                        .setExtensionMode(mode)
-                        .setCameraFilter(getFilter(mode))
-                        .setUseCaseConfigFactory(factory)
-                        .build();
+        // Adds the CameraFilter that determines which cameras can support the Extensions mode
+        // to the CameraSelector.
+        builder.addCameraFilter(getFilter(mode));
 
-        // Set the config on the camera
-        try {
-            camera.setExtendedConfig(extensionsConfig);
-        } catch (CameraUseCaseAdapter.CameraException e) {
-            throw new IllegalArgumentException("Camera unable to support the extension with the "
-                    + "attached UseCases. " + e);
-        }
+        return builder.build();
     }
 
     /**
      * Returns the extension mode that is currently set on the camera.
      */
-    public @ExtensionMode int getExtension(@NonNull Camera camera) {
+    @ExtensionMode
+    public int getExtension(@NonNull Camera camera) {
         Object extensionsConfigObject = camera.getExtendedConfig();
 
         if (extensionsConfigObject instanceof ExtensionsConfig) {
@@ -180,53 +166,55 @@
 
     /**
      * Returns true if the particular extension mode is available for the specified
-     * Camera.
+     * {@link CameraSelector}.
      *
-     * <p> This check is independent of the {@link UseCase} which are currently attached to the
-     * {@link Camera}. To check whether the Camera can support the attached UseCases use {@link
-     * #checkUseCases(Camera, List, int)}.
-     *
-     * @param camera The Camera to check if it supports the extension.
-     * @param mode   The extension mode to check
+     * @param cameraProvider The {@link CameraProvider} which will be used to bind use cases.
+     * @param baseCameraSelector The base {@link CameraSelector} to find a camera to use.
+     * @param mode The target extension mode to support.
      */
     @OptIn(markerClass = ExperimentalCameraFilter.class)
-    public boolean isExtensionAvailable(@NonNull Camera camera, @ExtensionMode int mode) {
-        CameraSelector cameraSelector =
-                new CameraSelector.Builder().addCameraFilter(getFilter(mode)).build();
-
-        // Extension is available for the camera if it contains a CameraInternal which supports
-        // the extension
+    public boolean isExtensionAvailable(
+            @NonNull CameraProvider cameraProvider,
+            @NonNull CameraSelector baseCameraSelector,
+            @ExtensionMode int mode) {
         try {
-            cameraSelector.select(new LinkedHashSet<>(camera.getCameraInternals()));
+            CameraSelector.Builder builder = CameraSelector.Builder.fromSelector(
+                    baseCameraSelector);
+            builder.addCameraFilter(getFilter(mode));
+
+            builder.build().filter(cameraProvider.getAvailableCameraInfos());
         } catch (IllegalArgumentException e) {
             return false;
         }
+
         return true;
     }
 
     @OptIn(markerClass = ExperimentalCameraFilter.class)
     private CameraFilter getFilter(@ExtensionMode int mode) {
         CameraFilter filter;
+        String id = getExtendedCameraConfigProviderId(mode);
+
         try {
             switch (mode) {
                 case EXTENSION_MODE_BOKEH:
-                    filter = new ExtensionCameraFilter(new BokehPreviewExtenderImpl(),
+                    filter = new ExtensionCameraFilter(id, new BokehPreviewExtenderImpl(),
                             new BokehImageCaptureExtenderImpl());
                     break;
                 case EXTENSION_MODE_HDR:
-                    filter = new ExtensionCameraFilter(new HdrPreviewExtenderImpl(),
+                    filter = new ExtensionCameraFilter(id, new HdrPreviewExtenderImpl(),
                             new HdrImageCaptureExtenderImpl());
                     break;
                 case EXTENSION_MODE_NIGHT:
-                    filter = new ExtensionCameraFilter(new NightPreviewExtenderImpl(),
+                    filter = new ExtensionCameraFilter(id, new NightPreviewExtenderImpl(),
                             new NightImageCaptureExtenderImpl());
                     break;
                 case EXTENSION_MODE_BEAUTY:
-                    filter = new ExtensionCameraFilter(new BeautyPreviewExtenderImpl(),
+                    filter = new ExtensionCameraFilter(id, new BeautyPreviewExtenderImpl(),
                             new BeautyImageCaptureExtenderImpl());
                     break;
                 case EXTENSION_MODE_AUTO:
-                    filter = new ExtensionCameraFilter(new AutoPreviewExtenderImpl(),
+                    filter = new ExtensionCameraFilter(id, new AutoPreviewExtenderImpl(),
                             new AutoImageCaptureExtenderImpl());
                     break;
                 case EXTENSION_MODE_NONE:
@@ -241,20 +229,49 @@
     }
 
     /**
-     * Checks if the list of UseCases attached to the Camera can support the
-     * extension.
-     *
-     * If the list of UseCases exceeds the capacity of Surfaces for the Camera then
-     * it returns a list of UseCase lists that can be removed in order to allow for
-     * the extension to be enabled. Any of the individual lists can be removed.
-     *
-     * @return null if the Camera supports the extension using the list of UseCases, otherwise a
-     * list of UseCase list to remove.
+     * Injects {@link CameraConfigProvider} for specific extension mode to the
+     * {@link ExtendedCameraConfigProviderStore}.
      */
-    @NonNull
-    public List<List<UseCase>> checkUseCases(@NonNull Camera camera,
-            @NonNull List<UseCase> useCases,
-            @ExtensionMode int mode) {
-        throw new UnsupportedOperationException("not yet implemented");
+    private void injectExtensionCameraConfig(@ExtensionMode int mode) {
+        CameraFilter.Id id = CameraFilter.Id.create(getExtendedCameraConfigProviderId(mode));
+
+        if (ExtendedCameraConfigProviderStore.getConfig(id) == CameraConfigProvider.EMPTY) {
+            ExtendedCameraConfigProviderStore.addConfig(id, (cameraInfo, context) -> {
+                ExtensionsUseCaseConfigFactory factory = new
+                        ExtensionsUseCaseConfigFactory(mode, cameraInfo, context);
+                return new ExtensionsConfig.Builder()
+                        .setExtensionMode(mode)
+                        .setUseCaseConfigFactory(factory)
+                        .build();
+            });
+        }
+    }
+
+    private String getExtendedCameraConfigProviderId(@ExtensionMode int mode) {
+        String id;
+
+        switch (mode) {
+            case EXTENSION_MODE_BOKEH:
+                id = EXTENDED_CAMERA_CONFIG_PROVIDER_ID_PREFIX + "EXTENSION_MODE_BOKEH";
+                break;
+            case EXTENSION_MODE_HDR:
+                id = EXTENDED_CAMERA_CONFIG_PROVIDER_ID_PREFIX + "EXTENSION_MODE_HDR";
+                break;
+            case EXTENSION_MODE_NIGHT:
+                id = EXTENDED_CAMERA_CONFIG_PROVIDER_ID_PREFIX + "EXTENSION_MODE_NIGHT";
+                break;
+            case EXTENSION_MODE_BEAUTY:
+                id = EXTENDED_CAMERA_CONFIG_PROVIDER_ID_PREFIX + "EXTENSION_MODE_BEAUTY";
+                break;
+            case EXTENSION_MODE_AUTO:
+                id = EXTENDED_CAMERA_CONFIG_PROVIDER_ID_PREFIX + "EXTENSION_MODE_AUTO";
+                break;
+            case EXTENSION_MODE_NONE:
+                id = EXTENDED_CAMERA_CONFIG_PROVIDER_ID_PREFIX + "EXTENSION_MODE_NONE";
+                break;
+            default:
+                throw new IllegalArgumentException("Invalid extension mode!");
+        }
+        return id;
     }
 }
diff --git a/camera/camera-lifecycle/lint-baseline.xml b/camera/camera-lifecycle/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/camera/camera-lifecycle/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/camera/camera-lifecycle/src/main/java/androidx/camera/lifecycle/ProcessCameraProvider.java b/camera/camera-lifecycle/src/main/java/androidx/camera/lifecycle/ProcessCameraProvider.java
index ddebddb..5233c98 100644
--- a/camera/camera-lifecycle/src/main/java/androidx/camera/lifecycle/ProcessCameraProvider.java
+++ b/camera/camera-lifecycle/src/main/java/androidx/camera/lifecycle/ProcessCameraProvider.java
@@ -42,7 +42,11 @@
 import androidx.camera.core.UseCase;
 import androidx.camera.core.UseCaseGroup;
 import androidx.camera.core.ViewPort;
+import androidx.camera.core.impl.CameraConfig;
+import androidx.camera.core.impl.CameraConfigs;
 import androidx.camera.core.impl.CameraInternal;
+import androidx.camera.core.impl.ExtendedCameraConfigProviderStore;
+import androidx.camera.core.impl.utils.ContextUtil;
 import androidx.camera.core.impl.utils.Threads;
 import androidx.camera.core.impl.utils.executor.CameraXExecutors;
 import androidx.camera.core.impl.utils.futures.Futures;
@@ -83,6 +87,7 @@
     private final LifecycleCameraRepository
             mLifecycleCameraRepository = new LifecycleCameraRepository();
     private CameraX mCameraX;
+    private Context mContext;
 
     /**
      * Retrieves the {@link ProcessCameraProvider} associated with the current process.
@@ -152,6 +157,7 @@
         Preconditions.checkNotNull(context);
         return Futures.transform(CameraX.getOrCreateInstance(context), cameraX ->  {
             sAppInstance.setCameraX(cameraX);
+            sAppInstance.setContext(ContextUtil.getApplicationContext(context));
             return sAppInstance;
         }, CameraXExecutors.directExecutor());
     }
@@ -213,6 +219,10 @@
         mCameraX = cameraX;
     }
 
+    private void setContext(Context context) {
+        mContext = context;
+    }
+
     /**
      * Binds the collection of {@link UseCase} to a {@link LifecycleOwner}.
      *
@@ -421,6 +431,33 @@
                                     mCameraX.getDefaultConfigFactory()));
         }
 
+        CameraConfig cameraConfig = CameraConfigs.emptyConfig();
+
+        // Retrieves extended camera configs from ExtendedCameraConfigProviderStore
+        for (CameraFilter cameraFilter : cameraSelector.getCameraFilterSet()) {
+            if (cameraFilter.getId() != CameraFilter.Id.DEFAULT) {
+                CameraConfig extendedCameraConfig = ExtendedCameraConfigProviderStore.getConfig(
+                        cameraFilter.getId()).getConfig(lifecycleCameraToBind.getCameraInfo(),
+                        mContext);
+
+                // Only allows one camera config now.
+                if (extendedCameraConfig != CameraConfigs.emptyConfig()
+                        && cameraConfig != CameraConfigs.emptyConfig()) {
+                    throw new IllegalArgumentException(
+                            "Cannot apply multiple extended camera configs at the same time.");
+                } else {
+                    cameraConfig = extendedCameraConfig;
+                }
+            }
+        }
+
+        // Applies extended camera configs to the camera
+        try {
+            lifecycleCameraToBind.setExtendedConfig(cameraConfig);
+        } catch (CameraUseCaseAdapter.CameraException e) {
+            throw new IllegalArgumentException(e.getMessage());
+        }
+
         if (useCases.length == 0) {
             return lifecycleCameraToBind;
         }
diff --git a/camera/camera-view/api/current.txt b/camera/camera-view/api/current.txt
index 645b302..160857f 100644
--- a/camera/camera-view/api/current.txt
+++ b/camera/camera-view/api/current.txt
@@ -33,40 +33,6 @@
     field public static final int IMAGE_CAPTURE = 1; // 0x1
   }
 
-  @Deprecated public final class CameraView extends android.widget.FrameLayout {
-    ctor @Deprecated public CameraView(android.content.Context);
-    ctor @Deprecated public CameraView(android.content.Context, android.util.AttributeSet?);
-    ctor @Deprecated public CameraView(android.content.Context, android.util.AttributeSet?, int);
-    ctor @Deprecated @RequiresApi(21) public CameraView(android.content.Context, android.util.AttributeSet?, int, int);
-    method @Deprecated @RequiresPermission(android.Manifest.permission.CAMERA) public void bindToLifecycle(androidx.lifecycle.LifecycleOwner);
-    method @Deprecated public void enableTorch(boolean);
-    method @Deprecated public Integer? getCameraLensFacing();
-    method @Deprecated public androidx.camera.view.CameraView.CaptureMode getCaptureMode();
-    method @Deprecated public int getFlash();
-    method @Deprecated public float getMaxZoomRatio();
-    method @Deprecated public float getMinZoomRatio();
-    method @Deprecated public androidx.lifecycle.LiveData<androidx.camera.view.PreviewView.StreamState!> getPreviewStreamState();
-    method @Deprecated public androidx.camera.view.PreviewView.ScaleType getScaleType();
-    method @Deprecated public float getZoomRatio();
-    method @Deprecated @RequiresPermission(android.Manifest.permission.CAMERA) public boolean hasCameraWithLensFacing(int);
-    method @Deprecated public boolean isPinchToZoomEnabled();
-    method @Deprecated public boolean isTorchOn();
-    method @Deprecated public boolean isZoomSupported();
-    method @Deprecated public void setCameraLensFacing(Integer?);
-    method @Deprecated public void setCaptureMode(androidx.camera.view.CameraView.CaptureMode);
-    method @Deprecated public void setFlash(int);
-    method @Deprecated public void setPinchToZoomEnabled(boolean);
-    method @Deprecated public void setScaleType(androidx.camera.view.PreviewView.ScaleType);
-    method @Deprecated public void setZoomRatio(float);
-    method @Deprecated public void takePicture(java.util.concurrent.Executor, androidx.camera.core.ImageCapture.OnImageCapturedCallback);
-    method @Deprecated public void takePicture(androidx.camera.core.ImageCapture.OutputFileOptions, java.util.concurrent.Executor, androidx.camera.core.ImageCapture.OnImageSavedCallback);
-    method @Deprecated public void toggleCamera();
-  }
-
-  @Deprecated public enum CameraView.CaptureMode {
-    enum_constant @Deprecated public static final androidx.camera.view.CameraView.CaptureMode IMAGE;
-  }
-
   public final class LifecycleCameraController extends androidx.camera.view.CameraController {
     ctor public LifecycleCameraController(android.content.Context);
     method @MainThread public void bindToLifecycle(androidx.lifecycle.LifecycleOwner);
diff --git a/camera/camera-view/api/public_plus_experimental_current.txt b/camera/camera-view/api/public_plus_experimental_current.txt
index 6dbb420..4190144 100644
--- a/camera/camera-view/api/public_plus_experimental_current.txt
+++ b/camera/camera-view/api/public_plus_experimental_current.txt
@@ -38,47 +38,6 @@
     field @androidx.camera.view.video.ExperimentalVideo public static final int VIDEO_CAPTURE = 4; // 0x4
   }
 
-  @Deprecated public final class CameraView extends android.widget.FrameLayout {
-    ctor @Deprecated public CameraView(android.content.Context);
-    ctor @Deprecated public CameraView(android.content.Context, android.util.AttributeSet?);
-    ctor @Deprecated public CameraView(android.content.Context, android.util.AttributeSet?, int);
-    ctor @Deprecated @RequiresApi(21) public CameraView(android.content.Context, android.util.AttributeSet?, int, int);
-    method @Deprecated @RequiresPermission(android.Manifest.permission.CAMERA) public void bindToLifecycle(androidx.lifecycle.LifecycleOwner);
-    method @Deprecated public void enableTorch(boolean);
-    method @Deprecated public Integer? getCameraLensFacing();
-    method @Deprecated public androidx.camera.view.CameraView.CaptureMode getCaptureMode();
-    method @Deprecated @androidx.camera.core.ImageCapture.FlashMode public int getFlash();
-    method @Deprecated public float getMaxZoomRatio();
-    method @Deprecated public float getMinZoomRatio();
-    method @Deprecated public androidx.lifecycle.LiveData<androidx.camera.view.PreviewView.StreamState!> getPreviewStreamState();
-    method @Deprecated public androidx.camera.view.PreviewView.ScaleType getScaleType();
-    method @Deprecated public float getZoomRatio();
-    method @Deprecated @RequiresPermission(android.Manifest.permission.CAMERA) public boolean hasCameraWithLensFacing(@androidx.camera.core.CameraSelector.LensFacing int);
-    method @Deprecated public boolean isPinchToZoomEnabled();
-    method @Deprecated @androidx.camera.view.video.ExperimentalVideo public boolean isRecording();
-    method @Deprecated public boolean isTorchOn();
-    method @Deprecated public boolean isZoomSupported();
-    method @Deprecated public void setCameraLensFacing(Integer?);
-    method @Deprecated public void setCaptureMode(androidx.camera.view.CameraView.CaptureMode);
-    method @Deprecated public void setFlash(@androidx.camera.core.ImageCapture.FlashMode int);
-    method @Deprecated public void setPinchToZoomEnabled(boolean);
-    method @Deprecated public void setScaleType(androidx.camera.view.PreviewView.ScaleType);
-    method @Deprecated public void setZoomRatio(float);
-    method @Deprecated @androidx.camera.view.video.ExperimentalVideo public void startRecording(java.io.File, java.util.concurrent.Executor, androidx.camera.view.video.OnVideoSavedCallback);
-    method @Deprecated @androidx.camera.view.video.ExperimentalVideo public void startRecording(android.os.ParcelFileDescriptor, java.util.concurrent.Executor, androidx.camera.view.video.OnVideoSavedCallback);
-    method @Deprecated @androidx.camera.view.video.ExperimentalVideo public void startRecording(androidx.camera.view.video.OutputFileOptions, java.util.concurrent.Executor, androidx.camera.view.video.OnVideoSavedCallback);
-    method @Deprecated @androidx.camera.view.video.ExperimentalVideo public void stopRecording();
-    method @Deprecated public void takePicture(java.util.concurrent.Executor, androidx.camera.core.ImageCapture.OnImageCapturedCallback);
-    method @Deprecated public void takePicture(androidx.camera.core.ImageCapture.OutputFileOptions, java.util.concurrent.Executor, androidx.camera.core.ImageCapture.OnImageSavedCallback);
-    method @Deprecated public void toggleCamera();
-  }
-
-  @Deprecated public enum CameraView.CaptureMode {
-    enum_constant @Deprecated public static final androidx.camera.view.CameraView.CaptureMode IMAGE;
-    enum_constant @Deprecated @androidx.camera.view.video.ExperimentalVideo public static final androidx.camera.view.CameraView.CaptureMode MIXED;
-    enum_constant @Deprecated @androidx.camera.view.video.ExperimentalVideo public static final androidx.camera.view.CameraView.CaptureMode VIDEO;
-  }
-
   public final class LifecycleCameraController extends androidx.camera.view.CameraController {
     ctor public LifecycleCameraController(android.content.Context);
     method @MainThread public void bindToLifecycle(androidx.lifecycle.LifecycleOwner);
diff --git a/camera/camera-view/api/restricted_current.txt b/camera/camera-view/api/restricted_current.txt
index d0ba609..56cdeaa 100644
--- a/camera/camera-view/api/restricted_current.txt
+++ b/camera/camera-view/api/restricted_current.txt
@@ -33,40 +33,6 @@
     field public static final int IMAGE_CAPTURE = 1; // 0x1
   }
 
-  @Deprecated public final class CameraView extends android.widget.FrameLayout {
-    ctor @Deprecated public CameraView(android.content.Context);
-    ctor @Deprecated public CameraView(android.content.Context, android.util.AttributeSet?);
-    ctor @Deprecated public CameraView(android.content.Context, android.util.AttributeSet?, int);
-    ctor @Deprecated @RequiresApi(21) public CameraView(android.content.Context, android.util.AttributeSet?, int, int);
-    method @Deprecated @RequiresPermission(android.Manifest.permission.CAMERA) public void bindToLifecycle(androidx.lifecycle.LifecycleOwner);
-    method @Deprecated public void enableTorch(boolean);
-    method @Deprecated public Integer? getCameraLensFacing();
-    method @Deprecated public androidx.camera.view.CameraView.CaptureMode getCaptureMode();
-    method @Deprecated @androidx.camera.core.ImageCapture.FlashMode public int getFlash();
-    method @Deprecated public float getMaxZoomRatio();
-    method @Deprecated public float getMinZoomRatio();
-    method @Deprecated public androidx.lifecycle.LiveData<androidx.camera.view.PreviewView.StreamState!> getPreviewStreamState();
-    method @Deprecated public androidx.camera.view.PreviewView.ScaleType getScaleType();
-    method @Deprecated public float getZoomRatio();
-    method @Deprecated @RequiresPermission(android.Manifest.permission.CAMERA) public boolean hasCameraWithLensFacing(@androidx.camera.core.CameraSelector.LensFacing int);
-    method @Deprecated public boolean isPinchToZoomEnabled();
-    method @Deprecated public boolean isTorchOn();
-    method @Deprecated public boolean isZoomSupported();
-    method @Deprecated public void setCameraLensFacing(Integer?);
-    method @Deprecated public void setCaptureMode(androidx.camera.view.CameraView.CaptureMode);
-    method @Deprecated public void setFlash(@androidx.camera.core.ImageCapture.FlashMode int);
-    method @Deprecated public void setPinchToZoomEnabled(boolean);
-    method @Deprecated public void setScaleType(androidx.camera.view.PreviewView.ScaleType);
-    method @Deprecated public void setZoomRatio(float);
-    method @Deprecated public void takePicture(java.util.concurrent.Executor, androidx.camera.core.ImageCapture.OnImageCapturedCallback);
-    method @Deprecated public void takePicture(androidx.camera.core.ImageCapture.OutputFileOptions, java.util.concurrent.Executor, androidx.camera.core.ImageCapture.OnImageSavedCallback);
-    method @Deprecated public void toggleCamera();
-  }
-
-  @Deprecated public enum CameraView.CaptureMode {
-    enum_constant @Deprecated public static final androidx.camera.view.CameraView.CaptureMode IMAGE;
-  }
-
   public final class LifecycleCameraController extends androidx.camera.view.CameraController {
     ctor public LifecycleCameraController(android.content.Context);
     method @MainThread public void bindToLifecycle(androidx.lifecycle.LifecycleOwner);
diff --git a/camera/camera-view/build.gradle b/camera/camera-view/build.gradle
index 06c00c8..2b2420f 100644
--- a/camera/camera-view/build.gradle
+++ b/camera/camera-view/build.gradle
@@ -36,7 +36,7 @@
     implementation("androidx.camera:camera-lifecycle:${VIEW_ATOMIC_GROUP_PINNED_VER}")
     implementation("androidx.annotation:annotation-experimental:1.1.0-rc01")
     implementation(GUAVA_LISTENABLE_FUTURE)
-    implementation("androidx.core:core:1.1.0")
+    implementation("androidx.core:core:1.3.2")
     implementation("androidx.concurrent:concurrent-futures:1.0.0")
     implementation(AUTO_VALUE_ANNOTATIONS)
     implementation("androidx.appcompat:appcompat:1.1.0")
diff --git a/camera/camera-view/src/main/java/androidx/camera/view/CameraView.java b/camera/camera-view/src/main/java/androidx/camera/view/CameraView.java
deleted file mode 100644
index b6106fc..0000000
--- a/camera/camera-view/src/main/java/androidx/camera/view/CameraView.java
+++ /dev/null
@@ -1,873 +0,0 @@
-/*
- * Copyright (C) 2019 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.view;
-
-import android.Manifest.permission;
-import android.annotation.SuppressLint;
-import android.content.Context;
-import android.content.res.TypedArray;
-import android.hardware.display.DisplayManager;
-import android.hardware.display.DisplayManager.DisplayListener;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.ParcelFileDescriptor;
-import android.os.Parcelable;
-import android.text.TextUtils;
-import android.util.AttributeSet;
-import android.view.Display;
-import android.view.MotionEvent;
-import android.view.ScaleGestureDetector;
-import android.view.Surface;
-import android.view.View;
-import android.view.ViewConfiguration;
-import android.view.ViewGroup;
-import android.widget.FrameLayout;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.annotation.RequiresApi;
-import androidx.annotation.RequiresPermission;
-import androidx.annotation.RestrictTo;
-import androidx.annotation.RestrictTo.Scope;
-import androidx.camera.core.Camera;
-import androidx.camera.core.CameraSelector;
-import androidx.camera.core.FocusMeteringAction;
-import androidx.camera.core.FocusMeteringResult;
-import androidx.camera.core.ImageCapture;
-import androidx.camera.core.ImageCapture.OnImageCapturedCallback;
-import androidx.camera.core.ImageCapture.OnImageSavedCallback;
-import androidx.camera.core.ImageProxy;
-import androidx.camera.core.Logger;
-import androidx.camera.core.MeteringPoint;
-import androidx.camera.core.MeteringPointFactory;
-import androidx.camera.core.VideoCapture;
-import androidx.camera.core.impl.LensFacingConverter;
-import androidx.camera.core.impl.utils.executor.CameraXExecutors;
-import androidx.camera.core.impl.utils.futures.FutureCallback;
-import androidx.camera.core.impl.utils.futures.Futures;
-import androidx.camera.view.video.ExperimentalVideo;
-import androidx.camera.view.video.OnVideoSavedCallback;
-import androidx.camera.view.video.OutputFileOptions;
-import androidx.camera.view.video.OutputFileResults;
-import androidx.lifecycle.LifecycleOwner;
-import androidx.lifecycle.LiveData;
-
-import com.google.common.util.concurrent.ListenableFuture;
-
-import java.io.File;
-import java.util.concurrent.Executor;
-
-/**
- * A {@link View} that displays a preview of the camera with methods {@link
- * #takePicture(Executor, OnImageCapturedCallback)},
- * {@link #takePicture(ImageCapture.OutputFileOptions, Executor, OnImageSavedCallback)},
- * {@link #startRecording(File, Executor, OnVideoSavedCallback callback)}
- * and {@link #stopRecording()}.
- *
- * <p>Because the Camera is a limited resource and consumes a high amount of power, CameraView must
- * be opened/closed. CameraView will handle opening/closing automatically through use of a {@link
- * LifecycleOwner}. Use {@link #bindToLifecycle(LifecycleOwner)} to start the camera.
- *
- * @deprecated Use {@link LifecycleCameraController}. See
- * <a href="https://medium.com/androiddevelopers/camerax-learn-how-to-use-cameracontroller
- * -e3ed10fffecf">migration guide</a>.
- */
-@Deprecated
-public final class CameraView extends FrameLayout {
-    static final String TAG = CameraView.class.getSimpleName();
-
-    static final int INDEFINITE_VIDEO_DURATION = -1;
-    static final int INDEFINITE_VIDEO_SIZE = -1;
-
-    private static final String EXTRA_SUPER = "super";
-    private static final String EXTRA_ZOOM_RATIO = "zoom_ratio";
-    private static final String EXTRA_PINCH_TO_ZOOM_ENABLED = "pinch_to_zoom_enabled";
-    private static final String EXTRA_FLASH = "flash";
-    private static final String EXTRA_MAX_VIDEO_DURATION = "max_video_duration";
-    private static final String EXTRA_MAX_VIDEO_SIZE = "max_video_size";
-    private static final String EXTRA_SCALE_TYPE = "scale_type";
-    private static final String EXTRA_CAMERA_DIRECTION = "camera_direction";
-    private static final String EXTRA_CAPTURE_MODE = "captureMode";
-
-    private static final int LENS_FACING_NONE = 0;
-    private static final int LENS_FACING_FRONT = 1;
-    private static final int LENS_FACING_BACK = 2;
-    private static final int FLASH_MODE_AUTO = 1;
-    private static final int FLASH_MODE_ON = 2;
-    private static final int FLASH_MODE_OFF = 4;
-    // For tap-to-focus
-    private long mDownEventTimestamp;
-    // For pinch-to-zoom
-    private PinchToZoomGestureDetector mPinchToZoomGestureDetector;
-    private boolean mIsPinchToZoomEnabled = true;
-    CameraXModule mCameraModule;
-    private final DisplayManager.DisplayListener mDisplayListener =
-            new DisplayListener() {
-                @Override
-                public void onDisplayAdded(int displayId) {
-                }
-
-                @Override
-                public void onDisplayRemoved(int displayId) {
-                }
-
-                @Override
-                public void onDisplayChanged(int displayId) {
-                    mCameraModule.invalidateView();
-                }
-            };
-    private PreviewView mPreviewView;
-    // For accessibility event
-    private MotionEvent mUpEvent;
-
-    public CameraView(@NonNull Context context) {
-        this(context, null);
-    }
-
-    public CameraView(@NonNull Context context, @Nullable AttributeSet attrs) {
-        this(context, attrs, 0);
-    }
-
-    public CameraView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyle) {
-        super(context, attrs, defStyle);
-        init(context, attrs);
-    }
-
-    @RequiresApi(21)
-    public CameraView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr,
-            int defStyleRes) {
-        super(context, attrs, defStyleAttr, defStyleRes);
-        init(context, attrs);
-    }
-
-    /**
-     * Binds control of the camera used by this view to the given lifecycle.
-     *
-     * <p>This links opening/closing the camera to the given lifecycle. The camera will not operate
-     * unless this method is called with a valid {@link LifecycleOwner} that is not in the {@link
-     * androidx.lifecycle.Lifecycle.State#DESTROYED} state. Call this method only once camera
-     * permissions have been obtained.
-     *
-     * <p>Once the provided lifecycle has transitioned to a {@link
-     * androidx.lifecycle.Lifecycle.State#DESTROYED} state, CameraView must be bound to a new
-     * lifecycle through this method in order to operate the camera.
-     *
-     * @param lifecycleOwner The lifecycle that will control this view's camera
-     * @throws IllegalArgumentException if provided lifecycle is in a {@link
-     *                                  androidx.lifecycle.Lifecycle.State#DESTROYED} state.
-     * @throws IllegalStateException    if camera permissions are not granted.
-     */
-    @RequiresPermission(permission.CAMERA)
-    public void bindToLifecycle(@NonNull LifecycleOwner lifecycleOwner) {
-        mCameraModule.bindToLifecycle(lifecycleOwner);
-    }
-
-    private void init(Context context, @Nullable AttributeSet attrs) {
-        addView(mPreviewView = new PreviewView(getContext()), 0 /* view position */);
-        mCameraModule = new CameraXModule(this);
-
-        if (attrs != null) {
-            TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CameraView);
-            setScaleType(
-                    PreviewView.ScaleType.fromId(
-                            a.getInteger(R.styleable.CameraView_scaleType,
-                                    getScaleType().getId())));
-            setPinchToZoomEnabled(
-                    a.getBoolean(
-                            R.styleable.CameraView_pinchToZoomEnabled, isPinchToZoomEnabled()));
-            setCaptureMode(
-                    CaptureMode.fromId(
-                            a.getInteger(R.styleable.CameraView_captureMode,
-                                    getCaptureMode().getId())));
-
-            int lensFacing = a.getInt(R.styleable.CameraView_lensFacing, LENS_FACING_BACK);
-            switch (lensFacing) {
-                case LENS_FACING_NONE:
-                    setCameraLensFacing(null);
-                    break;
-                case LENS_FACING_FRONT:
-                    setCameraLensFacing(CameraSelector.LENS_FACING_FRONT);
-                    break;
-                case LENS_FACING_BACK:
-                    setCameraLensFacing(CameraSelector.LENS_FACING_BACK);
-                    break;
-                default:
-                    // Unhandled event.
-            }
-
-            int flashMode = a.getInt(R.styleable.CameraView_flash, 0);
-            switch (flashMode) {
-                case FLASH_MODE_AUTO:
-                    setFlash(ImageCapture.FLASH_MODE_AUTO);
-                    break;
-                case FLASH_MODE_ON:
-                    setFlash(ImageCapture.FLASH_MODE_ON);
-                    break;
-                case FLASH_MODE_OFF:
-                    setFlash(ImageCapture.FLASH_MODE_OFF);
-                    break;
-                default:
-                    // Unhandled event.
-            }
-
-            a.recycle();
-        }
-
-        if (getBackground() == null) {
-            setBackgroundColor(0xFF111111);
-        }
-
-        mPinchToZoomGestureDetector = new PinchToZoomGestureDetector(context);
-    }
-
-    @Override
-    @NonNull
-    protected LayoutParams generateDefaultLayoutParams() {
-        return new LayoutParams(
-                ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
-    }
-
-    @Override
-    @NonNull
-    protected Parcelable onSaveInstanceState() {
-        // TODO(b/113884082): Decide what belongs here or what should be invalidated on
-        // configuration
-        // change
-        Bundle state = new Bundle();
-        state.putParcelable(EXTRA_SUPER, super.onSaveInstanceState());
-        state.putInt(EXTRA_SCALE_TYPE, getScaleType().getId());
-        state.putFloat(EXTRA_ZOOM_RATIO, getZoomRatio());
-        state.putBoolean(EXTRA_PINCH_TO_ZOOM_ENABLED, isPinchToZoomEnabled());
-        state.putString(EXTRA_FLASH, FlashModeConverter.nameOf(getFlash()));
-        state.putLong(EXTRA_MAX_VIDEO_DURATION, getMaxVideoDuration());
-        state.putLong(EXTRA_MAX_VIDEO_SIZE, getMaxVideoSize());
-        if (getCameraLensFacing() != null) {
-            state.putString(EXTRA_CAMERA_DIRECTION,
-                    LensFacingConverter.nameOf(getCameraLensFacing()));
-        }
-        state.putInt(EXTRA_CAPTURE_MODE, getCaptureMode().getId());
-        return state;
-    }
-
-    @Override
-    protected void onRestoreInstanceState(@Nullable Parcelable savedState) {
-        // TODO(b/113884082): Decide what belongs here or what should be invalidated on
-        // configuration
-        // change
-        if (savedState instanceof Bundle) {
-            Bundle state = (Bundle) savedState;
-            super.onRestoreInstanceState(state.getParcelable(EXTRA_SUPER));
-            setScaleType(PreviewView.ScaleType.fromId(state.getInt(EXTRA_SCALE_TYPE)));
-            setZoomRatio(state.getFloat(EXTRA_ZOOM_RATIO));
-            setPinchToZoomEnabled(state.getBoolean(EXTRA_PINCH_TO_ZOOM_ENABLED));
-            setFlash(FlashModeConverter.valueOf(state.getString(EXTRA_FLASH)));
-            setMaxVideoDuration(state.getLong(EXTRA_MAX_VIDEO_DURATION));
-            setMaxVideoSize(state.getLong(EXTRA_MAX_VIDEO_SIZE));
-            String lensFacingString = state.getString(EXTRA_CAMERA_DIRECTION);
-            setCameraLensFacing(
-                    TextUtils.isEmpty(lensFacingString)
-                            ? null
-                            : LensFacingConverter.valueOf(lensFacingString));
-            setCaptureMode(CaptureMode.fromId(state.getInt(EXTRA_CAPTURE_MODE)));
-        } else {
-            super.onRestoreInstanceState(savedState);
-        }
-    }
-
-    @Override
-    protected void onAttachedToWindow() {
-        super.onAttachedToWindow();
-        DisplayManager dpyMgr =
-                (DisplayManager) getContext().getSystemService(Context.DISPLAY_SERVICE);
-        dpyMgr.registerDisplayListener(mDisplayListener, new Handler(Looper.getMainLooper()));
-    }
-
-    @Override
-    protected void onDetachedFromWindow() {
-        super.onDetachedFromWindow();
-        DisplayManager dpyMgr =
-                (DisplayManager) getContext().getSystemService(Context.DISPLAY_SERVICE);
-        dpyMgr.unregisterDisplayListener(mDisplayListener);
-    }
-
-    /**
-     * Gets the {@link LiveData} of the underlying {@link PreviewView}'s
-     * {@link PreviewView.StreamState}.
-     *
-     * @return A {@link LiveData} containing the {@link PreviewView.StreamState}. Apps can either
-     * get current value by {@link LiveData#getValue()} or register a observer by
-     * {@link LiveData#observe}.
-     * @see PreviewView#getPreviewStreamState()
-     */
-    @NonNull
-    public LiveData<PreviewView.StreamState> getPreviewStreamState() {
-        return mPreviewView.getPreviewStreamState();
-    }
-
-    @NonNull
-    PreviewView getPreviewView() {
-        return mPreviewView;
-    }
-
-    // TODO(b/124269166): Rethink how we can handle permissions here.
-    @SuppressLint("MissingPermission")
-    @Override
-    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
-        // Since bindToLifecycle will depend on the measured dimension, only call it when measured
-        // dimension is not 0x0
-        if (getMeasuredWidth() > 0 && getMeasuredHeight() > 0) {
-            mCameraModule.bindToLifecycleAfterViewMeasured();
-        }
-
-        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
-    }
-
-    // TODO(b/124269166): Rethink how we can handle permissions here.
-    @SuppressLint("MissingPermission")
-    @Override
-    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
-        // In case that the CameraView size is always set as 0x0, we still need to trigger to force
-        // binding to lifecycle
-        mCameraModule.bindToLifecycleAfterViewMeasured();
-
-        mCameraModule.invalidateView();
-        super.onLayout(changed, left, top, right, bottom);
-    }
-
-    /**
-     * @return One of {@link Surface#ROTATION_0}, {@link Surface#ROTATION_90}, {@link
-     * Surface#ROTATION_180}, {@link Surface#ROTATION_270}.
-     */
-    int getDisplaySurfaceRotation() {
-        Display display = getDisplay();
-
-        // Null when the View is detached. If we were in the middle of a background operation,
-        // better to not NPE. When the background operation finishes, it'll realize that the camera
-        // was closed.
-        if (display == null) {
-            return 0;
-        }
-
-        return display.getRotation();
-    }
-
-    /**
-     * Returns the scale type used to scale the preview.
-     *
-     * @return The current {@link PreviewView.ScaleType}.
-     */
-    @NonNull
-    public PreviewView.ScaleType getScaleType() {
-        return mPreviewView.getScaleType();
-    }
-
-    /**
-     * Sets the view finder scale type.
-     *
-     * <p>This controls how the view finder should be scaled and positioned within the view.
-     *
-     * @param scaleType The desired {@link PreviewView.ScaleType}.
-     */
-    public void setScaleType(@NonNull PreviewView.ScaleType scaleType) {
-        mPreviewView.setScaleType(scaleType);
-    }
-
-    /**
-     * Returns the scale type used to scale the preview.
-     *
-     * @return The current {@link CaptureMode}.
-     */
-    @NonNull
-    public CaptureMode getCaptureMode() {
-        return mCameraModule.getCaptureMode();
-    }
-
-    /**
-     * Sets the CameraView capture mode
-     *
-     * <p>This controls only image or video capture function is enabled or both are enabled.
-     *
-     * @param captureMode The desired {@link CaptureMode}.
-     */
-    public void setCaptureMode(@NonNull CaptureMode captureMode) {
-        mCameraModule.setCaptureMode(captureMode);
-    }
-
-    /**
-     * Returns the maximum duration of videos, or {@link #INDEFINITE_VIDEO_DURATION} if there is no
-     * timeout.
-     *
-     * @hide Not currently implemented.
-     */
-    @RestrictTo(Scope.LIBRARY_GROUP)
-    public long getMaxVideoDuration() {
-        return mCameraModule.getMaxVideoDuration();
-    }
-
-    /**
-     * Sets the maximum video duration before
-     * {@link OnVideoSavedCallback#onVideoSaved(OutputFileResults)} is called
-     * automatically.
-     * Use {@link #INDEFINITE_VIDEO_DURATION} to disable the timeout.
-     */
-    private void setMaxVideoDuration(long duration) {
-        mCameraModule.setMaxVideoDuration(duration);
-    }
-
-    /**
-     * Returns the maximum size of videos in bytes, or {@link #INDEFINITE_VIDEO_SIZE} if there is no
-     * timeout.
-     */
-    private long getMaxVideoSize() {
-        return mCameraModule.getMaxVideoSize();
-    }
-
-    /**
-     * Sets the maximum video size in bytes before
-     * {@link OnVideoSavedCallback#onVideoSaved(OutputFileResults)}
-     * is called automatically. Use {@link #INDEFINITE_VIDEO_SIZE} to disable the size restriction.
-     */
-    private void setMaxVideoSize(long size) {
-        mCameraModule.setMaxVideoSize(size);
-    }
-
-    /**
-     * Takes a picture, and calls {@link OnImageCapturedCallback#onCaptureSuccess(ImageProxy)}
-     * once when done.
-     *
-     * @param executor The executor in which the callback methods will be run.
-     * @param callback Callback which will receive success or failure callbacks.
-     */
-    public void takePicture(@NonNull Executor executor, @NonNull OnImageCapturedCallback callback) {
-        mCameraModule.takePicture(executor, callback);
-    }
-
-    /**
-     * Takes a picture and calls
-     * {@link OnImageSavedCallback#onImageSaved(ImageCapture.OutputFileResults)} when done.
-     *
-     * <p> The value of {@link ImageCapture.Metadata#isReversedHorizontal()} in the
-     * {@link ImageCapture.OutputFileOptions} will be overwritten based on camera direction. For
-     * front camera, it will be set to true; for back camera, it will be set to false.
-     *
-     * @param outputFileOptions Options to store the newly captured image.
-     * @param executor          The executor in which the callback methods will be run.
-     * @param callback          Callback which will receive success or failure.
-     */
-    public void takePicture(@NonNull ImageCapture.OutputFileOptions outputFileOptions,
-            @NonNull Executor executor,
-            @NonNull OnImageSavedCallback callback) {
-        mCameraModule.takePicture(outputFileOptions, executor, callback);
-    }
-
-    /**
-     * Takes a video and calls the OnVideoSavedCallback when done.
-     *
-     * @param file     The destination.
-     * @param executor The executor in which the callback methods will be run.
-     * @param callback Callback which will receive success or failure.
-     */
-    @ExperimentalVideo
-    public void startRecording(@NonNull File file, @NonNull Executor executor,
-            @NonNull OnVideoSavedCallback callback) {
-        OutputFileOptions options = OutputFileOptions.builder(file).build();
-        startRecording(options, executor, callback);
-    }
-
-    /**
-     * Takes a video and calls the OnVideoSavedCallback when done.
-     *
-     * @param fd       The destination {@link ParcelFileDescriptor}.
-     * @param executor The executor in which the callback methods will be run.
-     * @param callback Callback which will receive success or failure.
-     */
-    @ExperimentalVideo
-    public void startRecording(@NonNull ParcelFileDescriptor fd, @NonNull Executor executor,
-            @NonNull OnVideoSavedCallback callback) {
-        OutputFileOptions options = OutputFileOptions.builder(fd).build();
-        startRecording(options, executor, callback);
-    }
-
-    /**
-     * Takes a video and calls the OnVideoSavedCallback when done.
-     *
-     * @param outputFileOptions Options to store the newly captured video.
-     * @param executor          The executor in which the callback methods will be run.
-     * @param callback          Callback which will receive success or failure.
-     */
-    @ExperimentalVideo
-    public void startRecording(@NonNull OutputFileOptions outputFileOptions,
-            @NonNull Executor executor,
-            @NonNull OnVideoSavedCallback callback) {
-        VideoCapture.OnVideoSavedCallback callbackWrapper =
-                new VideoCapture.OnVideoSavedCallback() {
-                    @Override
-                    public void onVideoSaved(
-                            @NonNull VideoCapture.OutputFileResults outputFileResults) {
-                        callback.onVideoSaved(
-                                OutputFileResults.create(outputFileResults.getSavedUri()));
-                    }
-
-                    @Override
-                    public void onError(int videoCaptureError, @NonNull String message,
-                            @Nullable Throwable cause) {
-                        callback.onError(videoCaptureError, message, cause);
-                    }
-                };
-
-        mCameraModule.startRecording(outputFileOptions.toVideoCaptureOutputFileOptions(), executor,
-                callbackWrapper);
-    }
-
-    /** Stops an in progress video. */
-    @ExperimentalVideo
-    public void stopRecording() {
-        mCameraModule.stopRecording();
-    }
-
-    /** @return True if currently recording. */
-    @ExperimentalVideo
-    public boolean isRecording() {
-        return mCameraModule.isRecording();
-    }
-
-    /**
-     * Queries whether the current device has a camera with the specified direction.
-     *
-     * @return True if the device supports the direction.
-     * @throws IllegalStateException if the CAMERA permission is not currently granted.
-     */
-    @RequiresPermission(permission.CAMERA)
-    public boolean hasCameraWithLensFacing(@CameraSelector.LensFacing int lensFacing) {
-        return mCameraModule.hasCameraWithLensFacing(lensFacing);
-    }
-
-    /**
-     * Toggles between the primary front facing camera and the primary back facing camera.
-     *
-     * <p>This will have no effect if not already bound to a lifecycle via {@link
-     * #bindToLifecycle(LifecycleOwner)}.
-     */
-    public void toggleCamera() {
-        mCameraModule.toggleCamera();
-    }
-
-    /**
-     * Sets the desired camera by specifying desired lensFacing.
-     *
-     * <p>This will choose the primary camera with the specified camera lensFacing.
-     *
-     * <p>If called before {@link #bindToLifecycle(LifecycleOwner)}, this will set the camera to be
-     * used when first bound to the lifecycle. If the specified lensFacing is not supported by the
-     * device, as determined by {@link #hasCameraWithLensFacing(int)}, the first supported
-     * lensFacing will be chosen when {@link #bindToLifecycle(LifecycleOwner)} is called.
-     *
-     * <p>If called with {@code null} AFTER binding to the lifecycle, the behavior would be
-     * equivalent to unbind the use cases without the lifecycle having to be destroyed.
-     *
-     * @param lensFacing The desired camera lensFacing.
-     */
-    public void setCameraLensFacing(@Nullable Integer lensFacing) {
-        mCameraModule.setCameraLensFacing(lensFacing);
-    }
-
-    /** Returns the currently selected lensFacing. */
-    @Nullable
-    public Integer getCameraLensFacing() {
-        return mCameraModule.getLensFacing();
-    }
-
-    /** Gets the active flash strategy. */
-    @ImageCapture.FlashMode
-    public int getFlash() {
-        return mCameraModule.getFlash();
-    }
-
-    /** Sets the active flash strategy. */
-    public void setFlash(@ImageCapture.FlashMode int flashMode) {
-        mCameraModule.setFlash(flashMode);
-    }
-
-    private long delta() {
-        return System.currentTimeMillis() - mDownEventTimestamp;
-    }
-
-    @Override
-    public boolean onTouchEvent(@NonNull MotionEvent event) {
-        // Disable pinch-to-zoom and tap-to-focus while the camera module is paused.
-        if (mCameraModule.isPaused()) {
-            return false;
-        }
-        // Only forward the event to the pinch-to-zoom gesture detector when pinch-to-zoom is
-        // enabled.
-        if (isPinchToZoomEnabled()) {
-            mPinchToZoomGestureDetector.onTouchEvent(event);
-        }
-        if (event.getPointerCount() == 2 && isPinchToZoomEnabled() && isZoomSupported()) {
-            return true;
-        }
-
-        // Camera focus
-        switch (event.getAction()) {
-            case MotionEvent.ACTION_DOWN:
-                mDownEventTimestamp = System.currentTimeMillis();
-                break;
-            case MotionEvent.ACTION_UP:
-                if (delta() < ViewConfiguration.getLongPressTimeout()
-                        && mCameraModule.isBoundToLifecycle()) {
-                    mUpEvent = event;
-                    performClick();
-                }
-                break;
-            default:
-                // Unhandled event.
-                return false;
-        }
-        return true;
-    }
-
-    /**
-     * Focus the position of the touch event, or focus the center of the preview for
-     * accessibility events
-     */
-    @Override
-    public boolean performClick() {
-        super.performClick();
-
-        final float x = (mUpEvent != null) ? mUpEvent.getX() : getX() + getWidth() / 2f;
-        final float y = (mUpEvent != null) ? mUpEvent.getY() : getY() + getHeight() / 2f;
-        mUpEvent = null;
-
-        Camera camera = mCameraModule.getCamera();
-        if (camera != null) {
-            MeteringPointFactory pointFactory = mPreviewView.getMeteringPointFactory();
-            float afPointWidth = 1.0f / 6.0f;  // 1/6 total area
-            float aePointWidth = afPointWidth * 1.5f;
-            MeteringPoint afPoint = pointFactory.createPoint(x, y, afPointWidth);
-            MeteringPoint aePoint = pointFactory.createPoint(x, y, aePointWidth);
-
-            ListenableFuture<FocusMeteringResult> future =
-                    camera.getCameraControl().startFocusAndMetering(
-                            new FocusMeteringAction.Builder(afPoint,
-                                    FocusMeteringAction.FLAG_AF).addPoint(aePoint,
-                                    FocusMeteringAction.FLAG_AE).build());
-            Futures.addCallback(future, new FutureCallback<FocusMeteringResult>() {
-                @Override
-                public void onSuccess(@Nullable FocusMeteringResult result) {
-                }
-
-                @Override
-                public void onFailure(Throwable t) {
-                    // Throw the unexpected error.
-                    throw new RuntimeException(t);
-                }
-            }, CameraXExecutors.directExecutor());
-
-        } else {
-            Logger.d(TAG, "cannot access camera");
-        }
-
-        return true;
-    }
-
-    float rangeLimit(float val, float max, float min) {
-        return Math.min(Math.max(val, min), max);
-    }
-
-    /**
-     * Returns whether the view allows pinch-to-zoom.
-     *
-     * @return True if pinch to zoom is enabled.
-     */
-    public boolean isPinchToZoomEnabled() {
-        return mIsPinchToZoomEnabled;
-    }
-
-    /**
-     * Sets whether the view should allow pinch-to-zoom.
-     *
-     * <p>When enabled, the user can pinch the camera to zoom in/out. This only has an effect if the
-     * bound camera supports zoom.
-     *
-     * @param enabled True to enable pinch-to-zoom.
-     */
-    public void setPinchToZoomEnabled(boolean enabled) {
-        mIsPinchToZoomEnabled = enabled;
-    }
-
-    /**
-     * Returns the current zoom ratio.
-     *
-     * @return The current zoom ratio.
-     */
-    public float getZoomRatio() {
-        return mCameraModule.getZoomRatio();
-    }
-
-    /**
-     * Sets the current zoom ratio.
-     *
-     * <p>Valid zoom values range from {@link #getMinZoomRatio()} to {@link #getMaxZoomRatio()}.
-     *
-     * @param zoomRatio The requested zoom ratio.
-     */
-    public void setZoomRatio(float zoomRatio) {
-        mCameraModule.setZoomRatio(zoomRatio);
-    }
-
-    /**
-     * Returns the minimum zoom ratio.
-     *
-     * <p>For most cameras this should return a zoom ratio of 1. A zoom ratio of 1 corresponds to a
-     * non-zoomed image.
-     *
-     * @return The minimum zoom ratio.
-     */
-    public float getMinZoomRatio() {
-        return mCameraModule.getMinZoomRatio();
-    }
-
-    /**
-     * Returns the maximum zoom ratio.
-     *
-     * <p>The zoom ratio corresponds to the ratio between both the widths and heights of a
-     * non-zoomed image and a maximally zoomed image for the selected camera.
-     *
-     * @return The maximum zoom ratio.
-     */
-    public float getMaxZoomRatio() {
-        return mCameraModule.getMaxZoomRatio();
-    }
-
-    /**
-     * Returns whether the bound camera supports zooming.
-     *
-     * @return True if the camera supports zooming.
-     */
-    public boolean isZoomSupported() {
-        return mCameraModule.isZoomSupported();
-    }
-
-    /**
-     * Turns on/off torch.
-     *
-     * @param torch True to turn on torch, false to turn off torch.
-     */
-    public void enableTorch(boolean torch) {
-        mCameraModule.enableTorch(torch);
-    }
-
-    /**
-     * Returns current torch status.
-     *
-     * @return true if torch is on , otherwise false
-     */
-    public boolean isTorchOn() {
-        return mCameraModule.isTorchOn();
-    }
-
-    /**
-     * The capture mode used by CameraView.
-     *
-     * <p>This enum can be used to determine which capture mode will be enabled for {@link
-     * CameraView}.
-     */
-    public enum CaptureMode {
-        /** A mode where image capture is enabled. */
-        IMAGE(0),
-        /** A mode where video capture is enabled. */
-        @ExperimentalVideo
-        VIDEO(1),
-        /**
-         * A mode where both image capture and video capture are simultaneously enabled. Note that
-         * this mode may not be available on every device.
-         */
-        @ExperimentalVideo
-        MIXED(2);
-
-        private final int mId;
-
-        int getId() {
-            return mId;
-        }
-
-        CaptureMode(int id) {
-            mId = id;
-        }
-
-        static CaptureMode fromId(int id) {
-            for (CaptureMode f : values()) {
-                if (f.mId == id) {
-                    return f;
-                }
-            }
-            throw new IllegalArgumentException();
-        }
-    }
-
-    static class S extends ScaleGestureDetector.SimpleOnScaleGestureListener {
-        private ScaleGestureDetector.OnScaleGestureListener mListener;
-
-        void setRealGestureDetector(ScaleGestureDetector.OnScaleGestureListener l) {
-            mListener = l;
-        }
-
-        @Override
-        public boolean onScale(ScaleGestureDetector detector) {
-            return mListener.onScale(detector);
-        }
-    }
-
-    private class PinchToZoomGestureDetector extends ScaleGestureDetector
-            implements ScaleGestureDetector.OnScaleGestureListener {
-        PinchToZoomGestureDetector(Context context) {
-            this(context, new S());
-        }
-
-        PinchToZoomGestureDetector(Context context, S s) {
-            super(context, s);
-            s.setRealGestureDetector(this);
-        }
-
-        @Override
-        public boolean onScale(ScaleGestureDetector detector) {
-            float scale = detector.getScaleFactor();
-
-            // Speeding up the zoom by 2X.
-            if (scale > 1f) {
-                scale = 1.0f + (scale - 1.0f) * 2;
-            } else {
-                scale = 1.0f - (1.0f - scale) * 2;
-            }
-
-            float newRatio = getZoomRatio() * scale;
-            newRatio = rangeLimit(newRatio, getMaxZoomRatio(), getMinZoomRatio());
-            setZoomRatio(newRatio);
-            return true;
-        }
-
-        @Override
-        public boolean onScaleBegin(ScaleGestureDetector detector) {
-            return true;
-        }
-
-        @Override
-        public void onScaleEnd(ScaleGestureDetector detector) {
-        }
-    }
-}
diff --git a/camera/camera-view/src/main/java/androidx/camera/view/CameraXModule.java b/camera/camera-view/src/main/java/androidx/camera/view/CameraXModule.java
deleted file mode 100644
index c1ad936..0000000
--- a/camera/camera-view/src/main/java/androidx/camera/view/CameraXModule.java
+++ /dev/null
@@ -1,659 +0,0 @@
-/*
- * Copyright (C) 2019 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.view;
-
-import static androidx.camera.core.ImageCapture.FLASH_MODE_OFF;
-
-import android.Manifest.permission;
-import android.annotation.SuppressLint;
-import android.content.Context;
-import android.util.Rational;
-import android.util.Size;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.annotation.OptIn;
-import androidx.annotation.RequiresPermission;
-import androidx.camera.core.AspectRatio;
-import androidx.camera.core.Camera;
-import androidx.camera.core.CameraInfoUnavailableException;
-import androidx.camera.core.CameraSelector;
-import androidx.camera.core.ImageCapture;
-import androidx.camera.core.ImageCapture.OnImageCapturedCallback;
-import androidx.camera.core.ImageCapture.OnImageSavedCallback;
-import androidx.camera.core.Logger;
-import androidx.camera.core.Preview;
-import androidx.camera.core.TorchState;
-import androidx.camera.core.UseCase;
-import androidx.camera.core.VideoCapture;
-import androidx.camera.core.VideoCapture.OnVideoSavedCallback;
-import androidx.camera.core.impl.LensFacingConverter;
-import androidx.camera.core.impl.utils.CameraOrientationUtil;
-import androidx.camera.core.impl.utils.executor.CameraXExecutors;
-import androidx.camera.core.impl.utils.futures.FutureCallback;
-import androidx.camera.core.impl.utils.futures.Futures;
-import androidx.camera.lifecycle.ProcessCameraProvider;
-import androidx.camera.view.video.ExperimentalVideo;
-import androidx.core.util.Preconditions;
-import androidx.lifecycle.Lifecycle;
-import androidx.lifecycle.LifecycleObserver;
-import androidx.lifecycle.LifecycleOwner;
-import androidx.lifecycle.OnLifecycleEvent;
-
-import com.google.common.util.concurrent.ListenableFuture;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.LinkedHashSet;
-import java.util.List;
-import java.util.Objects;
-import java.util.Set;
-import java.util.concurrent.Executor;
-import java.util.concurrent.atomic.AtomicBoolean;
-
-/**
- * CameraX use case operation built on @{link androidx.camera.core}.
- *
- * @deprecated Use {@link LifecycleCameraController}. See
- * <a href="https://medium.com/androiddevelopers/camerax-learn-how-to-use-cameracontroller
- * -e3ed10fffecf">migration guide</a>.
- */
-@Deprecated
-final class CameraXModule {
-    public static final String TAG = "CameraXModule";
-
-    private static final float UNITY_ZOOM_SCALE = 1f;
-    private static final float ZOOM_NOT_SUPPORTED = UNITY_ZOOM_SCALE;
-    private static final Rational ASPECT_RATIO_16_9 = new Rational(16, 9);
-    private static final Rational ASPECT_RATIO_4_3 = new Rational(4, 3);
-    private static final Rational ASPECT_RATIO_9_16 = new Rational(9, 16);
-    private static final Rational ASPECT_RATIO_3_4 = new Rational(3, 4);
-
-    private final Preview.Builder mPreviewBuilder;
-    private final VideoCapture.Builder mVideoCaptureBuilder;
-    private final ImageCapture.Builder mImageCaptureBuilder;
-    private final CameraView mCameraView;
-    final AtomicBoolean mVideoIsRecording = new AtomicBoolean(false);
-    private CameraView.CaptureMode mCaptureMode = CameraView.CaptureMode.IMAGE;
-    private long mMaxVideoDuration = CameraView.INDEFINITE_VIDEO_DURATION;
-    private long mMaxVideoSize = CameraView.INDEFINITE_VIDEO_SIZE;
-    @ImageCapture.FlashMode
-    private int mFlash = FLASH_MODE_OFF;
-    @Nullable
-    @SuppressWarnings("WeakerAccess") /* synthetic accessor */
-    Camera mCamera;
-    @Nullable
-    private ImageCapture mImageCapture;
-    @Nullable
-    private VideoCapture mVideoCapture;
-    @SuppressWarnings("WeakerAccess") /* synthetic accessor */
-    @Nullable
-    Preview mPreview;
-    @SuppressWarnings("WeakerAccess") /* synthetic accessor */
-    @Nullable
-    LifecycleOwner mCurrentLifecycle;
-    private final LifecycleObserver mCurrentLifecycleObserver =
-            new LifecycleObserver() {
-                @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
-                public void onDestroy(LifecycleOwner owner) {
-                    if (owner == mCurrentLifecycle) {
-                        clearCurrentLifecycle();
-                    }
-                }
-            };
-    @Nullable
-    private LifecycleOwner mNewLifecycle;
-    @SuppressWarnings("WeakerAccess") /* synthetic accessor */
-    @Nullable
-    Integer mCameraLensFacing = CameraSelector.LENS_FACING_BACK;
-    @SuppressWarnings("WeakerAccess") /* synthetic accessor */
-    @Nullable
-    ProcessCameraProvider mCameraProvider;
-
-    CameraXModule(CameraView view) {
-        mCameraView = view;
-
-        Futures.addCallback(ProcessCameraProvider.getInstance(view.getContext()),
-                new FutureCallback<ProcessCameraProvider>() {
-                    // TODO(b/124269166): Rethink how we can handle permissions here.
-                    @SuppressLint("MissingPermission")
-                    @Override
-                    public void onSuccess(@Nullable ProcessCameraProvider provider) {
-                        Preconditions.checkNotNull(provider);
-                        mCameraProvider = provider;
-                        if (mCurrentLifecycle != null) {
-                            bindToLifecycle(mCurrentLifecycle);
-                        }
-                    }
-
-                    @Override
-                    public void onFailure(Throwable t) {
-                        throw new RuntimeException("CameraX failed to initialize.", t);
-                    }
-                }, CameraXExecutors.mainThreadExecutor());
-
-        mPreviewBuilder = new Preview.Builder().setTargetName("Preview");
-
-        mImageCaptureBuilder = new ImageCapture.Builder().setTargetName("ImageCapture");
-
-        mVideoCaptureBuilder = new VideoCapture.Builder().setTargetName("VideoCapture");
-    }
-
-    @RequiresPermission(permission.CAMERA)
-    void bindToLifecycle(LifecycleOwner lifecycleOwner) {
-        mNewLifecycle = lifecycleOwner;
-
-        if (getMeasuredWidth() > 0 && getMeasuredHeight() > 0) {
-            bindToLifecycleAfterViewMeasured();
-        }
-    }
-
-    @OptIn(markerClass = ExperimentalVideo.class)
-    @RequiresPermission(permission.CAMERA)
-    void bindToLifecycleAfterViewMeasured() {
-        if (mNewLifecycle == null) {
-            return;
-        }
-
-        clearCurrentLifecycle();
-        if (mNewLifecycle.getLifecycle().getCurrentState() == Lifecycle.State.DESTROYED) {
-            // Lifecycle is already in a destroyed state. Since it may have been a valid
-            // lifecycle when bound, but became destroyed while waiting for layout, treat this as
-            // a no-op now that we have cleared the previous lifecycle.
-            mNewLifecycle = null;
-            return;
-        }
-        mCurrentLifecycle = mNewLifecycle;
-        mNewLifecycle = null;
-
-        if (mCameraProvider == null) {
-            // try again once the camera provider is no longer null
-            return;
-        }
-
-        Set<Integer> available = getAvailableCameraLensFacing();
-
-        if (available.isEmpty()) {
-            Logger.w(TAG, "Unable to bindToLifeCycle since no cameras available");
-            mCameraLensFacing = null;
-        }
-
-        // Ensure the current camera exists, or default to another camera
-        if (mCameraLensFacing != null && !available.contains(mCameraLensFacing)) {
-            Logger.w(TAG, "Camera does not exist with direction " + mCameraLensFacing);
-
-            // Default to the first available camera direction
-            mCameraLensFacing = available.iterator().next();
-
-            Logger.w(TAG, "Defaulting to primary camera with direction " + mCameraLensFacing);
-        }
-
-        // Do not attempt to create use cases for a null cameraLensFacing. This could occur if
-        // the user explicitly sets the LensFacing to null, or if we determined there
-        // were no available cameras, which should be logged in the logic above.
-        if (mCameraLensFacing == null) {
-            return;
-        }
-
-        // Set the preferred aspect ratio as 4:3 if it is IMAGE only mode. Set the preferred aspect
-        // ratio as 16:9 if it is VIDEO or MIXED mode. Then, it will be WYSIWYG when the view finder
-        // is in CENTER_INSIDE mode.
-
-        boolean isDisplayPortrait = getDisplayRotationDegrees() == 0
-                || getDisplayRotationDegrees() == 180;
-
-        Rational targetAspectRatio;
-        if (getCaptureMode() == CameraView.CaptureMode.IMAGE) {
-            targetAspectRatio = isDisplayPortrait ? ASPECT_RATIO_3_4 : ASPECT_RATIO_4_3;
-        } else {
-            mImageCaptureBuilder.setTargetAspectRatio(AspectRatio.RATIO_16_9);
-            mVideoCaptureBuilder.setTargetAspectRatio(AspectRatio.RATIO_16_9);
-            targetAspectRatio = isDisplayPortrait ? ASPECT_RATIO_9_16 : ASPECT_RATIO_16_9;
-        }
-
-        mImageCaptureBuilder.setTargetRotation(getDisplaySurfaceRotation());
-        mImageCapture = mImageCaptureBuilder.build();
-
-        mVideoCaptureBuilder.setTargetRotation(getDisplaySurfaceRotation());
-        mVideoCapture = mVideoCaptureBuilder.build();
-
-        // Adjusts the preview resolution according to the view size and the target aspect ratio.
-        int height = (int) (getMeasuredWidth() / targetAspectRatio.floatValue());
-        mPreviewBuilder.setTargetResolution(new Size(getMeasuredWidth(), height));
-
-        mPreview = mPreviewBuilder.build();
-        mPreview.setSurfaceProvider(mCameraView.getPreviewView().getSurfaceProvider());
-
-        CameraSelector cameraSelector =
-                new CameraSelector.Builder().requireLensFacing(mCameraLensFacing).build();
-        if (getCaptureMode() == CameraView.CaptureMode.IMAGE) {
-            mCamera = mCameraProvider.bindToLifecycle(mCurrentLifecycle, cameraSelector,
-                    mImageCapture,
-                    mPreview);
-        } else if (getCaptureMode() == CameraView.CaptureMode.VIDEO) {
-            mCamera = mCameraProvider.bindToLifecycle(mCurrentLifecycle, cameraSelector,
-                    mVideoCapture,
-                    mPreview);
-        } else {
-            mCamera = mCameraProvider.bindToLifecycle(mCurrentLifecycle, cameraSelector,
-                    mImageCapture,
-                    mVideoCapture, mPreview);
-        }
-
-        setZoomRatio(UNITY_ZOOM_SCALE);
-        mCurrentLifecycle.getLifecycle().addObserver(mCurrentLifecycleObserver);
-        // Enable flash setting in ImageCapture after use cases are created and binded.
-        setFlash(getFlash());
-    }
-
-    public void open() {
-        throw new UnsupportedOperationException(
-                "Explicit open/close of camera not yet supported. Use bindtoLifecycle() instead.");
-    }
-
-    public void close() {
-        throw new UnsupportedOperationException(
-                "Explicit open/close of camera not yet supported. Use bindtoLifecycle() instead.");
-    }
-
-    @OptIn(markerClass = ExperimentalVideo.class)
-    public void takePicture(Executor executor, OnImageCapturedCallback callback) {
-        if (mImageCapture == null) {
-            return;
-        }
-
-        if (getCaptureMode() == CameraView.CaptureMode.VIDEO) {
-            throw new IllegalStateException("Can not take picture under VIDEO capture mode.");
-        }
-
-        if (callback == null) {
-            throw new IllegalArgumentException("OnImageCapturedCallback should not be empty");
-        }
-
-        mImageCapture.takePicture(executor, callback);
-    }
-
-    @OptIn(markerClass = ExperimentalVideo.class)
-    public void takePicture(@NonNull ImageCapture.OutputFileOptions outputFileOptions,
-            @NonNull Executor executor, OnImageSavedCallback callback) {
-        if (mImageCapture == null) {
-            return;
-        }
-
-        if (getCaptureMode() == CameraView.CaptureMode.VIDEO) {
-            throw new IllegalStateException("Can not take picture under VIDEO capture mode.");
-        }
-
-        if (callback == null) {
-            throw new IllegalArgumentException("OnImageSavedCallback should not be empty");
-        }
-
-        outputFileOptions.getMetadata().setReversedHorizontal(mCameraLensFacing != null
-                && mCameraLensFacing == CameraSelector.LENS_FACING_FRONT);
-        mImageCapture.takePicture(outputFileOptions, executor, callback);
-    }
-
-    public void startRecording(VideoCapture.OutputFileOptions outputFileOptions,
-            Executor executor, final OnVideoSavedCallback callback) {
-        if (mVideoCapture == null) {
-            return;
-        }
-
-        if (getCaptureMode() == CameraView.CaptureMode.IMAGE) {
-            throw new IllegalStateException("Can not record video under IMAGE capture mode.");
-        }
-
-        if (callback == null) {
-            throw new IllegalArgumentException("OnVideoSavedCallback should not be empty");
-        }
-
-        mVideoIsRecording.set(true);
-        mVideoCapture.startRecording(
-                outputFileOptions,
-                executor,
-                new VideoCapture.OnVideoSavedCallback() {
-                    @Override
-                    public void onVideoSaved(
-                            @NonNull VideoCapture.OutputFileResults outputFileResults) {
-                        mVideoIsRecording.set(false);
-                        callback.onVideoSaved(outputFileResults);
-                    }
-
-                    @Override
-                    public void onError(
-                            @VideoCapture.VideoCaptureError int videoCaptureError,
-                            @NonNull String message,
-                            @Nullable Throwable cause) {
-                        mVideoIsRecording.set(false);
-                        Logger.e(TAG, message, cause);
-                        callback.onError(videoCaptureError, message, cause);
-                    }
-                });
-    }
-
-    public void stopRecording() {
-        if (mVideoCapture == null) {
-            return;
-        }
-
-        mVideoCapture.stopRecording();
-    }
-
-    public boolean isRecording() {
-        return mVideoIsRecording.get();
-    }
-
-    // TODO(b/124269166): Rethink how we can handle permissions here.
-    @SuppressLint("MissingPermission")
-    public void setCameraLensFacing(@Nullable Integer lensFacing) {
-        // Setting same lens facing is a no-op, so check for that first
-        if (!Objects.equals(mCameraLensFacing, lensFacing)) {
-            // If we're not bound to a lifecycle, just update the camera that will be opened when we
-            // attach to a lifecycle.
-            mCameraLensFacing = lensFacing;
-
-            if (mCurrentLifecycle != null) {
-                // Re-bind to lifecycle with new camera
-                bindToLifecycle(mCurrentLifecycle);
-            }
-        }
-    }
-
-    @RequiresPermission(permission.CAMERA)
-    public boolean hasCameraWithLensFacing(@CameraSelector.LensFacing int lensFacing) {
-        if (mCameraProvider == null) {
-            return false;
-        }
-        try {
-            return mCameraProvider.hasCamera(
-                    new CameraSelector.Builder().requireLensFacing(lensFacing).build());
-        } catch (CameraInfoUnavailableException e) {
-            return false;
-        }
-    }
-
-    @Nullable
-    public Integer getLensFacing() {
-        return mCameraLensFacing;
-    }
-
-    public void toggleCamera() {
-        // TODO(b/124269166): Rethink how we can handle permissions here.
-        @SuppressLint("MissingPermission")
-        Set<Integer> availableCameraLensFacing = getAvailableCameraLensFacing();
-
-        if (availableCameraLensFacing.isEmpty()) {
-            return;
-        }
-
-        if (mCameraLensFacing == null) {
-            setCameraLensFacing(availableCameraLensFacing.iterator().next());
-            return;
-        }
-
-        if (mCameraLensFacing == CameraSelector.LENS_FACING_BACK
-                && availableCameraLensFacing.contains(CameraSelector.LENS_FACING_FRONT)) {
-            setCameraLensFacing(CameraSelector.LENS_FACING_FRONT);
-            return;
-        }
-
-        if (mCameraLensFacing == CameraSelector.LENS_FACING_FRONT
-                && availableCameraLensFacing.contains(CameraSelector.LENS_FACING_BACK)) {
-            setCameraLensFacing(CameraSelector.LENS_FACING_BACK);
-            return;
-        }
-    }
-
-    public float getZoomRatio() {
-        if (mCamera != null) {
-            return mCamera.getCameraInfo().getZoomState().getValue().getZoomRatio();
-        } else {
-            return UNITY_ZOOM_SCALE;
-        }
-    }
-
-    public void setZoomRatio(float zoomRatio) {
-        if (mCamera != null) {
-            ListenableFuture<Void> future = mCamera.getCameraControl().setZoomRatio(
-                    zoomRatio);
-            Futures.addCallback(future, new FutureCallback<Void>() {
-                @Override
-                public void onSuccess(@Nullable Void result) {
-                }
-
-                @Override
-                public void onFailure(Throwable t) {
-                    // Throw the unexpected error.
-                    throw new RuntimeException(t);
-                }
-            }, CameraXExecutors.directExecutor());
-        } else {
-            Logger.e(TAG, "Failed to set zoom ratio");
-        }
-    }
-
-    public float getMinZoomRatio() {
-        if (mCamera != null) {
-            return mCamera.getCameraInfo().getZoomState().getValue().getMinZoomRatio();
-        } else {
-            return UNITY_ZOOM_SCALE;
-        }
-    }
-
-    public float getMaxZoomRatio() {
-        if (mCamera != null) {
-            return mCamera.getCameraInfo().getZoomState().getValue().getMaxZoomRatio();
-        } else {
-            return ZOOM_NOT_SUPPORTED;
-        }
-    }
-
-    public boolean isZoomSupported() {
-        return getMaxZoomRatio() != ZOOM_NOT_SUPPORTED;
-    }
-
-    // TODO(b/124269166): Rethink how we can handle permissions here.
-    @SuppressLint("MissingPermission")
-    private void rebindToLifecycle() {
-        if (mCurrentLifecycle != null) {
-            bindToLifecycle(mCurrentLifecycle);
-        }
-    }
-
-    boolean isBoundToLifecycle() {
-        return mCamera != null;
-    }
-
-    int getRelativeCameraOrientation(boolean compensateForMirroring) {
-        int rotationDegrees = 0;
-        if (mCamera != null) {
-            rotationDegrees =
-                    mCamera.getCameraInfo().getSensorRotationDegrees(getDisplaySurfaceRotation());
-            if (compensateForMirroring) {
-                rotationDegrees = (360 - rotationDegrees) % 360;
-            }
-        }
-
-        return rotationDegrees;
-    }
-
-    public void invalidateView() {
-        updateViewInfo();
-    }
-
-    void clearCurrentLifecycle() {
-        if (mCurrentLifecycle != null && mCameraProvider != null) {
-            // Remove previous use cases
-            List<UseCase> toUnbind = new ArrayList<>();
-            if (mImageCapture != null && mCameraProvider.isBound(mImageCapture)) {
-                toUnbind.add(mImageCapture);
-            }
-            if (mVideoCapture != null && mCameraProvider.isBound(mVideoCapture)) {
-                toUnbind.add(mVideoCapture);
-            }
-            if (mPreview != null && mCameraProvider.isBound(mPreview)) {
-                toUnbind.add(mPreview);
-            }
-
-            if (!toUnbind.isEmpty()) {
-                mCameraProvider.unbind(toUnbind.toArray((new UseCase[0])));
-            }
-
-            // Remove surface provider once unbound.
-            if (mPreview != null) {
-                mPreview.setSurfaceProvider(null);
-            }
-        }
-        mCamera = null;
-        mCurrentLifecycle = null;
-    }
-
-    // Update view related information used in use cases
-    private void updateViewInfo() {
-        if (mImageCapture != null) {
-            mImageCapture.setCropAspectRatio(new Rational(getWidth(), getHeight()));
-            mImageCapture.setTargetRotation(getDisplaySurfaceRotation());
-        }
-
-        if (mVideoCapture != null) {
-            mVideoCapture.setTargetRotation(getDisplaySurfaceRotation());
-        }
-    }
-
-    @RequiresPermission(permission.CAMERA)
-    private Set<Integer> getAvailableCameraLensFacing() {
-        // Start with all camera directions
-        Set<Integer> available = new LinkedHashSet<>(Arrays.asList(LensFacingConverter.values()));
-
-        // If we're bound to a lifecycle, remove unavailable cameras
-        if (mCurrentLifecycle != null) {
-            if (!hasCameraWithLensFacing(CameraSelector.LENS_FACING_BACK)) {
-                available.remove(CameraSelector.LENS_FACING_BACK);
-            }
-
-            if (!hasCameraWithLensFacing(CameraSelector.LENS_FACING_FRONT)) {
-                available.remove(CameraSelector.LENS_FACING_FRONT);
-            }
-        }
-
-        return available;
-    }
-
-    @ImageCapture.FlashMode
-    public int getFlash() {
-        return mFlash;
-    }
-
-    public void setFlash(@ImageCapture.FlashMode int flash) {
-        this.mFlash = flash;
-
-        if (mImageCapture == null) {
-            // Do nothing if there is no imageCapture
-            return;
-        }
-
-        mImageCapture.setFlashMode(flash);
-    }
-
-    public void enableTorch(boolean torch) {
-        if (mCamera == null) {
-            return;
-        }
-        ListenableFuture<Void> future = mCamera.getCameraControl().enableTorch(torch);
-        Futures.addCallback(future, new FutureCallback<Void>() {
-            @Override
-            public void onSuccess(@Nullable Void result) {
-            }
-
-            @Override
-            public void onFailure(Throwable t) {
-                // Throw the unexpected error.
-                throw new RuntimeException(t);
-            }
-        }, CameraXExecutors.directExecutor());
-    }
-
-    public boolean isTorchOn() {
-        if (mCamera == null) {
-            return false;
-        }
-        return mCamera.getCameraInfo().getTorchState().getValue() == TorchState.ON;
-    }
-
-    public Context getContext() {
-        return mCameraView.getContext();
-    }
-
-    public int getWidth() {
-        return mCameraView.getWidth();
-    }
-
-    public int getHeight() {
-        return mCameraView.getHeight();
-    }
-
-    public int getDisplayRotationDegrees() {
-        return CameraOrientationUtil.surfaceRotationToDegrees(getDisplaySurfaceRotation());
-    }
-
-    protected int getDisplaySurfaceRotation() {
-        return mCameraView.getDisplaySurfaceRotation();
-    }
-
-    private int getMeasuredWidth() {
-        return mCameraView.getMeasuredWidth();
-    }
-
-    private int getMeasuredHeight() {
-        return mCameraView.getMeasuredHeight();
-    }
-
-    @Nullable
-    public Camera getCamera() {
-        return mCamera;
-    }
-
-    @NonNull
-    public CameraView.CaptureMode getCaptureMode() {
-        return mCaptureMode;
-    }
-
-    public void setCaptureMode(@NonNull CameraView.CaptureMode captureMode) {
-        this.mCaptureMode = captureMode;
-        rebindToLifecycle();
-    }
-
-    public long getMaxVideoDuration() {
-        return mMaxVideoDuration;
-    }
-
-    public void setMaxVideoDuration(long duration) {
-        mMaxVideoDuration = duration;
-    }
-
-    public long getMaxVideoSize() {
-        return mMaxVideoSize;
-    }
-
-    public void setMaxVideoSize(long size) {
-        mMaxVideoSize = size;
-    }
-
-    public boolean isPaused() {
-        return false;
-    }
-}
diff --git a/camera/camera-view/src/main/java/androidx/camera/view/PreviewView.java b/camera/camera-view/src/main/java/androidx/camera/view/PreviewView.java
index 9459be0..89775f6 100644
--- a/camera/camera-view/src/main/java/androidx/camera/view/PreviewView.java
+++ b/camera/camera-view/src/main/java/androidx/camera/view/PreviewView.java
@@ -69,6 +69,7 @@
 import androidx.camera.view.transform.CoordinateTransform;
 import androidx.camera.view.transform.OutputTransform;
 import androidx.core.content.ContextCompat;
+import androidx.core.view.ViewCompat;
 import androidx.lifecycle.LiveData;
 import androidx.lifecycle.MutableLiveData;
 
@@ -156,6 +157,7 @@
     @SuppressWarnings("WeakerAccess")
     final Preview.SurfaceProvider mSurfaceProvider = new Preview.SurfaceProvider() {
 
+        @SuppressLint("NewApi")
         @OptIn(markerClass = ExperimentalUseCaseGroup.class)
         @Override
         @AnyThread
@@ -230,10 +232,8 @@
         Threads.checkMainThread();
         final TypedArray attributes = context.getTheme().obtainStyledAttributes(attrs,
                 R.styleable.PreviewView, defStyleAttr, defStyleRes);
-        if (Build.VERSION.SDK_INT >= 29) {
-            saveAttributeDataForStyleable(context, R.styleable.PreviewView, attrs, attributes,
-                    defStyleAttr, defStyleRes);
-        }
+        ViewCompat.saveAttributeDataForStyleable(this, context, R.styleable.PreviewView, attrs,
+                attributes, defStyleAttr, defStyleRes);
 
         try {
             final int scaleTypeId = attributes.getInteger(
diff --git a/camera/camera-view/src/main/java/androidx/camera/view/SurfaceViewImplementation.java b/camera/camera-view/src/main/java/androidx/camera/view/SurfaceViewImplementation.java
index 4110046..0d9a473 100644
--- a/camera/camera-view/src/main/java/androidx/camera/view/SurfaceViewImplementation.java
+++ b/camera/camera-view/src/main/java/androidx/camera/view/SurfaceViewImplementation.java
@@ -16,7 +16,6 @@
 
 package androidx.camera.view;
 
-import android.annotation.TargetApi;
 import android.graphics.Bitmap;
 import android.util.Size;
 import android.view.PixelCopy;
@@ -28,6 +27,7 @@
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
+import androidx.annotation.RequiresApi;
 import androidx.annotation.UiThread;
 import androidx.camera.core.Logger;
 import androidx.camera.core.SurfaceRequest;
@@ -114,7 +114,7 @@
      * would introduced in API level 24. PreviewView doesn't currently use a SurfaceView on API
      * levels below 24.
      */
-    @TargetApi(24)
+    @RequiresApi(24)
     @Nullable
     @Override
     Bitmap getPreviewBitmap() {
diff --git a/camera/integration-tests/extensionstestapp/src/main/java/androidx/camera/integration/extensions/CameraExtensionsActivity.java b/camera/integration-tests/extensionstestapp/src/main/java/androidx/camera/integration/extensions/CameraExtensionsActivity.java
index a121fac..d84d77d 100644
--- a/camera/integration-tests/extensionstestapp/src/main/java/androidx/camera/integration/extensions/CameraExtensionsActivity.java
+++ b/camera/integration-tests/extensionstestapp/src/main/java/androidx/camera/integration/extensions/CameraExtensionsActivity.java
@@ -184,15 +184,19 @@
         // Check that extension can be enabled and if so enable it
         @Extensions.ExtensionMode
         int extensionMode = extensionModeFrom(imageCaptureType);
-        boolean extensionAvailable = mExtensions.isExtensionAvailable(mCamera, extensionMode);
-        if (extensionAvailable) {
-            Log.d(TAG, "Enabling extension mode: " + imageCaptureType.name());
-            mExtensions.setExtension(mCamera, extensionMode);
-        } else {
-            Log.d(TAG, "Unable to enable extension mode, skipping: " + imageCaptureType.name());
+
+        if (!mExtensions.isExtensionAvailable(mCameraProvider, mCurrentCameraSelector,
+                extensionMode)) {
             return false;
         }
 
+        CameraSelector cameraSelector = mExtensions.getExtensionCameraSelector(
+                mCurrentCameraSelector, extensionMode);
+
+        mCameraProvider.unbindAll();
+
+        mCameraProvider.bindToLifecycle(this, cameraSelector, mImageCapture, mPreview);
+
         // Update the UI and save location for ImageCapture
         Button toggleButton = findViewById(R.id.PhotoToggle);
         toggleButton.setText(mCurrentImageCaptureType.toString());
diff --git a/camera/integration-tests/extensionstestlib/src/main/java/androidx/camera/extensions/impl/AutoImageCaptureExtenderImpl.java b/camera/integration-tests/extensionstestlib/src/main/java/androidx/camera/extensions/impl/AutoImageCaptureExtenderImpl.java
index 7c9077f..6154482 100755
--- a/camera/integration-tests/extensionstestlib/src/main/java/androidx/camera/extensions/impl/AutoImageCaptureExtenderImpl.java
+++ b/camera/integration-tests/extensionstestlib/src/main/java/androidx/camera/extensions/impl/AutoImageCaptureExtenderImpl.java
@@ -24,6 +24,7 @@
 import android.os.Build;
 import android.util.Log;
 import android.util.Pair;
+import android.util.Range;
 import android.util.Size;
 import android.view.Surface;
 
@@ -194,4 +195,10 @@
     public List<Pair<Integer, Size[]>> getSupportedResolutions() {
         return null;
     }
+
+    @Nullable
+    @Override
+    public Range<Long> getEstimatedCaptureLatencyRange(@Nullable Size captureOutputSize) {
+        return new Range<>(300L, 1000L);
+    }
 }
diff --git a/camera/integration-tests/extensionstestlib/src/main/java/androidx/camera/extensions/impl/BeautyImageCaptureExtenderImpl.java b/camera/integration-tests/extensionstestlib/src/main/java/androidx/camera/extensions/impl/BeautyImageCaptureExtenderImpl.java
index e23aeb9..8380a7f 100755
--- a/camera/integration-tests/extensionstestlib/src/main/java/androidx/camera/extensions/impl/BeautyImageCaptureExtenderImpl.java
+++ b/camera/integration-tests/extensionstestlib/src/main/java/androidx/camera/extensions/impl/BeautyImageCaptureExtenderImpl.java
@@ -26,6 +26,7 @@
 import android.os.Build;
 import android.util.Log;
 import android.util.Pair;
+import android.util.Range;
 import android.util.Size;
 import android.view.Surface;
 
@@ -220,4 +221,10 @@
 
         return formatResolutionsPairList;
     }
+
+    @Nullable
+    @Override
+    public Range<Long> getEstimatedCaptureLatencyRange(@Nullable Size captureOutputSize) {
+        return new Range<>(300L, 1000L);
+    }
 }
diff --git a/camera/integration-tests/extensionstestlib/src/main/java/androidx/camera/extensions/impl/BokehImageCaptureExtenderImpl.java b/camera/integration-tests/extensionstestlib/src/main/java/androidx/camera/extensions/impl/BokehImageCaptureExtenderImpl.java
index eff6232..f0f7c59 100644
--- a/camera/integration-tests/extensionstestlib/src/main/java/androidx/camera/extensions/impl/BokehImageCaptureExtenderImpl.java
+++ b/camera/integration-tests/extensionstestlib/src/main/java/androidx/camera/extensions/impl/BokehImageCaptureExtenderImpl.java
@@ -24,6 +24,7 @@
 import android.os.Build;
 import android.util.Log;
 import android.util.Pair;
+import android.util.Range;
 import android.util.Size;
 import android.view.Surface;
 
@@ -196,4 +197,9 @@
         return null;
     }
 
+    @Nullable
+    @Override
+    public Range<Long> getEstimatedCaptureLatencyRange(@Nullable Size captureOutputSize) {
+        return new Range<>(300L, 1000L);
+    }
 }
diff --git a/camera/integration-tests/extensionstestlib/src/main/java/androidx/camera/extensions/impl/HdrImageCaptureExtenderImpl.java b/camera/integration-tests/extensionstestlib/src/main/java/androidx/camera/extensions/impl/HdrImageCaptureExtenderImpl.java
index 2ac4548..d2ea323 100644
--- a/camera/integration-tests/extensionstestlib/src/main/java/androidx/camera/extensions/impl/HdrImageCaptureExtenderImpl.java
+++ b/camera/integration-tests/extensionstestlib/src/main/java/androidx/camera/extensions/impl/HdrImageCaptureExtenderImpl.java
@@ -24,6 +24,7 @@
 import android.os.Build;
 import android.util.Log;
 import android.util.Pair;
+import android.util.Range;
 import android.util.Size;
 import android.view.Surface;
 
@@ -261,4 +262,9 @@
         return null;
     }
 
+    @Nullable
+    @Override
+    public Range<Long> getEstimatedCaptureLatencyRange(@Nullable Size captureOutputSize) {
+        return new Range<>(300L, 1000L);
+    }
 }
diff --git a/camera/integration-tests/extensionstestlib/src/main/java/androidx/camera/extensions/impl/ImageCaptureExtenderImpl.java b/camera/integration-tests/extensionstestlib/src/main/java/androidx/camera/extensions/impl/ImageCaptureExtenderImpl.java
index a488f14..884c0dd 100644
--- a/camera/integration-tests/extensionstestlib/src/main/java/androidx/camera/extensions/impl/ImageCaptureExtenderImpl.java
+++ b/camera/integration-tests/extensionstestlib/src/main/java/androidx/camera/extensions/impl/ImageCaptureExtenderImpl.java
@@ -19,6 +19,7 @@
 import android.graphics.ImageFormat;
 import android.hardware.camera2.CameraCharacteristics;
 import android.util.Pair;
+import android.util.Range;
 import android.util.Size;
 
 import androidx.annotation.Nullable;
@@ -80,5 +81,22 @@
      */
     @Nullable
     List<Pair<Integer, Size[]>> getSupportedResolutions();
+
+    /**
+     * Returns the estimated capture latency range in milliseconds for the target capture
+     * resolution.
+     *
+     * <p> This includes the time spent processing the multi-frame capture request along with any
+     * additional time for encoding of the processed buffer in the framework if necessary.
+     *
+     * @param captureOutputSize size of the capture output surface. If it is null or not in the
+     *                          supported output sizes, maximum capture output size is used for
+     *                          the estimation.
+     * @return the range of estimated minimal and maximal capture latency in milliseconds.
+     * Returns null if no capture latency info can be provided.
+     * @since 1.2
+     */
+    @Nullable
+    Range<Long> getEstimatedCaptureLatencyRange(@Nullable Size captureOutputSize);
 }
 
diff --git a/camera/integration-tests/extensionstestlib/src/main/java/androidx/camera/extensions/impl/NightImageCaptureExtenderImpl.java b/camera/integration-tests/extensionstestlib/src/main/java/androidx/camera/extensions/impl/NightImageCaptureExtenderImpl.java
index 1434871..405da8d 100755
--- a/camera/integration-tests/extensionstestlib/src/main/java/androidx/camera/extensions/impl/NightImageCaptureExtenderImpl.java
+++ b/camera/integration-tests/extensionstestlib/src/main/java/androidx/camera/extensions/impl/NightImageCaptureExtenderImpl.java
@@ -24,6 +24,7 @@
 import android.os.Build;
 import android.util.Log;
 import android.util.Pair;
+import android.util.Range;
 import android.util.Size;
 import android.view.Surface;
 
@@ -194,4 +195,10 @@
     public List<Pair<Integer, Size[]>> getSupportedResolutions() {
         return null;
     }
+
+    @Nullable
+    @Override
+    public Range<Long> getEstimatedCaptureLatencyRange(@Nullable Size captureOutputSize) {
+        return new Range<>(300L, 1000L);
+    }
 }
diff --git a/camera/integration-tests/uiwidgetstestapp/build.gradle b/camera/integration-tests/uiwidgetstestapp/build.gradle
index 7d8e90f..e52e977 100644
--- a/camera/integration-tests/uiwidgetstestapp/build.gradle
+++ b/camera/integration-tests/uiwidgetstestapp/build.gradle
@@ -39,10 +39,6 @@
     }
 
     buildTypes {
-        debug {
-            testCoverageEnabled true
-        }
-
         release {
             minifyEnabled true
             shrinkResources true
diff --git a/camera/integration-tests/viewtestapp/src/androidTest/java/androidx/camera/integration/view/CameraViewFragmentTest.kt b/camera/integration-tests/viewtestapp/src/androidTest/java/androidx/camera/integration/view/CameraViewFragmentTest.kt
deleted file mode 100644
index 65a5116..0000000
--- a/camera/integration-tests/viewtestapp/src/androidTest/java/androidx/camera/integration/view/CameraViewFragmentTest.kt
+++ /dev/null
@@ -1,151 +0,0 @@
-/*
- * Copyright 2020 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.integration.view
-
-import androidx.camera.testing.CameraUtil
-import androidx.camera.testing.CoreAppTestUtil
-import androidx.camera.view.PreviewView
-import androidx.core.os.bundleOf
-import androidx.fragment.app.Fragment
-import androidx.fragment.app.testing.FragmentScenario
-import androidx.fragment.app.testing.launchFragmentInContainer
-import androidx.lifecycle.Lifecycle
-import androidx.lifecycle.asFlow
-import androidx.lifecycle.testing.TestLifecycleOwner
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.LargeTest
-import androidx.test.platform.app.InstrumentationRegistry
-import androidx.test.rule.GrantPermissionRule
-import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.flow.first
-import kotlinx.coroutines.runBlocking
-import org.junit.Assume
-import org.junit.Before
-import org.junit.Rule
-import org.junit.Test
-import org.junit.rules.TestRule
-import org.junit.runner.RunWith
-
-@LargeTest
-@RunWith(AndroidJUnit4::class)
-class CameraViewFragmentTest {
-
-    @get:Rule
-    val useCamera: TestRule = CameraUtil.grantCameraPermissionAndPreTest()
-
-    @get:Rule
-    val permissionRule: GrantPermissionRule =
-        GrantPermissionRule.grant(
-            android.Manifest.permission.WRITE_EXTERNAL_STORAGE,
-            android.Manifest.permission.RECORD_AUDIO
-        )
-
-    @Before
-    fun setup() {
-        Assume.assumeTrue(CameraUtil.deviceHasCamera())
-        CoreAppTestUtil.assumeCompatibleDevice()
-        // Clear the device UI and check if there is no dialog or lock screen on the top of the
-        // window before start the test.
-        CoreAppTestUtil.prepareDeviceUI(InstrumentationRegistry.getInstrumentation())
-    }
-
-    @Test
-    fun cameraView_canStream_defaultLifecycle() = runBlocking {
-        with(launchFragmentInContainer<CameraViewFragment>()) { assertStreaming() }
-    }
-
-    @Test
-    fun cameraView_canStream_withActivityLifecycle() = runBlocking {
-        with(
-            launchFragmentInContainer<CameraViewFragment>(
-                fragmentArgs = bundleOf(
-                    CameraViewFragment.ARG_LIFECYCLE_TYPE to
-                        CameraViewFragment.LIFECYCLE_TYPE_ACTIVITY
-                )
-            )
-        ) { assertStreaming() }
-    }
-
-    @Test
-    fun cameraView_canStream_withFragmentLifecycle() = runBlocking {
-        with(
-            launchFragmentInContainer<CameraViewFragment>(
-                fragmentArgs = bundleOf(
-                    CameraViewFragment.ARG_LIFECYCLE_TYPE to
-                        CameraViewFragment.LIFECYCLE_TYPE_FRAGMENT
-                )
-            )
-        ) { assertStreaming() }
-    }
-
-    @Test
-    fun cameraView_canStream_withFragmentViewLifecycle() = runBlocking {
-        with(
-            launchFragmentInContainer<CameraViewFragment>(
-                fragmentArgs = bundleOf(
-                    CameraViewFragment.ARG_LIFECYCLE_TYPE to
-                        CameraViewFragment.LIFECYCLE_TYPE_FRAGMENT_VIEW
-                )
-            )
-        ) { assertStreaming() }
-    }
-
-    @Test
-    fun cameraView_ignoresLifecycleInDestroyedState() {
-        // Since launchFragmentInContainer waits for onResume() to complete, CameraView should
-        // have measured its view and bound to the lifecycle by this time. This would crash with
-        // an IllegalArgumentException prior to applying fix for b/157949175
-        launchFragmentInContainer(
-            fragmentArgs = bundleOf(
-                CameraViewFragment.ARG_LIFECYCLE_TYPE to CameraViewFragment.LIFECYCLE_TYPE_DEBUG
-            ),
-            instantiate = {
-                CameraViewFragment().apply {
-                    setDebugLifecycleOwner(TestLifecycleOwner(Lifecycle.State.DESTROYED))
-                }
-            }
-        )
-    }
-}
-
-@Suppress("DEPRECATION")
-private suspend inline fun FragmentScenario<CameraViewFragment>.assertStreaming() {
-    val streamState = withFragment {
-        (view?.findViewById<androidx.camera.view.CameraView>(R.id.camera)?.previewStreamState)!!
-    }.asFlow().first {
-        it == PreviewView.StreamState.STREAMING
-    }
-
-    assertThat(streamState).isEqualTo(PreviewView.StreamState.STREAMING)
-}
-
-// Adapted from ActivityScenario.withActivity extension function
-private inline fun <reified F : Fragment, T : Any> FragmentScenario<F>.withFragment(
-    crossinline block: F.() -> T
-): T {
-    lateinit var value: T
-    var err: Throwable? = null
-    onFragment { fragment ->
-        try {
-            value = block(fragment)
-        } catch (t: Throwable) {
-            err = t
-        }
-    }
-    err?.let { throw it }
-    return value
-}
\ No newline at end of file
diff --git a/camera/integration-tests/viewtestapp/src/main/java/androidx/camera/integration/view/CameraViewFragment.java b/camera/integration-tests/viewtestapp/src/main/java/androidx/camera/integration/view/CameraViewFragment.java
deleted file mode 100644
index 6a5e574..0000000
--- a/camera/integration-tests/viewtestapp/src/main/java/androidx/camera/integration/view/CameraViewFragment.java
+++ /dev/null
@@ -1,328 +0,0 @@
-/*
- * Copyright (C) 2019 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.integration.view;
-
-import android.app.Activity;
-import android.content.pm.PackageManager;
-import android.graphics.Rect;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.Looper;
-import android.util.Log;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.ViewTreeObserver;
-import android.widget.CompoundButton;
-import android.widget.Toast;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.annotation.OptIn;
-import androidx.camera.core.CameraSelector;
-import androidx.camera.view.CameraView;
-import androidx.camera.view.CameraView.CaptureMode;
-import androidx.camera.view.PreviewView;
-import androidx.camera.view.video.ExperimentalVideo;
-import androidx.core.content.ContextCompat;
-import androidx.fragment.app.Fragment;
-import androidx.lifecycle.LifecycleOwner;
-
-import java.io.File;
-import java.io.IOException;
-import java.io.PrintStream;
-import java.util.Locale;
-import java.util.Objects;
-
-/** The main camera fragment. */
-public class CameraViewFragment extends Fragment {
-    private static final String TAG = "CameraViewFragment";
-
-    // Possible values for this intent key are the name values of LensFacing encoded as
-    // strings (case-insensitive): "back", "front".
-    private static final String INTENT_EXTRA_CAMERA_DIRECTION = "camera_direction";
-
-    // Possible values for this intent key are the name values of CameraView.CaptureMode encoded as
-    // strings (case-insensitive): "image", "video", "mixed"
-    private static final String INTENT_EXTRA_CAPTURE_MODE = "captureMode";
-
-    // The time-out to wait for the ready of CameraProver in the CameraXModule of CameraView.
-    private static final int CAMERA_PROVIDER_READY_TIMEOUT = 2000;
-
-    // Argument key which determines the lifecycle used to control the camera of CameraView.
-    // Possible values for this argument key are LIFECYCLE_TYPE_ACTIVITY, LIFECYCLE_TYPE_FRAGMENT,
-    // LIFECYCLE_TYPE_FRAGMENT_VIEW, LIFECYCLE_TYPE_CUSTOM. If using LIFECYCLE_TYPE_DEBUG, then
-    // a lifecycle must be provided via setDebugLifecycleOwner().
-    static final String ARG_LIFECYCLE_TYPE = "lifecycle_type";
-
-    // Fragment's Activity lifecycle
-    static final String LIFECYCLE_TYPE_ACTIVITY = "activity";
-    // Fragment lifecycle (this). This is the default lifecycle used by this fragment
-    static final String LIFECYCLE_TYPE_FRAGMENT = "fragment";
-    // Fragment's View lifecycle (getViewLifecycleOwner())
-    static final String LIFECYCLE_TYPE_FRAGMENT_VIEW = "fragment_view";
-    // Lifecycle provided by setDebugLifecycleOwner
-    static final String LIFECYCLE_TYPE_DEBUG = "debug";
-
-
-    private View mCameraHolder;
-    CameraView mCameraView;
-    private View mCaptureView;
-    private CompoundButton mModeButton;
-    @Nullable
-    private CompoundButton mToggleCameraButton;
-    private CompoundButton mToggleCropButton;
-    @Nullable
-    private LifecycleOwner mDebugLifecycleOwner;
-
-    /**
-     * Sets the debug lifecycle used by this fragment IF the fragment has argument
-     * {@link #ARG_LIFECYCLE_TYPE} set to {@link #LIFECYCLE_TYPE_DEBUG}.
-     *
-     * <p>This lifecycle must be set before the fragment lifecycle reaches
-     * {@link #onViewStateRestored(Bundle)}, or it will be ignored.
-     *
-     * <p>This value set here is not retained across fragment recreation, so it is only safe to
-     * use for debugging/testing purposes.
-     */
-    public void setDebugLifecycleOwner(@NonNull LifecycleOwner owner) {
-        mDebugLifecycleOwner = owner;
-    }
-
-    @Override
-    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
-        mCameraHolder = view.findViewById(R.id.layout_camera);
-        mCameraView = view.findViewById(R.id.camera);
-        mToggleCameraButton = view.findViewById(R.id.toggle);
-        mToggleCropButton = view.findViewById(R.id.toggle_crop);
-        mCaptureView = mCameraHolder.findViewById(R.id.capture);
-        if (mCameraHolder == null) {
-            throw new IllegalStateException("No View found with id R.id.layout_camera");
-        }
-        if (mCameraView == null) {
-            throw new IllegalStateException("No CameraView found with id R.id.camera");
-        }
-        if (mCaptureView == null) {
-            throw new IllegalStateException("No CameraView found with id R.id.capture");
-        }
-
-        mModeButton = mCameraHolder.findViewById(R.id.mode);
-
-        if (mModeButton == null) {
-            throw new IllegalStateException("No View found with id R.id.mode");
-        }
-
-        // Log the location of some views, so their locations can be used to perform some automated
-        // clicks in tests.
-        logCenterCoordinates(mCameraView, "camera_view");
-        logCenterCoordinates(mCaptureView, "capture");
-        logCenterCoordinates(mToggleCameraButton, "toggle_camera");
-        logCenterCoordinates(mToggleCropButton, "toggle_crop");
-        logCenterCoordinates(mModeButton, "mode");
-
-        // Get extra option for setting initial camera direction
-        Bundle bundle = getActivity().getIntent().getExtras();
-        if (bundle != null) {
-            final String cameraDirectionString = bundle.getString(INTENT_EXTRA_CAMERA_DIRECTION);
-            final boolean isCameraDirectionValid =
-                    cameraDirectionString != null && (cameraDirectionString.equalsIgnoreCase("BACK")
-                            || cameraDirectionString.equalsIgnoreCase("FRONT"));
-            if (isCameraDirectionValid) {
-                @CameraSelector.LensFacing int lensFacing = cameraDirectionString.equalsIgnoreCase(
-                        "BACK")
-                        ? CameraSelector.LENS_FACING_BACK : CameraSelector.LENS_FACING_FRONT;
-                mCameraView.setCameraLensFacing(lensFacing);
-            }
-
-            String captureModeString = bundle.getString(INTENT_EXTRA_CAPTURE_MODE);
-            if (captureModeString != null) {
-                CaptureMode captureMode = CaptureMode.valueOf(captureModeString.toUpperCase());
-                mCameraView.setCaptureMode(captureMode);
-            }
-        }
-    }
-
-    @Override
-    @OptIn(markerClass = ExperimentalVideo.class)
-    public void onViewStateRestored(@Nullable Bundle savedInstanceState) {
-        super.onViewStateRestored(savedInstanceState);
-
-        if (ContextCompat.checkSelfPermission(requireContext(), android.Manifest.permission.CAMERA)
-                != PackageManager.PERMISSION_GRANTED) {
-            throw new IllegalStateException("App has not been granted CAMERA permission");
-        }
-
-        // Set the lifecycle that will be used to control the camera
-        Bundle args = getArguments();
-        String lifecycleType = args == null ? LIFECYCLE_TYPE_FRAGMENT :
-                args.getString(ARG_LIFECYCLE_TYPE, LIFECYCLE_TYPE_FRAGMENT);
-        Log.d(TAG, "Attempting bindToLifecycle with lifecycle type: " + lifecycleType);
-        switch (lifecycleType) {
-            case LIFECYCLE_TYPE_ACTIVITY:
-                mCameraView.bindToLifecycle(requireActivity());
-                break;
-            case LIFECYCLE_TYPE_FRAGMENT:
-                mCameraView.bindToLifecycle(CameraViewFragment.this);
-                break;
-            case LIFECYCLE_TYPE_FRAGMENT_VIEW:
-                mCameraView.bindToLifecycle(Objects.requireNonNull(getViewLifecycleOwner()));
-                break;
-            case LIFECYCLE_TYPE_DEBUG:
-                if (mDebugLifecycleOwner == null) {
-                    throw new IllegalStateException("Lifecycle type set to DEBUG, but no debug "
-                            + "lifecycle exists. setDebugLifecycleOwner() must be called before "
-                            + "onViewStateRestored()");
-                }
-                mCameraView.bindToLifecycle(mDebugLifecycleOwner);
-                break;
-            default:
-                throw new IllegalArgumentException(String.format(Locale.US, "Invalid lifecycle "
-                                + "type: %s. Valid options are %s, %s, %s, and %s.", lifecycleType,
-                        LIFECYCLE_TYPE_ACTIVITY, LIFECYCLE_TYPE_FRAGMENT,
-                        LIFECYCLE_TYPE_FRAGMENT_VIEW, LIFECYCLE_TYPE_DEBUG));
-        }
-
-        mCameraView.setPinchToZoomEnabled(true);
-        mCaptureView.setOnTouchListener(new CaptureViewOnTouchListener(mCameraView));
-
-        // Set clickable, Let the cameraView can be interacted by Voice Access
-        mCameraView.setClickable(true);
-
-        // CameraView.hasCameraWithLensFacing need to wait for the ready of CameraProvider. Check
-        // the b/183916771 for the workaround.
-        Handler handler = new Handler(Looper.getMainLooper());
-        handler.postDelayed(() -> {
-            if (mToggleCameraButton != null) {
-                if (mToggleCameraButton != null) {
-                    mToggleCameraButton.setVisibility(
-                            (mCameraView.hasCameraWithLensFacing(CameraSelector.LENS_FACING_BACK)
-                                    && mCameraView.hasCameraWithLensFacing(
-                                    CameraSelector.LENS_FACING_FRONT))
-                                    ? View.VISIBLE
-                                    : View.INVISIBLE);
-                    mToggleCameraButton.setChecked(
-                            mCameraView.getCameraLensFacing() == CameraSelector.LENS_FACING_FRONT);
-                }
-            }
-        }, CAMERA_PROVIDER_READY_TIMEOUT);
-
-        // Set listeners here, or else restoring state will trigger them.
-        if (mToggleCameraButton != null) {
-            mToggleCameraButton.setOnCheckedChangeListener(
-                    new CompoundButton.OnCheckedChangeListener() {
-                        @Override
-                        public void onCheckedChanged(CompoundButton b, boolean checked) {
-                            mCameraView.setCameraLensFacing(
-                                    checked ? CameraSelector.LENS_FACING_FRONT
-                                            : CameraSelector.LENS_FACING_BACK);
-                        }
-                    });
-        }
-
-        mToggleCropButton.setChecked(
-                mCameraView.getScaleType() == PreviewView.ScaleType.FILL_CENTER);
-        mToggleCropButton.setOnCheckedChangeListener((b, checked) -> {
-            if (checked) {
-                mCameraView.setScaleType(PreviewView.ScaleType.FILL_CENTER);
-            } else {
-                mCameraView.setScaleType(PreviewView.ScaleType.FIT_CENTER);
-            }
-        });
-
-        if (mModeButton != null) {
-            updateModeButtonIcon();
-
-            mModeButton.setOnClickListener(
-                    new View.OnClickListener() {
-                        @Override
-                        public void onClick(View view) {
-                            if (mCameraView.isRecording()) {
-                                Toast.makeText(
-                                        CameraViewFragment.this.getContext(),
-                                        "Can not switch mode during video recording.",
-                                        Toast.LENGTH_SHORT)
-                                        .show();
-                                return;
-                            }
-
-                            if (mCameraView.getCaptureMode() == CaptureMode.MIXED) {
-                                mCameraView.setCaptureMode(CaptureMode.IMAGE);
-                            } else if (mCameraView.getCaptureMode() == CaptureMode.IMAGE) {
-                                mCameraView.setCaptureMode(CaptureMode.VIDEO);
-                            } else {
-                                mCameraView.setCaptureMode(CaptureMode.MIXED);
-                            }
-
-                            CameraViewFragment.this.updateModeButtonIcon();
-                        }
-                    });
-        }
-    }
-
-    @OptIn(markerClass = ExperimentalVideo.class)
-    void updateModeButtonIcon() {
-        if (mCameraView.getCaptureMode() == CaptureMode.MIXED) {
-            mModeButton.setButtonDrawable(R.drawable.ic_photo_camera);
-        } else if (mCameraView.getCaptureMode() == CaptureMode.IMAGE) {
-            mModeButton.setButtonDrawable(R.drawable.ic_camera);
-        } else {
-            mModeButton.setButtonDrawable(R.drawable.ic_videocam);
-        }
-    }
-
-    @Override
-    @NonNull
-    public View onCreateView(
-            @NonNull LayoutInflater inflater,
-            @Nullable ViewGroup container,
-            @Nullable Bundle savedInstanceState) {
-        return inflater.inflate(R.layout.fragment_main, container, false);
-    }
-
-    private void logCenterCoordinates(View view, String name) {
-        view.getViewTreeObserver()
-                .addOnGlobalLayoutListener(
-                        new ViewTreeObserver.OnGlobalLayoutListener() {
-                            @Override
-                            public void onGlobalLayout() {
-                                Activity activity = getActivity();
-                                if (activity == null) {
-                                    // The fragment has been detached from the parent Activity.
-                                    return;
-                                }
-                                Rect rect = new Rect();
-                                view.getGlobalVisibleRect(rect);
-                                Log.d(
-                                        TAG,
-                                        "View "
-                                                + name
-                                                + " Center "
-                                                + rect.centerX()
-                                                + " "
-                                                + rect.centerY());
-                                File externalDir = activity.getExternalFilesDir(null);
-                                File logFile =
-                                        new File(externalDir, name + "_button_coordinates.txt");
-                                try (PrintStream stream = new PrintStream(logFile)) {
-                                    stream.print(rect.centerX() + " " + rect.centerY());
-                                } catch (IOException e) {
-                                    Log.e(TAG, "Could not save to " + logFile, e);
-                                }
-                            }
-                        });
-    }
-}
diff --git a/camera/integration-tests/viewtestapp/src/main/java/androidx/camera/integration/view/CaptureViewOnTouchListener.java b/camera/integration-tests/viewtestapp/src/main/java/androidx/camera/integration/view/CaptureViewOnTouchListener.java
deleted file mode 100644
index 0f08848..0000000
--- a/camera/integration-tests/viewtestapp/src/main/java/androidx/camera/integration/view/CaptureViewOnTouchListener.java
+++ /dev/null
@@ -1,252 +0,0 @@
-/*
- * Copyright (C) 2019 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.integration.view;
-
-import android.content.ContentValues;
-import android.graphics.Rect;
-import android.os.Build;
-import android.os.Environment;
-import android.os.Handler;
-import android.os.Message;
-import android.provider.MediaStore;
-import android.util.Log;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.ViewConfiguration;
-import android.widget.Toast;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.annotation.OptIn;
-import androidx.camera.core.ImageCapture;
-import androidx.camera.core.ImageCapture.OnImageSavedCallback;
-import androidx.camera.core.ImageCaptureException;
-import androidx.camera.view.CameraView;
-import androidx.camera.view.CameraView.CaptureMode;
-import androidx.camera.view.video.ExperimentalVideo;
-import androidx.camera.view.video.OnVideoSavedCallback;
-import androidx.camera.view.video.OutputFileResults;
-import androidx.core.content.ContextCompat;
-
-import java.io.File;
-import java.text.SimpleDateFormat;
-import java.util.Locale;
-
-/**
- * A {@link View.OnTouchListener} which converts a view's touches into camera actions.
- *
- * <p>The listener converts touches on a {@link View}, such as a button, into appropriate photo
- * taking or video recording actions through a {@link CameraView}. A click is interpreted as a
- * take-photo signal, while a long-press is interpreted as a record-video signal.
- */
-class CaptureViewOnTouchListener implements View.OnTouchListener {
-    private static final String TAG = "ViewOnTouchListener";
-
-    private static final String FILENAME = "yyyy-MM-dd-HH-mm-ss-SSS";
-    private static final String PHOTO_EXTENSION = ".jpg";
-    private static final String VIDEO_EXTENSION = ".mp4";
-
-    private static final int TAP = 1;
-    private static final int HOLD = 2;
-    private static final int RELEASE = 3;
-
-    private final long mLongPress = ViewConfiguration.getLongPressTimeout();
-    private final CameraView mCameraView;
-
-    // TODO: Use a Handler for a background thread, rather than running on the current (main)
-    // thread.
-    private final Handler mHandler =
-            new Handler() {
-                @Override
-                public void handleMessage(Message msg) {
-                    switch (msg.what) {
-                        case TAP:
-                            onTap();
-                            break;
-                        case HOLD:
-                            onHold();
-                            if (mCameraView.getMaxVideoDuration() > 0) {
-                                sendEmptyMessageDelayed(RELEASE, mCameraView.getMaxVideoDuration());
-                            }
-                            break;
-                        case RELEASE:
-                            onRelease();
-                            break;
-                        default:
-                            // No op
-                    }
-                }
-            };
-
-    private long mDownEventTimestamp;
-    private Rect mViewBoundsRect;
-
-    /** Creates a new listener which links to the given {@link CameraView}. */
-    CaptureViewOnTouchListener(CameraView cameraView) {
-        mCameraView = cameraView;
-    }
-
-    /** Called when the user taps. */
-    @OptIn(markerClass = ExperimentalVideo.class)
-    private void onTap() {
-        if (mCameraView.getCaptureMode() == CaptureMode.IMAGE
-                || mCameraView.getCaptureMode() == CaptureMode.MIXED) {
-
-            File saveFile = createNewFile(PHOTO_EXTENSION);
-            ImageCapture.OutputFileOptions outputFileOptions;
-            ContentValues contentValues = new ContentValues();
-            contentValues.put(MediaStore.MediaColumns.DISPLAY_NAME, saveFile.getName());
-            contentValues.put(MediaStore.MediaColumns.MIME_TYPE, "image/jpeg");
-            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
-                contentValues.put(MediaStore.MediaColumns.RELATIVE_PATH,
-                        Environment.DIRECTORY_PICTURES);
-            } else {
-                contentValues.put(MediaStore.MediaColumns.DATA, saveFile.getAbsolutePath());
-            }
-            outputFileOptions = new ImageCapture.OutputFileOptions.Builder(
-                    mCameraView.getContext().getContentResolver(),
-                    MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
-                    contentValues).build();
-
-            mCameraView.takePicture(outputFileOptions,
-                    ContextCompat.getMainExecutor(mCameraView.getContext()),
-                    new OnImageSavedCallback() {
-                        @Override
-                        public void onImageSaved(
-                                @NonNull ImageCapture.OutputFileResults outputFileResults) {
-                            report("Picture saved to " + saveFile.getAbsolutePath());
-                            // Print out metadata about the picture
-                            // TODO: Print out metadata to log once metadata is implemented
-                        }
-
-                        @Override
-                        public void onError(@NonNull ImageCaptureException exception) {
-                            report("Failure: " + exception.getMessage(), exception.getCause());
-                        }
-                    });
-        }
-    }
-
-    /** Called when the user holds (long presses). */
-    @OptIn(markerClass = ExperimentalVideo.class)
-    private void onHold() {
-        if (mCameraView.getCaptureMode() == CaptureMode.VIDEO
-                || mCameraView.getCaptureMode() == CaptureMode.MIXED) {
-
-            final File saveFile = createNewFile(VIDEO_EXTENSION);
-            mCameraView.startRecording(saveFile,
-                    ContextCompat.getMainExecutor(mCameraView.getContext()),
-                    new OnVideoSavedCallback() {
-                        @Override
-                        public void onVideoSaved(
-                                @NonNull OutputFileResults outputFileResults) {
-                            report("Video saved to " + outputFileResults.getSavedUri());
-                        }
-
-                        @Override
-                        public void onError(int videoCaptureError, @NonNull String message,
-                                @Nullable Throwable cause) {
-                            report("Failure: " + message, cause);
-                        }
-                    });
-        }
-    }
-
-    /** Called when the user releases. */
-    @OptIn(markerClass = ExperimentalVideo.class)
-    private void onRelease() {
-        if (mCameraView.getCaptureMode() == CaptureMode.VIDEO
-                || mCameraView.getCaptureMode() == CaptureMode.MIXED) {
-            mCameraView.stopRecording();
-        }
-    }
-
-    @Override
-    public boolean onTouch(View view, MotionEvent event) {
-        switch (event.getAction()) {
-            case MotionEvent.ACTION_DOWN:
-                mDownEventTimestamp = System.currentTimeMillis();
-                mViewBoundsRect =
-                        new Rect(view.getLeft(), view.getTop(), view.getRight(), view.getBottom());
-                mHandler.sendEmptyMessageDelayed(HOLD, mLongPress);
-                view.setPressed(true);
-                break;
-            case MotionEvent.ACTION_MOVE:
-                // If the user moves their finger off the button, trigger RELEASE
-                if (mViewBoundsRect.contains(
-                        view.getLeft() + (int) event.getX(), view.getTop() + (int) event.getY())) {
-                    break;
-                }
-                // Fall-through
-            case MotionEvent.ACTION_CANCEL:
-                clearHandler();
-                if (deltaSinceDownEvent() > mLongPress
-                        && (mCameraView.getMaxVideoDuration() <= 0
-                        || deltaSinceDownEvent() < mCameraView.getMaxVideoDuration())) {
-                    mHandler.sendEmptyMessage(RELEASE);
-                }
-                view.setPressed(false);
-                break;
-            case MotionEvent.ACTION_UP:
-                clearHandler();
-                if (deltaSinceDownEvent() < mLongPress) {
-                    mHandler.sendEmptyMessage(TAP);
-                } else if ((mCameraView.getMaxVideoDuration() <= 0
-                        || deltaSinceDownEvent() < mCameraView.getMaxVideoDuration())) {
-                    mHandler.sendEmptyMessage(RELEASE);
-                }
-                view.setPressed(false);
-                break;
-            default:
-                // No op
-        }
-        return true;
-    }
-
-    private long deltaSinceDownEvent() {
-        return System.currentTimeMillis() - mDownEventTimestamp;
-    }
-
-    private void clearHandler() {
-        mHandler.removeMessages(TAP);
-        mHandler.removeMessages(HOLD);
-        mHandler.removeMessages(RELEASE);
-    }
-
-    private File createNewFile(String extension) {
-        File dirFile =
-                Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES);
-        if (dirFile != null && !dirFile.exists()) {
-            dirFile.mkdirs();
-        }
-        // Use Locale.US to ensure we get ASCII digits
-        return new File(dirFile,
-                new SimpleDateFormat(FILENAME, Locale.US).format(System.currentTimeMillis())
-                        + extension);
-    }
-
-    @SuppressWarnings("WeakerAccess")
-    void report(@NonNull String msg) {
-        report(msg, null);
-    }
-
-    @SuppressWarnings("WeakerAccess")
-    void report(@NonNull String msg, @Nullable Throwable cause) {
-        Log.d(TAG, msg, cause);
-        Toast.makeText(mCameraView.getContext(), msg, Toast.LENGTH_SHORT).show();
-    }
-}
diff --git a/camera/integration-tests/viewtestapp/src/main/java/androidx/camera/integration/view/MainActivity.java b/camera/integration-tests/viewtestapp/src/main/java/androidx/camera/integration/view/MainActivity.java
index 504277c..411ec1f 100644
--- a/camera/integration-tests/viewtestapp/src/main/java/androidx/camera/integration/view/MainActivity.java
+++ b/camera/integration-tests/viewtestapp/src/main/java/androidx/camera/integration/view/MainActivity.java
@@ -54,7 +54,7 @@
     private static final int REQUEST_CODE_PERMISSIONS = 10;
 
     private boolean mCheckedPermissions = false;
-    private Mode mMode = Mode.CAMERA_VIEW;
+    private Mode mMode = Mode.CAMERA_CONTROLLER;
 
     @Override
     protected void onCreate(@Nullable Bundle savedInstanceState) {
@@ -114,9 +114,6 @@
     @Override
     public boolean onOptionsItemSelected(@NonNull MenuItem item) {
         switch (item.getItemId()) {
-            case R.id.camera_view:
-                mMode = Mode.CAMERA_VIEW;
-                break;
             case R.id.preview_view:
                 mMode = Mode.PREVIEW_VIEW;
                 break;
@@ -143,9 +140,6 @@
 
     private void startFragment() {
         switch (mMode) {
-            case CAMERA_VIEW:
-                startFragment(R.string.camera_view, new CameraViewFragment());
-                break;
             case PREVIEW_VIEW:
                 startFragment(R.string.preview_view, new PreviewViewFragment());
                 break;
@@ -175,6 +169,6 @@
     }
 
     private enum Mode {
-        CAMERA_VIEW, PREVIEW_VIEW, CAMERA_CONTROLLER, TRANSFORM
+        PREVIEW_VIEW, CAMERA_CONTROLLER, TRANSFORM
     }
 }
diff --git a/camera/integration-tests/viewtestapp/src/main/res/drawable/fullscreen_selector.xml b/camera/integration-tests/viewtestapp/src/main/res/drawable/fullscreen_selector.xml
deleted file mode 100644
index ff39715..0000000
--- a/camera/integration-tests/viewtestapp/src/main/res/drawable/fullscreen_selector.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2019 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.
--->
-<selector
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:constantSize="true">
-    <item android:drawable="@drawable/ic_fullscreen_exit" android:state_checked="true" android:state_enabled="true" />
-    <item android:drawable="@drawable/ic_fullscreen" android:state_checked="false" android:state_enabled="true" />
-</selector>
diff --git a/camera/integration-tests/viewtestapp/src/main/res/drawable/ic_camera.xml b/camera/integration-tests/viewtestapp/src/main/res/drawable/ic_camera.xml
deleted file mode 100644
index a35682e..0000000
--- a/camera/integration-tests/viewtestapp/src/main/res/drawable/ic_camera.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2019 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.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
-    android:width="24dp"
-    android:height="24dp"
-    android:viewportHeight="24.0"
-    android:viewportWidth="24.0">
-    <path
-
-        android:fillColor="?android:attr/colorControlNormal"
-        android:pathData="M9.4,10.5l4.77,-8.26C13.47,2.09 12.75,2 12,2c-2.4,0 -4.6,0.85 -6.32,2.25l3.66,6.35 0.06,-0.1zM21.54,9c-0.92,-2.92 -3.15,-5.26 -6,-6.34L11.88,9h9.66zM21.8,10h-7.49l0.29,0.5 4.76,8.25C21,16.97 22,14.61 22,12c0,-0.69 -0.07,-1.35 -0.2,-2zM8.54,12l-3.9,-6.75C3.01,7.03 2,9.39 2,12c0,0.69 0.07,1.35 0.2,2h7.49l-1.15,-2zM2.46,15c0.92,2.92 3.15,5.26 6,6.34L12.12,15L2.46,15zM13.73,15l-3.9,6.76c0.7,0.15 1.42,0.24 2.17,0.24 2.4,0 4.6,-0.85 6.32,-2.25l-3.66,-6.35 -0.93,1.6z" />
-</vector>
diff --git a/camera/integration-tests/viewtestapp/src/main/res/drawable/ic_fullscreen.xml b/camera/integration-tests/viewtestapp/src/main/res/drawable/ic_fullscreen.xml
deleted file mode 100644
index 467ce90..0000000
--- a/camera/integration-tests/viewtestapp/src/main/res/drawable/ic_fullscreen.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2019 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.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
-    android:width="24dp"
-    android:height="24dp"
-    android:viewportHeight="24.0"
-    android:viewportWidth="24.0">
-    <path
-        android:fillColor="?android:attr/colorControlNormal"
-        android:pathData="M7,14L5,14v5h5v-2L7,17v-3zM5,10h2L7,7h3L10,5L5,5v5zM17,17h-3v2h5v-5h-2v3zM14,5v2h3v3h2L19,5h-5z" />
-</vector>
diff --git a/camera/integration-tests/viewtestapp/src/main/res/drawable/ic_fullscreen_exit.xml b/camera/integration-tests/viewtestapp/src/main/res/drawable/ic_fullscreen_exit.xml
deleted file mode 100644
index 8ee9dfc..0000000
--- a/camera/integration-tests/viewtestapp/src/main/res/drawable/ic_fullscreen_exit.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2019 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.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
-    android:width="24dp"
-    android:height="24dp"
-    android:viewportHeight="24.0"
-    android:viewportWidth="24.0">
-    <path
-        android:fillColor="?android:attr/colorControlNormal"
-        android:pathData="M5,16h3v3h2v-5L5,14v2zM8,8L5,8v2h5L10,5L8,5v3zM14,19h2v-3h3v-2h-5v5zM16,8L16,5h-2v5h5L19,8h-3z" />
-</vector>
diff --git a/camera/integration-tests/viewtestapp/src/main/res/drawable/ic_photo_camera.xml b/camera/integration-tests/viewtestapp/src/main/res/drawable/ic_photo_camera.xml
deleted file mode 100644
index 625e155..0000000
--- a/camera/integration-tests/viewtestapp/src/main/res/drawable/ic_photo_camera.xml
+++ /dev/null
@@ -1,28 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2019 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.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
-    android:width="24dp"
-    android:height="24dp"
-    android:tint="?attr/colorControlNormal"
-    android:viewportHeight="24.0"
-    android:viewportWidth="24.0">
-    <path
-        android:fillColor="@android:color/white"
-        android:pathData="M12,12m-3.2,0a3.2,3.2 0,1 1,6.4 0a3.2,3.2 0,1 1,-6.4 0" />
-    <path
-        android:fillColor="@android:color/white"
-        android:pathData="M9,2L7.17,4L4,4c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2L22,6c0,-1.1 -0.9,-2 -2,-2h-3.17L15,2L9,2zM12,17c-2.76,0 -5,-2.24 -5,-5s2.24,-5 5,-5 5,2.24 5,5 -2.24,5 -5,5z" />
-</vector>
diff --git a/camera/integration-tests/viewtestapp/src/main/res/drawable/ic_videocam.xml b/camera/integration-tests/viewtestapp/src/main/res/drawable/ic_videocam.xml
deleted file mode 100644
index f009891..0000000
--- a/camera/integration-tests/viewtestapp/src/main/res/drawable/ic_videocam.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2019 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.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
-    android:width="24dp"
-    android:height="24dp"
-    android:tint="?attr/colorControlNormal"
-    android:viewportHeight="24.0"
-    android:viewportWidth="24.0">
-    <path
-        android:fillColor="@android:color/white"
-        android:pathData="M18,10.48V6c0,-1.1 -0.9,-2 -2,-2H4C2.9,4 2,4.9 2,6v12c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2v-4.48l4,3.98v-11L18,10.48zM16,9.69V18H4V6h12V9.69zM11.67,11l-2.5,3.72L7.5,12L5,16h10L11.67,11z" />
-</vector>
diff --git a/camera/integration-tests/viewtestapp/src/main/res/layout-land/fragment_main.xml b/camera/integration-tests/viewtestapp/src/main/res/layout-land/fragment_main.xml
deleted file mode 100644
index 7938a58..0000000
--- a/camera/integration-tests/viewtestapp/src/main/res/layout-land/fragment_main.xml
+++ /dev/null
@@ -1,73 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2019 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.
--->
-<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent"
-    android:orientation="vertical">
-
-    <LinearLayout
-        android:id="@+id/layout_camera"
-        android:layout_width="match_parent"
-        android:layout_height="match_parent"
-        android:orientation="horizontal">
-
-        <androidx.camera.view.CameraView
-            android:id="@+id/camera"
-            android:layout_width="0dp"
-            android:layout_height="match_parent"
-            android:layout_gravity="center_vertical"
-            android:layout_weight="1" />
-
-        <LinearLayout
-            android:layout_width="125dp"
-            android:layout_height="match_parent"
-            android:layout_marginBottom="10dp"
-            android:layout_marginTop="10dp"
-            android:gravity="center"
-            android:orientation="vertical">
-
-            <CheckBox
-                android:id="@+id/toggle_crop"
-                android:layout_width="wrap_content"
-                android:layout_height="50dp"
-                android:button="@drawable/fullscreen_selector" />
-
-            <CheckBox
-                android:id="@+id/mode"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:button="@drawable/ic_photo_camera" />
-
-            <Button
-                android:id="@+id/capture"
-                style="?android:buttonBarButtonStyle"
-                android:layout_width="match_parent"
-                android:layout_height="0dp"
-                android:layout_marginBottom="10dp"
-                android:layout_marginTop="10dp"
-                android:layout_weight="1"
-                android:text="@string/btn_capture" />
-
-            <CheckBox
-                android:id="@+id/toggle"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content" />
-
-        </LinearLayout>
-
-    </LinearLayout>
-
-</FrameLayout>
diff --git a/camera/integration-tests/viewtestapp/src/main/res/layout/fragment_main.xml b/camera/integration-tests/viewtestapp/src/main/res/layout/fragment_main.xml
deleted file mode 100644
index 72f8ecb..0000000
--- a/camera/integration-tests/viewtestapp/src/main/res/layout/fragment_main.xml
+++ /dev/null
@@ -1,73 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2019 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.
--->
-<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent"
-    android:orientation="vertical">
-
-    <LinearLayout
-        android:id="@+id/layout_camera"
-        android:layout_width="match_parent"
-        android:layout_height="match_parent"
-        android:orientation="vertical">
-
-        <androidx.camera.view.CameraView
-            android:id="@+id/camera"
-            android:layout_width="match_parent"
-            android:layout_height="0dp"
-            android:layout_gravity="center_horizontal"
-            android:layout_weight="1" />
-
-        <LinearLayout
-            android:layout_width="match_parent"
-            android:layout_height="58dp"
-            android:layout_marginLeft="10dp"
-            android:layout_marginRight="10dp"
-            android:gravity="center">
-
-            <CheckBox
-                android:id="@+id/toggle"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content" />
-
-            <Button
-                android:id="@+id/capture"
-                style="?android:buttonBarButtonStyle"
-                android:layout_width="0dp"
-                android:layout_height="match_parent"
-                android:layout_marginLeft="50dp"
-                android:layout_marginRight="50dp"
-                android:layout_weight="1"
-                android:text="@string/btn_capture" />
-
-            <CheckBox
-                android:id="@+id/mode"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:layout_marginRight="15dp"
-                android:button="@drawable/ic_photo_camera" />
-
-            <CheckBox
-                android:id="@+id/toggle_crop"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:button="@drawable/fullscreen_selector" />
-
-        </LinearLayout>
-
-    </LinearLayout>
-
-</FrameLayout>
diff --git a/camera/integration-tests/viewtestapp/src/main/res/menu/actionbar_menu.xml b/camera/integration-tests/viewtestapp/src/main/res/menu/actionbar_menu.xml
index 8babd1e..4868f69 100644
--- a/camera/integration-tests/viewtestapp/src/main/res/menu/actionbar_menu.xml
+++ b/camera/integration-tests/viewtestapp/src/main/res/menu/actionbar_menu.xml
@@ -17,10 +17,6 @@
 <menu xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:app="http://schemas.android.com/apk/res-auto">
     <item
-        android:id="@+id/camera_view"
-        android:title="@string/camera_view"
-        app:showAsAction="never" />
-    <item
         android:id="@+id/preview_view"
         android:title="@string/preview_view"
         app:showAsAction="never" />
diff --git a/camera/integration-tests/viewtestapp/src/main/res/values/donottranslate-strings.xml b/camera/integration-tests/viewtestapp/src/main/res/values/donottranslate-strings.xml
index 0eb3bd9..e257662 100644
--- a/camera/integration-tests/viewtestapp/src/main/res/values/donottranslate-strings.xml
+++ b/camera/integration-tests/viewtestapp/src/main/res/values/donottranslate-strings.xml
@@ -41,7 +41,6 @@
     <string name="btn_switch">Switch</string>
     <string name="btn_confirm">Confirm</string>
     <string name="btn_cancel">Cancel</string>
-    <string name="camera_view">Camera View</string>
     <string name="preview_view">Preview View</string>
     <string name="camera_controller">Camera Controller</string>
     <string name="transform">Transform</string>
diff --git a/car/app/app-activity/lint-baseline.xml b/car/app/app-activity/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/car/app/app-activity/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/car/app/app-activity/src/main/java/androidx/car/app/activity/ActivityLifecycleDelegate.java b/car/app/app-activity/src/main/java/androidx/car/app/activity/ActivityLifecycleDelegate.java
index 66bad88..0df1174 100644
--- a/car/app/app-activity/src/main/java/androidx/car/app/activity/ActivityLifecycleDelegate.java
+++ b/car/app/app-activity/src/main/java/androidx/car/app/activity/ActivityLifecycleDelegate.java
@@ -32,7 +32,6 @@
  * IRendererCallback}.
  */
 final class ActivityLifecycleDelegate implements ActivityLifecycleCallbacks {
-    public static final String TAG = "ActivityLifecycleListener";
     @NonNull
     private ServiceDispatcher mServiceDispatcher;
     @Nullable
@@ -88,8 +87,7 @@
 
     @Override
     public void onActivityPreDestroyed(@NonNull Activity activity) {
-        requireNonNull(activity);
-        notifyEvent(Event.ON_DESTROY);
+        // No-op as the view model will call terminate on the service when needed.
     }
 
     @Override
diff --git a/car/app/app-activity/src/main/java/androidx/car/app/activity/CarAppActivity.java b/car/app/app-activity/src/main/java/androidx/car/app/activity/CarAppActivity.java
index f87ebec..1130a27 100644
--- a/car/app/app-activity/src/main/java/androidx/car/app/activity/CarAppActivity.java
+++ b/car/app/app-activity/src/main/java/androidx/car/app/activity/CarAppActivity.java
@@ -22,15 +22,10 @@
 
 import android.annotation.SuppressLint;
 import android.content.ComponentName;
-import android.content.Context;
 import android.content.Intent;
-import android.content.ServiceConnection;
 import android.content.pm.ActivityInfo;
 import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
 import android.os.Bundle;
-import android.os.IBinder;
-import android.os.RemoteException;
 import android.util.Log;
 import android.view.View;
 
@@ -49,8 +44,7 @@
 import androidx.car.app.serialization.BundlerException;
 import androidx.car.app.utils.ThreadUtils;
 import androidx.fragment.app.FragmentActivity;
-
-import java.util.List;
+import androidx.lifecycle.ViewModelProvider;
 
 /**
  * The class representing a car app activity.
@@ -96,20 +90,16 @@
 public final class CarAppActivity extends FragmentActivity {
     @VisibleForTesting
     static final String SERVICE_METADATA_KEY = "androidx.car.app.CAR_APP_SERVICE";
-    private static final String TAG = "CarAppActivity";
 
     @SuppressLint({"ActionValue"})
     @VisibleForTesting
     static final String ACTION_RENDER = "android.car.template.host.RendererService";
 
-    @Nullable
-    private ComponentName mServiceComponentName;
     TemplateSurfaceView mSurfaceView;
-    SurfaceHolderListener mSurfaceHolderListener;
-    ActivityLifecycleDelegate mActivityLifecycleDelegate;
-    @Nullable
-    OnBackPressedListener mOnBackPressedListener;
-    ServiceDispatcher mServiceDispatcher;
+    @Nullable SurfaceHolderListener mSurfaceHolderListener;
+    @Nullable ActivityLifecycleDelegate mActivityLifecycleDelegate;
+    @Nullable OnBackPressedListener mOnBackPressedListener;
+    @Nullable CarAppViewModel mViewModel;
     private int mDisplayId;
 
     /**
@@ -121,7 +111,7 @@
 
         Log.e(LogTags.TAG, "Service error: " + errorType, exception);
 
-        unbindService();
+        requireNonNull(mViewModel).unbind();
 
         ThreadUtils.runOnMain(() -> {
             Log.d(LogTags.TAG, "Showing error fragment");
@@ -162,14 +152,16 @@
                     ThreadUtils.runOnMain(
                             () -> {
                                 mSurfaceView.setOnCreateInputConnectionListener(editorInfo ->
-                                        mServiceDispatcher.fetch(null, () ->
+                                        getServiceDispatcher().fetch(null, () ->
                                                 callback.onCreateInputConnection(
                                                         editorInfo)));
 
                                 mOnBackPressedListener = () ->
-                                        mServiceDispatcher.dispatch(callback::onBackPressed);
+                                        getServiceDispatcher().dispatch(callback::onBackPressed);
 
-                                mActivityLifecycleDelegate.registerRendererCallback(callback);
+                                requireNonNull(mActivityLifecycleDelegate)
+                                        .registerRendererCallback(callback);
+                                requireNonNull(mViewModel).setRendererCallback(callback);
                             });
                 }
 
@@ -177,7 +169,8 @@
                 public void setSurfaceListener(@NonNull ISurfaceListener listener) {
                     requireNonNull(listener);
                     ThreadUtils.runOnMain(
-                            () -> mSurfaceHolderListener.setSurfaceListener(listener));
+                            () -> requireNonNull(mSurfaceHolderListener)
+                                    .setSurfaceListener(listener));
                 }
 
                 @Override
@@ -201,87 +194,41 @@
                 }
             };
 
-    /** The service connection for the renderer service. */
-    private ServiceConnection mServiceConnectionImpl =
-            new ServiceConnection() {
-                @Override
-                public void onServiceConnected(
-                        @NonNull ComponentName name, @NonNull IBinder service) {
-                    requireNonNull(name);
-                    requireNonNull(service);
-                    Log.i(LogTags.TAG, String.format("Host service %s is connected",
-                            name.flattenToShortString()));
-                    IRendererService rendererService = IRendererService.Stub.asInterface(service);
-                    if (rendererService == null) {
-                        mErrorHandler.onError(ErrorHandler.ErrorType.HOST_INCOMPATIBLE,
-                                new Exception("Failed to get IRenderService binder from host: "
-                                        + name));
-                        return;
-                    }
-
-                    mServiceDispatcher.setRendererService(rendererService);
-                    verifyServiceVersion(rendererService);
-                    initializeService(rendererService);
-                    updateIntent(getIntent());
-                }
-
-                @Override
-                public void onServiceDisconnected(@NonNull ComponentName name) {
-                    requireNonNull(name);
-
-                    // Connection lost, but it might reconnect.
-                    Log.w(LogTags.TAG, String.format("Host service %s is disconnected",
-                            name.flattenToShortString()));
-                }
-
-                @Override
-                public void onBindingDied(@NonNull ComponentName name) {
-                    requireNonNull(name);
-
-                    // Connection permanently lost
-                    mErrorHandler.onError(ErrorHandler.ErrorType.HOST_CONNECTION_LOST,
-                            new Exception("Host service " + name + " is permanently disconnected"));
-                }
-
-                @Override
-                public void onNullBinding(@NonNull ComponentName name) {
-                    requireNonNull(name);
-
-                    // Host rejected the binding.
-                    mErrorHandler.onError(ErrorHandler.ErrorType.HOST_INCOMPATIBLE,
-                            new Exception("Host service " + name + " rejected the binding "
-                                    + "request"));
-                }
-            };
-
     @SuppressWarnings("deprecation")
     @Override
     protected void onCreate(@Nullable Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
-
-        mServiceDispatcher = new ServiceDispatcher(mErrorHandler);
         setContentView(R.layout.activity_template);
         mSurfaceView = requireViewById(R.id.template_view_surface);
-        mActivityLifecycleDelegate = new ActivityLifecycleDelegate(mServiceDispatcher);
-        mSurfaceHolderListener = new SurfaceHolderListener(mServiceDispatcher,
-                new SurfaceWrapperProvider(mSurfaceView));
 
-        mServiceComponentName = retrieveServiceComponentName();
-        if (mServiceComponentName == null) {
-            Log.e(TAG, "Unspecified service class name");
+        ComponentName serviceComponentName = retrieveServiceComponentName();
+        if (serviceComponentName == null) {
+            Log.e(LogTags.TAG, "Unspecified service class name");
             finish();
             return;
         }
+        mDisplayId = getWindowManager().getDefaultDisplay().getDisplayId();
 
-        registerActivityLifecycleCallbacks(mActivityLifecycleDelegate);
+        CarAppViewModelFactory factory = CarAppViewModelFactory.getInstance(getApplication(),
+                serviceComponentName);
+        mViewModel = new ViewModelProvider(this, factory).get(CarAppViewModel.class);
+        mViewModel.getErrorEvent().observe(this,
+                errorEvent -> mErrorHandler.onError(errorEvent.getErrorType(),
+                        errorEvent.getException()));
+
+        mActivityLifecycleDelegate = new ActivityLifecycleDelegate(getServiceDispatcher());
+        mSurfaceHolderListener = new SurfaceHolderListener(getServiceDispatcher(),
+                new SurfaceWrapperProvider(mSurfaceView));
+
+        registerActivityLifecycleCallbacks(requireNonNull(mActivityLifecycleDelegate));
 
         // Set the z-order to receive the UI events on the surface.
         mSurfaceView.setZOrderOnTop(true);
-        mSurfaceView.setServiceDispatcher(mServiceDispatcher);
+        mSurfaceView.setServiceDispatcher(getServiceDispatcher());
         mSurfaceView.setErrorHandler(mErrorHandler);
         mSurfaceView.getHolder().addCallback(mSurfaceHolderListener);
-        mDisplayId = getWindowManager().getDefaultDisplay().getDisplayId();
-        bindService();
+
+        mViewModel.bind(getIntent(), mCarActivity, mDisplayId);
     }
 
     @Override
@@ -300,13 +247,6 @@
         }
     }
 
-    @Override
-    protected void onDestroy() {
-        if (isFinishing()) {
-            unbindService();
-        }
-        super.onDestroy();
-    }
 
     @Override
     public void onBackPressed() {
@@ -318,21 +258,7 @@
     @Override
     protected void onNewIntent(@NonNull Intent intent) {
         super.onNewIntent(intent);
-        if (!mServiceDispatcher.isBound()) {
-            bindService();
-        } else {
-            updateIntent(intent);
-        }
-    }
-
-    @VisibleForTesting
-    ServiceConnection getServiceConnection() {
-        return mServiceConnectionImpl;
-    }
-
-    @VisibleForTesting
-    void setServiceConnection(ServiceConnection serviceConnection) {
-        mServiceConnectionImpl = serviceConnection;
+        requireNonNull(mViewModel).bind(intent, mCarActivity, mDisplayId);
     }
 
     @VisibleForTesting
@@ -340,6 +266,11 @@
         return mDisplayId;
     }
 
+    @VisibleForTesting
+    ServiceDispatcher getServiceDispatcher() {
+        return requireNonNull(mViewModel).getServiceDispatcher();
+    }
+
     @Nullable
     private ComponentName retrieveServiceComponentName() {
         ActivityInfo activityInfo = null;
@@ -348,7 +279,7 @@
                     getPackageManager()
                             .getActivityInfo(getComponentName(), PackageManager.GET_META_DATA);
         } catch (NameNotFoundException e) {
-            Log.e(TAG, "Unable to find component: " + getComponentName(), e);
+            Log.e(LogTags.TAG, "Unable to find component: " + getComponentName(), e);
         }
 
         if (activityInfo == null) {
@@ -358,7 +289,7 @@
         String serviceName = activityInfo.metaData.getString(SERVICE_METADATA_KEY);
         if (serviceName == null) {
             Log.e(
-                    TAG,
+                    LogTags.TAG,
                     "Unable to find required metadata tag with name "
                             + SERVICE_METADATA_KEY
                             + ". App manifest must include metadata tag with name "
@@ -369,113 +300,4 @@
 
         return new ComponentName(this, serviceName);
     }
-
-    /** Binds to the renderer service. */
-    private void bindService() {
-        Intent rendererIntent = new Intent(ACTION_RENDER);
-        List<ResolveInfo> resolveInfoList =
-                getPackageManager()
-                        .queryIntentServices(rendererIntent, PackageManager.GET_META_DATA);
-        if (resolveInfoList.size() == 1) {
-            rendererIntent.setPackage(resolveInfoList.get(0).serviceInfo.packageName);
-            if (!bindService(
-                    rendererIntent,
-                    mServiceConnectionImpl,
-                    Context.BIND_AUTO_CREATE | Context.BIND_INCLUDE_CAPABILITIES)) {
-                mErrorHandler.onError(ErrorHandler.ErrorType.HOST_INCOMPATIBLE,
-                        new Exception("Cannot bind to the renderer host with intent: "
-                                + rendererIntent));
-            }
-        } else if (resolveInfoList.isEmpty()) {
-            mErrorHandler.onError(ErrorHandler.ErrorType.HOST_NOT_FOUND, new Exception("No "
-                    + "handlers found for intent: " + rendererIntent));
-        } else {
-            StringBuilder logMessage =
-                    new StringBuilder("Multiple hosts found, only one is allowed");
-            for (ResolveInfo resolveInfo : resolveInfoList) {
-                logMessage.append(
-                        String.format("\nFound host %s", resolveInfo.serviceInfo.packageName));
-            }
-            mErrorHandler.onError(ErrorHandler.ErrorType.MULTIPLE_HOSTS,
-                    new Exception(logMessage.toString()));
-        }
-    }
-
-    /**
-     * Verifies that the renderer service supports the current version.
-     *
-     * @param rendererService the renderer service which should verify the version
-     */
-    void verifyServiceVersion(IRendererService rendererService) {
-        // TODO(169604451) Add version support logic
-        boolean isCompatible = true;
-
-        if (!isCompatible) {
-            mErrorHandler.onError(ErrorHandler.ErrorType.HOST_INCOMPATIBLE,
-                    new Exception("Renderer service unsupported"));
-        }
-    }
-
-    /**
-     * Initializes the {@code rendererService} for the current activity with {@code carActivity},
-     * {@code serviceComponentName} and {@code displayId}.
-     *
-     * @param rendererService the renderer service that needs to be initialized
-     */
-    void initializeService(@NonNull IRendererService rendererService) {
-        requireNonNull(rendererService);
-        requireNonNull(mServiceComponentName);
-        ComponentName serviceComponentName = mServiceComponentName;
-        Boolean success = mServiceDispatcher.fetch(false,
-                () -> rendererService.initialize(mCarActivity,
-                        serviceComponentName, mDisplayId));
-        if (success == null || !success) {
-            mErrorHandler.onError(ErrorHandler.ErrorType.HOST_ERROR,
-                    new Exception("Cannot create renderer for" + mServiceComponentName));
-        }
-    }
-
-    /** Closes the connection to the connected {@code rendererService} if any. */
-    void unbindService() {
-        // Remove the renderer callback since there is no need to communicate the state with
-        // the host.
-        mActivityLifecycleDelegate.registerRendererCallback(null);
-        // Stop sending SurfaceView updates
-        mSurfaceView.getHolder().removeCallback(mSurfaceHolderListener);
-        // If host has already disconnected, there is no need for an unbind.
-        IRendererService rendererService = mServiceDispatcher.getRendererService();
-        if (rendererService == null) {
-            return;
-        }
-        try {
-            rendererService.terminate(requireNonNull(mServiceComponentName));
-        } catch (RemoteException e) {
-            // We are already unbinding (maybe because the host has already cut the connection)
-            // Let's not log more errors unnecessarily.
-        }
-
-        Log.i(LogTags.TAG, "Unbinding from " + mServiceComponentName);
-        unbindService(mServiceConnectionImpl);
-        mServiceDispatcher.setRendererService(null);
-    }
-
-    /**
-     * Updates the activity intent for the {@code rendererService}.
-     */
-    void updateIntent(Intent intent) {
-        requireNonNull(mServiceComponentName);
-        IRendererService service = mServiceDispatcher.getRendererService();
-        if (service == null) {
-            mErrorHandler.onError(ErrorHandler.ErrorType.CLIENT_SIDE_ERROR,
-                    new Exception("Service dispatcher is not connected"));
-            return;
-        }
-        ComponentName serviceComponentName = mServiceComponentName;
-        Boolean success = mServiceDispatcher.fetch(false, () ->
-                service.onNewIntent(intent, serviceComponentName, mDisplayId));
-        if (success == null || !success) {
-            mErrorHandler.onError(ErrorHandler.ErrorType.HOST_ERROR, new Exception("Renderer "
-                    + "cannot handle the intent: " + intent));
-        }
-    }
 }
diff --git a/car/app/app-activity/src/main/java/androidx/car/app/activity/CarAppViewModel.java b/car/app/app-activity/src/main/java/androidx/car/app/activity/CarAppViewModel.java
new file mode 100644
index 0000000..aa48ca9
--- /dev/null
+++ b/car/app/app-activity/src/main/java/androidx/car/app/activity/CarAppViewModel.java
@@ -0,0 +1,130 @@
+/*
+ * 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.car.app.activity;
+
+import static androidx.annotation.RestrictTo.Scope.LIBRARY;
+
+import android.app.Application;
+import android.content.ComponentName;
+import android.content.Intent;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.RestrictTo;
+import androidx.annotation.VisibleForTesting;
+import androidx.car.app.activity.renderer.ICarAppActivity;
+import androidx.car.app.activity.renderer.IRendererCallback;
+import androidx.lifecycle.AndroidViewModel;
+import androidx.lifecycle.MutableLiveData;
+
+/**
+ * The view model to keep track of the CarAppActivity data.
+ *
+ * This main role of this class is to extent the life of a service connection beyond the regular
+ * lifecycle of an activity. This is done by making sure the unbind happens when the view model
+ * clears instead of when the activity calls onDestroy.
+ *
+ * @hide
+ */
+@RestrictTo(LIBRARY)
+public class CarAppViewModel extends AndroidViewModel implements ErrorHandler {
+    /** Holds the information about an error event. */
+    public static class ErrorEvent {
+        private final ErrorType mErrorType;
+        private final Throwable mException;
+
+        public ErrorEvent(@NonNull ErrorType errorType, @NonNull Throwable exception) {
+            mErrorType = errorType;
+            mException = exception;
+        }
+
+        /** Returns the type of error. */
+        @NonNull ErrorType getErrorType() {
+            return mErrorType;
+        }
+
+        /** Returns the exception associated with this error event. */
+        @NonNull Throwable getException() {
+            return mException;
+        }
+    }
+
+    private final MutableLiveData<ErrorEvent> mErrorEvent = new MutableLiveData<>();
+    private ServiceConnectionManager mServiceConnectionManager;
+    @Nullable private IRendererCallback mIRendererCallback;
+
+    public CarAppViewModel(@NonNull Application application, @NonNull ComponentName componentName) {
+        super(application);
+
+        mServiceConnectionManager = new ServiceConnectionManager(application, componentName, this);
+    }
+
+    @VisibleForTesting
+    @NonNull ServiceConnectionManager getServiceConnectionManager() {
+        return mServiceConnectionManager;
+    }
+
+    @VisibleForTesting
+    void setServiceConnectionManager(ServiceConnectionManager serviceConnectionManager) {
+        mServiceConnectionManager = serviceConnectionManager;
+    }
+
+    @NonNull ServiceDispatcher getServiceDispatcher() {
+        return mServiceConnectionManager.getServiceDispatcher();
+    }
+
+    /** Updates the rendeer callback. */
+    void setRendererCallback(@NonNull IRendererCallback rendererCallback) {
+        mIRendererCallback = rendererCallback;
+    }
+
+    /**
+     * Binds to the renderer service and initializes the service if not bound already.
+     *
+     * Initializes the renderer service with given properties if already bound to the renderer
+     * service.
+     */
+    void bind(@NonNull Intent intent, @NonNull ICarAppActivity iCarAppActivity,
+            int displayId) {
+        mServiceConnectionManager.bind(intent, iCarAppActivity, displayId);
+    }
+
+    /** Closes the connection to the renderer service if any. */
+    void unbind() {
+        mServiceConnectionManager.unbind();
+    }
+
+    @NonNull
+    MutableLiveData<ErrorEvent> getErrorEvent() {
+        return mErrorEvent;
+    }
+
+    @Override
+    protected void onCleared() {
+        super.onCleared();
+        if (mIRendererCallback != null) {
+            mServiceConnectionManager.getServiceDispatcher()
+                    .dispatch(mIRendererCallback::onDestroyed);
+        }
+        mServiceConnectionManager.unbind();
+    }
+
+    @Override
+    public void onError(@NonNull ErrorHandler.ErrorType errorType, @NonNull Throwable exception) {
+        mErrorEvent.setValue(new ErrorEvent(errorType, exception));
+    }
+}
diff --git a/car/app/app-activity/src/main/java/androidx/car/app/activity/CarAppViewModelFactory.java b/car/app/app-activity/src/main/java/androidx/car/app/activity/CarAppViewModelFactory.java
new file mode 100644
index 0000000..3c9627d
--- /dev/null
+++ b/car/app/app-activity/src/main/java/androidx/car/app/activity/CarAppViewModelFactory.java
@@ -0,0 +1,72 @@
+/*
+ * 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.car.app.activity;
+
+import static androidx.annotation.RestrictTo.Scope.LIBRARY;
+
+import android.app.Application;
+import android.content.ComponentName;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.RestrictTo;
+import androidx.lifecycle.ViewModel;
+import androidx.lifecycle.ViewModelProvider;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * A factory to provide a unique {@link CarAppViewModel} for each given {@link ComponentName}.
+ *
+ * @hide
+ */
+@RestrictTo(LIBRARY)
+class CarAppViewModelFactory implements ViewModelProvider.Factory {
+    private static final Map<ComponentName, CarAppViewModelFactory> sInstances = new HashMap<>();
+
+    Application mApplication;
+    ComponentName mComponentName;
+
+    private CarAppViewModelFactory(@NonNull ComponentName componentName,
+            @NonNull Application application) {
+        mComponentName = componentName;
+        mApplication = application;
+    }
+
+    /**
+     * Retrieve a singleton instance of CarAppViewModelFactory for the given key.
+     *
+     * @return A valid {@link CarAppViewModelFactory}
+     */
+    @NonNull
+    static CarAppViewModelFactory getInstance(Application application,
+            ComponentName componentName) {
+        CarAppViewModelFactory instance = sInstances.get(componentName);
+        if (instance == null) {
+            instance = new CarAppViewModelFactory(componentName, application);
+            sInstances.put(componentName, instance);
+        }
+        return instance;
+    }
+
+    @SuppressWarnings("unchecked")
+    @NonNull
+    @Override
+    public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
+        return (T) new CarAppViewModel(mApplication, mComponentName);
+    }
+}
diff --git a/car/app/app-activity/src/main/java/androidx/car/app/activity/ServiceConnectionManager.java b/car/app/app-activity/src/main/java/androidx/car/app/activity/ServiceConnectionManager.java
new file mode 100644
index 0000000..93f9b5d
--- /dev/null
+++ b/car/app/app-activity/src/main/java/androidx/car/app/activity/ServiceConnectionManager.java
@@ -0,0 +1,267 @@
+/*
+ * 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.car.app.activity;
+
+import static androidx.annotation.RestrictTo.Scope.LIBRARY;
+
+import static java.util.Objects.requireNonNull;
+
+import android.annotation.SuppressLint;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.RestrictTo;
+import androidx.annotation.VisibleForTesting;
+import androidx.car.app.activity.renderer.ICarAppActivity;
+import androidx.car.app.activity.renderer.IRendererService;
+
+import java.util.List;
+
+/**
+ * Manages the renderer service connection state.
+ *
+ * This class handles binding and unbinding to the renderer service and make sure the renderer
+ * service gets initialized and terminated properly.
+ *
+ * @hide
+ */
+@RestrictTo(LIBRARY)
+public class ServiceConnectionManager {
+    @SuppressLint({"ActionValue"})
+    @VisibleForTesting
+    static final String ACTION_RENDER = "android.car.template.host.RendererService";
+
+    final ErrorHandler mErrorHandler;
+    private final ComponentName mServiceComponentName;
+    private final Context mContext;
+    private final ServiceDispatcher mServiceDispatcher;
+    private int mDisplayId;
+    @Nullable private Intent mIntent;
+    @Nullable private ICarAppActivity mICarAppActivity;
+
+    @Nullable IRendererService mRendererService;
+
+    public ServiceConnectionManager(@NonNull Context context,
+            @NonNull ComponentName serviceComponentName, @NonNull ErrorHandler errorHandler) {
+        mContext = context;
+        mErrorHandler = errorHandler;
+        mServiceComponentName = serviceComponentName;
+        mServiceDispatcher = new ServiceDispatcher(mErrorHandler, this::isBound);
+    }
+
+    /**
+     * Returns a {@link ServiceDispatcher} that can be used to communicate with the renderer
+     * service.
+     */
+    @NonNull ServiceDispatcher getServiceDispatcher() {
+        return mServiceDispatcher;
+    }
+
+    @VisibleForTesting
+    ComponentName getServiceComponentName() {
+        return mServiceComponentName;
+    }
+
+    @VisibleForTesting
+    ErrorHandler getErrorHandler() {
+        return mErrorHandler;
+    }
+
+    @VisibleForTesting
+    ServiceConnection getServiceConnection() {
+        return mServiceConnectionImpl;
+    }
+
+    @VisibleForTesting
+    void setServiceConnection(ServiceConnection serviceConnection) {
+        mServiceConnectionImpl = serviceConnection;
+    }
+
+    @VisibleForTesting
+    void setRendererService(@Nullable IRendererService rendererService) {
+        mRendererService = rendererService;
+    }
+
+    /** Returns true if the service is currently bound and able to receive messages */
+    boolean isBound() {
+        return mRendererService != null;
+    }
+
+    /** The service connection for the renderer service. */
+    private ServiceConnection mServiceConnectionImpl =
+            new ServiceConnection() {
+                @Override
+                public void onServiceConnected(
+                        @NonNull ComponentName name, @NonNull IBinder service) {
+                    requireNonNull(name);
+                    requireNonNull(service);
+                    Log.i(LogTags.TAG, String.format("Host service %s is connected",
+                            name.flattenToShortString()));
+                    IRendererService rendererService = IRendererService.Stub.asInterface(service);
+                    if (rendererService == null) {
+                        mErrorHandler.onError(ErrorHandler.ErrorType.HOST_INCOMPATIBLE,
+                                new Exception("Failed to get IRenderService binder from host: "
+                                        + name));
+                        return;
+                    }
+
+                    mRendererService = rendererService;
+                    initializeService();
+                }
+
+                @Override
+                public void onServiceDisconnected(@NonNull ComponentName name) {
+                    requireNonNull(name);
+
+                    // Connection lost, but it might reconnect.
+                    Log.w(LogTags.TAG, String.format("Host service %s is disconnected",
+                            name.flattenToShortString()));
+                }
+
+                @Override
+                public void onBindingDied(@NonNull ComponentName name) {
+                    requireNonNull(name);
+
+                    // Connection permanently lost
+                    mErrorHandler.onError(ErrorHandler.ErrorType.HOST_CONNECTION_LOST,
+                            new Exception("Host service " + name + " is permanently disconnected"));
+                }
+
+                @Override
+                public void onNullBinding(@NonNull ComponentName name) {
+                    requireNonNull(name);
+
+                    // Host rejected the binding.
+                    mErrorHandler.onError(ErrorHandler.ErrorType.HOST_INCOMPATIBLE,
+                            new Exception("Host service " + name + " rejected the binding "
+                                    + "request"));
+                }
+            };
+
+    /**
+     * Binds to the renderer service and initializes the service if not bound already.
+     *
+     * Initializes the renderer service with given properties if already bound to the renderer
+     * service.
+     */
+    void bind(@NonNull Intent intent, @NonNull ICarAppActivity iCarAppActivity, int displayId) {
+        mIntent = requireNonNull(intent);
+        mICarAppActivity = requireNonNull(iCarAppActivity);
+        mDisplayId = displayId;
+
+        if (isBound()) {
+            initializeService();
+            return;
+        }
+
+        Intent rendererIntent = new Intent(ACTION_RENDER);
+        List<ResolveInfo> resolveInfoList =
+                mContext.getPackageManager()
+                        .queryIntentServices(rendererIntent, PackageManager.GET_META_DATA);
+        if (resolveInfoList.size() == 1) {
+            rendererIntent.setPackage(resolveInfoList.get(0).serviceInfo.packageName);
+            if (!mContext.bindService(
+                    rendererIntent,
+                    mServiceConnectionImpl,
+                    Context.BIND_AUTO_CREATE | Context.BIND_INCLUDE_CAPABILITIES)) {
+                mErrorHandler.onError(ErrorHandler.ErrorType.HOST_INCOMPATIBLE,
+                        new Exception("Cannot bind to the renderer host with intent: "
+                                + rendererIntent));
+            }
+        } else if (resolveInfoList.isEmpty()) {
+            mErrorHandler.onError(ErrorHandler.ErrorType.HOST_NOT_FOUND, new Exception("No "
+                    + "handlers found for intent: " + rendererIntent));
+        } else {
+            StringBuilder logMessage =
+                    new StringBuilder("Multiple hosts found, only one is allowed");
+            for (ResolveInfo resolveInfo : resolveInfoList) {
+                logMessage.append(
+                        String.format("\nFound host %s", resolveInfo.serviceInfo.packageName));
+            }
+            mErrorHandler.onError(ErrorHandler.ErrorType.MULTIPLE_HOSTS,
+                    new Exception(logMessage.toString()));
+        }
+    }
+
+    /** Closes the connection to the connected {@code rendererService} if any. */
+    void unbind() {
+        if (mRendererService == null) {
+            return;
+        }
+        try {
+            mRendererService.terminate(requireNonNull(mServiceComponentName));
+        } catch (RemoteException e) {
+            // We are already unbinding (maybe because the host has already cut the connection)
+            // Let's not log more errors unnecessarily.
+        }
+
+        Log.i(LogTags.TAG, "Unbinding from " + mServiceComponentName);
+        mContext.unbindService(mServiceConnectionImpl);
+        mRendererService = null;
+    }
+
+    /**
+     * Initializes the {@code rendererService} for the current {@code carIAppActivity},
+     * {@code serviceComponentName} and {@code displayId}.
+     */
+    void initializeService() {
+        ICarAppActivity carAppActivity = requireNonNull(mICarAppActivity);
+        IRendererService rendererService = requireNonNull(mRendererService);
+        ComponentName serviceComponentName = requireNonNull(mServiceComponentName);
+
+        Boolean success = mServiceDispatcher.fetch(false,
+                () -> rendererService.initialize(carAppActivity,
+                        serviceComponentName, mDisplayId));
+        if (success == null || !success) {
+            mErrorHandler.onError(ErrorHandler.ErrorType.HOST_ERROR,
+                    new Exception("Cannot create renderer for" + serviceComponentName));
+            return;
+        }
+        updateIntent();
+    }
+
+    /**
+     * Updates the activity intent for the connected {@code rendererService}.
+     */
+    private void updateIntent() {
+        ComponentName serviceComponentName = requireNonNull(mServiceComponentName);
+        Intent intent = requireNonNull(mIntent);
+
+        IRendererService service = mRendererService;
+        if (service == null) {
+            mErrorHandler.onError(ErrorHandler.ErrorType.CLIENT_SIDE_ERROR,
+                    new Exception("Service dispatcher is not connected"));
+            return;
+        }
+
+        Boolean success = mServiceDispatcher.fetch(false, () ->
+                service.onNewIntent(intent, serviceComponentName, mDisplayId));
+        if (success == null || !success) {
+            mErrorHandler.onError(ErrorHandler.ErrorType.HOST_ERROR, new Exception("Renderer "
+                    + "cannot handle the intent: " + intent));
+        }
+    }
+}
diff --git a/car/app/app-activity/src/main/java/androidx/car/app/activity/ServiceDispatcher.java b/car/app/app-activity/src/main/java/androidx/car/app/activity/ServiceDispatcher.java
index a77a3e5..83c575f 100644
--- a/car/app/app-activity/src/main/java/androidx/car/app/activity/ServiceDispatcher.java
+++ b/car/app/app-activity/src/main/java/androidx/car/app/activity/ServiceDispatcher.java
@@ -23,6 +23,7 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.annotation.RestrictTo;
+import androidx.annotation.VisibleForTesting;
 import androidx.car.app.activity.renderer.IRendererService;
 import androidx.car.app.serialization.BundlerException;
 
@@ -33,9 +34,15 @@
  */
 @RestrictTo(LIBRARY)
 public class ServiceDispatcher {
+
+    /** An interface for monitoring the binding state of a service connection. */
+    public interface OnBindingListener {
+        /** Returns true if the service connection is bound. */
+        boolean isBound();
+    }
+
     private final ErrorHandler mErrorHandler;
-    @Nullable
-    private IRendererService mRendererService;
+    private OnBindingListener mOnBindingListener;
 
     /** A one way call to the service */
     public interface OneWayCall {
@@ -56,28 +63,15 @@
         T invoke() throws RemoteException, BundlerException;
     }
 
-    public ServiceDispatcher(@NonNull ErrorHandler errorHandler) {
+    public ServiceDispatcher(@NonNull ErrorHandler errorHandler,
+            @NonNull OnBindingListener onBindingListener) {
         mErrorHandler = errorHandler;
+        mOnBindingListener = onBindingListener;
     }
 
-    /**
-     * Updates the bound service reference
-     *
-     * @param rendererService bound service or {@code null} if the service is not bound.
-     */
-    public void setRendererService(@Nullable IRendererService rendererService) {
-        mRendererService = rendererService;
-    }
-
-    /** Returns the bound service, or null if the service is not bound */
-    @Nullable
-    public IRendererService getRendererService() {
-        return mRendererService;
-    }
-
-    /** Returns true if the service is currently bound and able to receive messages */
-    public boolean isBound() {
-        return mRendererService != null;
+    @VisibleForTesting
+    public void setOnBindingListener(@NonNull OnBindingListener onBindingListener) {
+        mOnBindingListener = onBindingListener;
     }
 
     /** Dispatches the given {@link OneWayCall}. This is a non-blocking call. */
@@ -99,7 +93,7 @@
     // TODO(b/184697399): Remove two-way calls as these are blocking.
     @Nullable
     public <T> T fetch(@Nullable T fallbackValue, @NonNull ReturnCall<T> call) {
-        if (mRendererService == null) {
+        if (!mOnBindingListener.isBound()) {
             // Avoid dispatching messages if we are not bound to the service
             return fallbackValue;
         }
diff --git a/car/app/app-activity/src/main/res/layout/activity_template.xml b/car/app/app-activity/src/main/res/layout/activity_template.xml
index 09c432f..7054403 100644
--- a/car/app/app-activity/src/main/res/layout/activity_template.xml
+++ b/car/app/app-activity/src/main/res/layout/activity_template.xml
@@ -1,6 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
 <merge xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:app="http://schemas.android.com/apk/res-auto"
     android:id="@+id/fragment_container">
 
     <androidx.car.app.activity.renderer.surface.TemplateSurfaceView
@@ -11,6 +10,7 @@
         android:focusableInTouchMode="true" />
 
     <LinearLayout
+        android:background="#000000"
         android:id="@+id/error_message_container"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
diff --git a/car/app/app-activity/src/test/java/androidx/car/app/activity/CarAppActivityTest.java b/car/app/app-activity/src/test/java/androidx/car/app/activity/CarAppActivityTest.java
index 8cb2e32..7ffef3b 100644
--- a/car/app/app-activity/src/test/java/androidx/car/app/activity/CarAppActivityTest.java
+++ b/car/app/app-activity/src/test/java/androidx/car/app/activity/CarAppActivityTest.java
@@ -239,8 +239,11 @@
                 CarAppActivity.class)) {
             scenario.onActivity(activity -> {
                 try {
-                    ServiceConnection serviceConnection = spy(activity.getServiceConnection());
-                    activity.setServiceConnection(serviceConnection);
+                    ServiceConnectionManager serviceConnectionManager =
+                            activity.mViewModel.getServiceConnectionManager();
+                    ServiceConnection serviceConnection =
+                            spy(serviceConnectionManager.getServiceConnection());
+                    serviceConnectionManager.setServiceConnection(serviceConnection);
 
                     // Destroy activity to force unbind.
                     scenario.moveToState(Lifecycle.State.DESTROYED);
@@ -251,7 +254,7 @@
                     // Verify service connection is closed.
                     verify(serviceConnection, times(1)).onServiceDisconnected(
                             mRendererComponent);
-                    assertThat(activity.mServiceDispatcher.isBound()).isFalse();
+                    assertThat(serviceConnectionManager.isBound()).isFalse();
                 } catch (RemoteException e) {
                     fail(Log.getStackTraceString(e));
                 }
diff --git a/car/app/app-activity/src/test/java/androidx/car/app/activity/CarAppViewModelFactoryTest.java b/car/app/app-activity/src/test/java/androidx/car/app/activity/CarAppViewModelFactoryTest.java
new file mode 100644
index 0000000..7650a2d
--- /dev/null
+++ b/car/app/app-activity/src/test/java/androidx/car/app/activity/CarAppViewModelFactoryTest.java
@@ -0,0 +1,75 @@
+/*
+ * 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.car.app.activity;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.app.Application;
+import android.content.ComponentName;
+
+import androidx.test.core.app.ApplicationProvider;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.internal.DoNotInstrument;
+
+/** Tests for {@link CarAppViewModelFactory} */
+@RunWith(RobolectricTestRunner.class)
+@DoNotInstrument
+public class CarAppViewModelFactoryTest {
+    private static final ComponentName TEST_COMPONENT_NAME_1 = new ComponentName(
+            ApplicationProvider.getApplicationContext(), "Class1");
+    private static final ComponentName TEST_COMPONENT_NAME_2 = new ComponentName(
+            ApplicationProvider.getApplicationContext(), "Class2");
+
+    private final Application mApplication = ApplicationProvider.getApplicationContext();
+
+    @Test
+    public void getInstance_sameKey_returnsSame() {
+        CarAppViewModelFactory factory1 = CarAppViewModelFactory.getInstance(mApplication,
+                TEST_COMPONENT_NAME_1);
+
+        CarAppViewModelFactory factory2 = CarAppViewModelFactory.getInstance(mApplication,
+                TEST_COMPONENT_NAME_1);
+
+        assertThat(factory1).isEqualTo(factory2);
+    }
+
+    @Test
+    public void getInstance_differentKeys_returnsDifferent() {
+        CarAppViewModelFactory factory1 = CarAppViewModelFactory.getInstance(mApplication,
+                TEST_COMPONENT_NAME_1);
+
+        CarAppViewModelFactory factory2 = CarAppViewModelFactory.getInstance(mApplication,
+                TEST_COMPONENT_NAME_2);
+
+        assertThat(factory1).isNotEqualTo(factory2);
+    }
+
+    @Test
+    public void create_correctComponentName() {
+        CarAppViewModelFactory factory = CarAppViewModelFactory.getInstance(mApplication,
+                TEST_COMPONENT_NAME_1);
+
+        CarAppViewModel viewModel = factory.create(CarAppViewModel.class);
+
+        assertThat(viewModel).isNotNull();
+        assertThat(viewModel.getServiceConnectionManager().getServiceComponentName())
+                .isEqualTo(TEST_COMPONENT_NAME_1);
+    }
+}
diff --git a/car/app/app-activity/src/test/java/androidx/car/app/activity/CarAppViewModelTest.java b/car/app/app-activity/src/test/java/androidx/car/app/activity/CarAppViewModelTest.java
new file mode 100644
index 0000000..9eda009
--- /dev/null
+++ b/car/app/app-activity/src/test/java/androidx/car/app/activity/CarAppViewModelTest.java
@@ -0,0 +1,96 @@
+/*
+ * 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.car.app.activity;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+
+import android.app.Application;
+import android.content.ComponentName;
+import android.content.Intent;
+
+import androidx.car.app.activity.renderer.ICarAppActivity;
+import androidx.test.core.app.ApplicationProvider;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.internal.DoNotInstrument;
+
+/** Tests for {@link CarAppViewModel} */
+@RunWith(RobolectricTestRunner.class)
+@DoNotInstrument
+public class CarAppViewModelTest {
+    private static final ComponentName TEST_COMPONENT_NAME = new ComponentName(
+            ApplicationProvider.getApplicationContext(), "Class1");
+    private static final int TEST_DISPLAY_ID = 123;
+    private static final Intent TEST_INTENT = new Intent("TestAction");
+
+    private final Application mApplication = ApplicationProvider.getApplicationContext();
+    private CarAppViewModel mCarAppViewModel;
+    private ICarAppActivity mICarAppActivity;
+
+    @Before
+    public void setUp() {
+        mCarAppViewModel = new CarAppViewModel(mApplication, TEST_COMPONENT_NAME);
+        mICarAppActivity = mock(ICarAppActivity.class);
+    }
+
+    @Test
+    public void testSetup() {
+        assertThat(mCarAppViewModel.getServiceConnectionManager()).isNotNull();
+        assertThat(mCarAppViewModel.getServiceConnectionManager().getServiceComponentName())
+                .isEqualTo(TEST_COMPONENT_NAME);
+        assertThat(mCarAppViewModel.getServiceDispatcher()).isNotNull();
+    }
+
+    @Test
+    public void testBind_serviceConnectionManager_invoke() {
+        ServiceConnectionManager serviceConnectionManager = mock(ServiceConnectionManager.class);
+        mCarAppViewModel.setServiceConnectionManager(serviceConnectionManager);
+        mCarAppViewModel.bind(TEST_INTENT, mICarAppActivity, TEST_DISPLAY_ID);
+
+        verify(serviceConnectionManager).bind(TEST_INTENT, mICarAppActivity, TEST_DISPLAY_ID);
+    }
+
+    @Test
+    public void testUnbind_serviceConnectionManager_invoke() {
+        ServiceConnectionManager serviceConnectionManager = mock(ServiceConnectionManager.class);
+        mCarAppViewModel.setServiceConnectionManager(serviceConnectionManager);
+        mCarAppViewModel.unbind();
+
+        verify(serviceConnectionManager).unbind();
+    }
+
+    @Test
+    public void testErrorHandler_capturesErrorEvent() {
+        ErrorHandler.ErrorType errorType = ErrorHandler.ErrorType.HOST_ERROR;
+        Throwable exception = new IllegalStateException();
+
+        ErrorHandler errorHandler =
+                mCarAppViewModel.getServiceConnectionManager().getErrorHandler();
+        errorHandler.onError(errorType, exception);
+
+        CarAppViewModel.ErrorEvent errorEvent = mCarAppViewModel.getErrorEvent().getValue();
+        assertThat(errorEvent).isNotNull();
+        assertThat(errorEvent.getErrorType()).isEqualTo(errorType);
+        assertThat(errorEvent.getException()).isEqualTo(exception);
+    }
+}
diff --git a/car/app/app-activity/src/test/java/androidx/car/app/activity/ServiceConnectionManagerTest.java b/car/app/app-activity/src/test/java/androidx/car/app/activity/ServiceConnectionManagerTest.java
new file mode 100644
index 0000000..82bc8c1
--- /dev/null
+++ b/car/app/app-activity/src/test/java/androidx/car/app/activity/ServiceConnectionManagerTest.java
@@ -0,0 +1,289 @@
+/*
+ * 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.car.app.activity;
+
+import static android.os.Looper.getMainLooper;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import static org.robolectric.Shadows.shadowOf;
+
+import android.app.Application;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.os.RemoteException;
+import android.util.Log;
+
+import androidx.car.app.activity.renderer.ICarAppActivity;
+import androidx.car.app.activity.renderer.IRendererService;
+import androidx.test.core.app.ApplicationProvider;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.internal.DoNotInstrument;
+import org.robolectric.shadows.ShadowApplication;
+import org.robolectric.shadows.ShadowPackageManager;
+
+/** Tests for {@link ServiceConnectionManager}. */
+@RunWith(RobolectricTestRunner.class)
+@DoNotInstrument
+public class ServiceConnectionManagerTest {
+
+    private static final int TEST_DISPLAY_ID = 123;
+    private static final Intent TEST_INTENT = new Intent("Test");
+
+    private final ComponentName mRendererComponent = new ComponentName(
+            ApplicationProvider.getApplicationContext(), getClass().getName());
+
+    private final String mFakeCarAppServiceClass = "com.fake.FakeCarAppService";
+    private final ComponentName mFakeCarAppServiceComponent = new ComponentName(
+            ApplicationProvider.getApplicationContext(), mFakeCarAppServiceClass);
+    private final IRendererService mRenderService = mock(IRendererService.class);
+    private final RenderServiceDelegate mRenderServiceDelegate =
+            new RenderServiceDelegate(mRenderService);
+
+    private final ErrorHandler mErrorHandler = mock(ErrorHandler.class);
+    private final ServiceConnectionManager mServiceConnectionManager =
+            new ServiceConnectionManager(ApplicationProvider.getApplicationContext(),
+                    mFakeCarAppServiceComponent, mErrorHandler);
+
+    private void setupCarAppActivityForTesting() {
+        try {
+            Application app = ApplicationProvider.getApplicationContext();
+
+            PackageManager packageManager = app.getPackageManager();
+            ShadowPackageManager spm = shadowOf(packageManager);
+
+            // Register fake renderer service which will be simulated by {@code mRenderService}.
+            spm.addServiceIfNotPresent(mRendererComponent);
+            spm.addIntentFilterForService(mRendererComponent,
+                    new IntentFilter(CarAppActivity.ACTION_RENDER));
+
+            when(mRenderService.initialize(any(ICarAppActivity.class),
+                    any(ComponentName.class),
+                    anyInt())).thenReturn(true);
+            when(mRenderService.onNewIntent(any(Intent.class), any(ComponentName.class),
+                    anyInt())).thenReturn(true);
+
+            ShadowApplication sa = shadowOf(app);
+            sa.setComponentNameAndServiceForBindService(mRendererComponent, mRenderServiceDelegate);
+        } catch (Exception e) {
+            fail(Log.getStackTraceString(e));
+        }
+    }
+
+    @Test
+    public void testIsBound_serviceNotNull_returnsTrue() {
+        mServiceConnectionManager.setRendererService(mock(IRendererService.class));
+        assertThat(mServiceConnectionManager.isBound()).isTrue();
+    }
+
+    @Test
+    public void testIsBound_serviceIsNull_returnsFalse() {
+        mServiceConnectionManager.setRendererService(null);
+        assertThat(mServiceConnectionManager.isBound()).isFalse();
+    }
+
+    @Test
+    public void testBind_unbound_bindsToRenderer() {
+        setupCarAppActivityForTesting();
+        ICarAppActivity iCarAppActivity = mock(ICarAppActivity.class);
+
+        mServiceConnectionManager.bind(TEST_INTENT, iCarAppActivity, TEST_DISPLAY_ID);
+        shadowOf(getMainLooper()).idle();
+        try {
+            verify(mErrorHandler, never()).onError(any(), any());
+            verify(mRenderService).initialize(iCarAppActivity, mFakeCarAppServiceComponent,
+                    TEST_DISPLAY_ID);
+            verify(mRenderService).onNewIntent(TEST_INTENT, mFakeCarAppServiceComponent,
+                    TEST_DISPLAY_ID);
+        } catch (RemoteException e) {
+            fail(Log.getStackTraceString(e));
+        }
+        assertThat(mServiceConnectionManager.isBound()).isTrue();
+    }
+
+    @Test
+    public void testBind_bound_doesNotRebound() {
+        setupCarAppActivityForTesting();
+        ICarAppActivity iCarAppActivity = mock(ICarAppActivity.class);
+
+        IRendererService renderService = createMockRendererService();
+        mServiceConnectionManager.setRendererService(renderService);
+        mServiceConnectionManager.bind(TEST_INTENT, iCarAppActivity, TEST_DISPLAY_ID);
+        shadowOf(getMainLooper()).idle();
+        try {
+            verify(mErrorHandler, never()).onError(any(), any());
+            verify(mRenderService, never()).initialize(iCarAppActivity, mFakeCarAppServiceComponent,
+                    TEST_DISPLAY_ID);
+            verify(mRenderService, never()).onNewIntent(TEST_INTENT, mFakeCarAppServiceComponent,
+                    TEST_DISPLAY_ID);
+        } catch (RemoteException e) {
+            fail(Log.getStackTraceString(e));
+        }
+        assertThat(mServiceConnectionManager.isBound()).isTrue();
+    }
+
+    @Test
+    public void testBind_bound_initializes() {
+        setupCarAppActivityForTesting();
+        ICarAppActivity iCarAppActivity = mock(ICarAppActivity.class);
+
+        IRendererService renderService = createMockRendererService();
+
+        mServiceConnectionManager.setRendererService(renderService);
+        mServiceConnectionManager.bind(TEST_INTENT, iCarAppActivity, TEST_DISPLAY_ID);
+        shadowOf(getMainLooper()).idle();
+
+        try {
+            verify(mErrorHandler, never()).onError(any(), any());
+            verify(renderService).initialize(iCarAppActivity, mFakeCarAppServiceComponent,
+                    TEST_DISPLAY_ID);
+            verify(renderService).onNewIntent(TEST_INTENT, mFakeCarAppServiceComponent,
+                    TEST_DISPLAY_ID);
+        } catch (RemoteException e) {
+            fail(Log.getStackTraceString(e));
+        }
+        assertThat(mServiceConnectionManager.isBound()).isTrue();
+    }
+
+    @Test
+    public void testBind_bound_noRebound() {
+        setupCarAppActivityForTesting();
+        ICarAppActivity iCarAppActivity = mock(ICarAppActivity.class);
+        IRendererService renderService = createMockRendererService();
+
+        mServiceConnectionManager.setRendererService(renderService);
+        mServiceConnectionManager.bind(TEST_INTENT, iCarAppActivity, TEST_DISPLAY_ID);
+        shadowOf(getMainLooper()).idle();
+        try {
+            verify(mErrorHandler, never()).onError(any(), any());
+            verify(mRenderService, never()).initialize(iCarAppActivity, mFakeCarAppServiceComponent,
+                    TEST_DISPLAY_ID);
+            verify(mRenderService, never()).onNewIntent(TEST_INTENT, mFakeCarAppServiceComponent,
+                    TEST_DISPLAY_ID);
+        } catch (RemoteException e) {
+            fail(Log.getStackTraceString(e));
+        }
+        assertThat(mServiceConnectionManager.isBound()).isTrue();
+    }
+
+    @Test
+    public void testBind_unbound_failure() {
+        setupCarAppActivityForTesting();
+        ICarAppActivity iCarAppActivity = mock(ICarAppActivity.class);
+
+        try {
+            when(mRenderService.initialize(any(ICarAppActivity.class),
+                    any(ComponentName.class),
+                    anyInt())).thenReturn(false);
+        } catch (RemoteException e) {
+            fail(Log.getStackTraceString(e));
+        }
+
+        mServiceConnectionManager.bind(TEST_INTENT, iCarAppActivity, TEST_DISPLAY_ID);
+        shadowOf(getMainLooper()).idle();
+
+        verify(mErrorHandler).onError(eq(ErrorHandler.ErrorType.HOST_ERROR), any());
+    }
+
+    @Test
+    public void testUnBind_bound_terminate() {
+        setupCarAppActivityForTesting();
+
+        IRendererService renderService = mock(IRendererService.class);
+        mServiceConnectionManager.setRendererService(renderService);
+        mServiceConnectionManager.unbind();
+        shadowOf(getMainLooper()).idle();
+
+        try {
+            verify(renderService).terminate(mFakeCarAppServiceComponent);
+        } catch (RemoteException e) {
+            fail(Log.getStackTraceString(e));
+        }
+
+        assertThat(mServiceConnectionManager.isBound()).isFalse();
+    }
+
+    @Test
+    public void testUnBind_unbound_doNothing() {
+        setupCarAppActivityForTesting();
+
+        mServiceConnectionManager.unbind();
+        shadowOf(getMainLooper()).idle();
+
+        try {
+            verify(mRenderService, never()).terminate(mFakeCarAppServiceComponent);
+        } catch (RemoteException e) {
+            fail(Log.getStackTraceString(e));
+        }
+
+        assertThat(mServiceConnectionManager.isBound()).isFalse();
+    }
+
+    // Use delegate to forward events to a mock. Mockito interceptor is not maintained on
+    // top-level IBinder after call to IRenderService.Stub.asInterface() in CarAppActivity.
+    private static class RenderServiceDelegate extends IRendererService.Stub {
+        private final IRendererService mService;
+
+        RenderServiceDelegate(IRendererService service) {
+            mService = service;
+        }
+
+        @Override
+        public boolean initialize(ICarAppActivity carActivity, ComponentName serviceName,
+                int displayId) throws RemoteException {
+            return mService.initialize(carActivity, serviceName, displayId);
+        }
+
+        @Override
+        public boolean onNewIntent(Intent intent, ComponentName serviceName, int displayId)
+                throws RemoteException {
+            return mService.onNewIntent(intent, serviceName, displayId);
+        }
+
+        @Override
+        public void terminate(ComponentName serviceName) throws RemoteException {
+            mService.terminate(serviceName);
+        }
+    }
+
+    private IRendererService createMockRendererService() {
+        IRendererService renderService = mock(IRendererService.class);
+        try {
+            when(renderService.initialize(any(ICarAppActivity.class),
+                    any(ComponentName.class),
+                    anyInt())).thenReturn(true);
+            when(renderService.onNewIntent(any(Intent.class), any(ComponentName.class),
+                    anyInt())).thenReturn(true);
+        } catch (RemoteException e) {
+            fail(Log.getStackTraceString(e));
+        }
+        return renderService;
+    }
+}
diff --git a/car/app/app-activity/src/test/java/androidx/car/app/activity/ServiceDispatcherTest.java b/car/app/app-activity/src/test/java/androidx/car/app/activity/ServiceDispatcherTest.java
index bbb8137..ea22617 100644
--- a/car/app/app-activity/src/test/java/androidx/car/app/activity/ServiceDispatcherTest.java
+++ b/car/app/app-activity/src/test/java/androidx/car/app/activity/ServiceDispatcherTest.java
@@ -48,19 +48,7 @@
     @Before
     public void setup() {
         mErrorHandler = mock(ErrorHandler.class);
-        mServiceDispatcher = new ServiceDispatcher(mErrorHandler);
-    }
-
-    @Test
-    public void isBound_serviceNotNull_returnsTrue() {
-        mServiceDispatcher.setRendererService(mock(IRendererService.class));
-        assertThat(mServiceDispatcher.isBound()).isTrue();
-    }
-
-    @Test
-    public void isBound_serviceIsNull_returnsFalse() {
-        mServiceDispatcher.setRendererService(null);
-        assertThat(mServiceDispatcher.isBound()).isFalse();
+        mServiceDispatcher = new ServiceDispatcher(mErrorHandler, () -> false);
     }
 
     @Test
@@ -73,7 +61,7 @@
         ServiceDispatcher.OneWayCall call = () -> rendererService.onNewIntent(intent,
                 componentName, 0);
 
-        mServiceDispatcher.setRendererService(rendererService);
+        mServiceDispatcher.setOnBindingListener(() -> true);
         mServiceDispatcher.dispatch(call);
 
         verify(rendererService, times(1)).onNewIntent(intent, componentName, 0);
@@ -83,7 +71,7 @@
     public void dispatch_serviceNotBound_notInvoked() throws BundlerException, RemoteException {
         ServiceDispatcher.OneWayCall call = mock(ServiceDispatcher.OneWayCall.class);
 
-        mServiceDispatcher.setRendererService(null);
+        mServiceDispatcher.setOnBindingListener(() -> false);
         mServiceDispatcher.dispatch(call);
 
         verify(call, never()).invoke();
@@ -95,7 +83,7 @@
             throw new RemoteException();
         };
 
-        mServiceDispatcher.setRendererService(mock(IRendererService.class));
+        mServiceDispatcher.setOnBindingListener(() -> true);
         mServiceDispatcher.dispatch(call);
 
         verify(mErrorHandler, times(1))
@@ -104,10 +92,9 @@
 
     @Test
     public void fetch_serviceBound_valueReturned() {
-        IRendererService rendererService = mock(IRendererService.class);
         ServiceDispatcher.ReturnCall<Integer> call = () -> 123;
 
-        mServiceDispatcher.setRendererService(rendererService);
+        mServiceDispatcher.setOnBindingListener(() -> true);
         Integer result = mServiceDispatcher.fetch(234, call);
 
         assertThat(result).isEqualTo(123);
@@ -119,7 +106,7 @@
             throws BundlerException, RemoteException {
         ServiceDispatcher.ReturnCall<Integer> call = mock(ServiceDispatcher.ReturnCall.class);
 
-        mServiceDispatcher.setRendererService(null);
+        mServiceDispatcher.setOnBindingListener(() -> false);
         Integer result = mServiceDispatcher.fetch(234, call);
 
         verify(call, never()).invoke();
@@ -132,7 +119,7 @@
             throw new RemoteException();
         };
 
-        mServiceDispatcher.setRendererService(mock(IRendererService.class));
+        mServiceDispatcher.setOnBindingListener(() -> true);
         Integer result = mServiceDispatcher.fetch(234, call);
 
         verify(mErrorHandler, times(1))
diff --git a/car/app/app-samples/helloworld/automotive/lint-baseline.xml b/car/app/app-samples/helloworld/automotive/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/car/app/app-samples/helloworld/automotive/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/car/app/app-samples/helloworld/automotive/src/main/AndroidManifest.xml b/car/app/app-samples/helloworld/automotive/src/main/AndroidManifest.xml
index 023f647..431920d 100644
--- a/car/app/app-samples/helloworld/automotive/src/main/AndroidManifest.xml
+++ b/car/app/app-samples/helloworld/automotive/src/main/AndroidManifest.xml
@@ -24,7 +24,8 @@
 
     <application
       android:label="@string/app_name"
-      android:icon="@drawable/ic_launcher">
+      android:icon="@drawable/ic_launcher"
+      android:extractNativeLibs="false">
 
         <meta-data
             android:name="com.android.automotive"
diff --git a/car/app/app-samples/helloworld/mobile/lint-baseline.xml b/car/app/app-samples/helloworld/mobile/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/car/app/app-samples/helloworld/mobile/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/car/app/app-samples/helloworld/mobile/src/main/AndroidManifest.xml b/car/app/app-samples/helloworld/mobile/src/main/AndroidManifest.xml
index 4ad83c7..7a36a70 100644
--- a/car/app/app-samples/helloworld/mobile/src/main/AndroidManifest.xml
+++ b/car/app/app-samples/helloworld/mobile/src/main/AndroidManifest.xml
@@ -21,7 +21,8 @@
     android:versionName="1.0">
   <application
       android:label="@string/app_name"
-      android:icon="@drawable/ic_launcher">
+      android:icon="@drawable/ic_launcher"
+      android:extractNativeLibs="false">
     <meta-data android:name="com.google.android.gms.car.application"
         android:resource="@xml/automotive_app_desc"
         tools:ignore="MetadataTagInsideApplicationTag" />
diff --git a/car/app/app-samples/navigation/automotive/src/main/AndroidManifest.xml b/car/app/app-samples/navigation/automotive/src/main/AndroidManifest.xml
index 97f5250..be7c611 100644
--- a/car/app/app-samples/navigation/automotive/src/main/AndroidManifest.xml
+++ b/car/app/app-samples/navigation/automotive/src/main/AndroidManifest.xml
@@ -32,7 +32,8 @@
 
   <application
     android:label="@string/app_name"
-    android:icon="@drawable/ic_launcher">
+    android:icon="@drawable/ic_launcher"
+    android:extractNativeLibs="false">
 
     <meta-data
         android:name="com.android.automotive"
diff --git a/car/app/app-samples/navigation/common/src/main/java/androidx/car/app/sample/navigation/common/car/NavigationScreen.java b/car/app/app-samples/navigation/common/src/main/java/androidx/car/app/sample/navigation/common/car/NavigationScreen.java
index 49b6deb..00def13 100644
--- a/car/app/app-samples/navigation/common/src/main/java/androidx/car/app/sample/navigation/common/car/NavigationScreen.java
+++ b/car/app/app-samples/navigation/common/src/main/java/androidx/car/app/sample/navigation/common/car/NavigationScreen.java
@@ -19,6 +19,7 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.car.app.CarContext;
+import androidx.car.app.CarToast;
 import androidx.car.app.Screen;
 import androidx.car.app.model.Action;
 import androidx.car.app.model.ActionStrip;
@@ -42,6 +43,14 @@
 
 /** Simple demo of how to present a trip on the routing screen. */
 public final class NavigationScreen extends Screen {
+    /** Invalid zoom focal point value, used for the zoom buttons. */
+    private static final float INVALID_FOCAL_POINT_VAL = -1f;
+
+    /** Zoom-in scale factor, used for the zoom-in button. */
+    private static final float ZOOM_IN_BUTTON_SCALE_FACTOR = 1.1f;
+
+    /** Zoom-out scale factor, used for the zoom-out button. */
+    private static final float ZOOM_OUT_BUTTON_SCALE_FACTOR = 0.9f;
 
     /** A listener for navigation start and stop signals. */
     public interface Listener {
@@ -80,6 +89,8 @@
     @Nullable
     CarIcon mJunctionImage;
 
+    private boolean mIsInPanMode;
+
     public NavigationScreen(
             @NonNull CarContext carContext,
             @NonNull Action settingsAction,
@@ -125,6 +136,7 @@
         NavigationTemplate.Builder builder = new NavigationTemplate.Builder();
         builder.setBackgroundColor(CarColor.SECONDARY);
 
+        // Set the action strip.
         ActionStrip.Builder actionStripBuilder = new ActionStrip.Builder();
         actionStripBuilder.addAction(mSettingsAction);
         if (mIsNavigating) {
@@ -152,6 +164,59 @@
         }
         builder.setActionStrip(actionStripBuilder.build());
 
+        // Set the map action strip with the pan and zoom buttons.
+        CarIcon.Builder panIconBuilder = new CarIcon.Builder(
+                IconCompat.createWithResource(
+                        getCarContext(),
+                        R.drawable.ic_pan_24));
+        if (mIsInPanMode) {
+            panIconBuilder.setTint(CarColor.BLUE);
+        }
+
+        builder.setMapActionStrip(new ActionStrip.Builder()
+                .addAction(new Action.Builder(Action.PAN)
+                        .setIcon(panIconBuilder.build())
+                        .build())
+                .addAction(
+                        new Action.Builder()
+                                .setIcon(
+                                        new CarIcon.Builder(
+                                                IconCompat.createWithResource(
+                                                        getCarContext(),
+                                                        R.drawable.ic_zoom_out_24))
+                                                .build())
+                                .setOnClickListener(
+                                        () -> mSurfaceRenderer.handleScale(INVALID_FOCAL_POINT_VAL,
+                                                INVALID_FOCAL_POINT_VAL,
+                                                ZOOM_OUT_BUTTON_SCALE_FACTOR))
+                                .build())
+                .addAction(
+                        new Action.Builder()
+                                .setIcon(
+                                        new CarIcon.Builder(
+                                                IconCompat.createWithResource(
+                                                        getCarContext(),
+                                                        R.drawable.ic_zoom_in_24))
+                                                .build())
+                                .setOnClickListener(
+                                        () -> mSurfaceRenderer.handleScale(INVALID_FOCAL_POINT_VAL,
+                                                INVALID_FOCAL_POINT_VAL,
+                                                ZOOM_IN_BUTTON_SCALE_FACTOR))
+                                .build())
+                .build());
+
+        // When the user enters the pan mode, remind the user that they can exit the pan mode by
+        // pressing the select button again.
+        builder.setPanModeListener(isInPanMode -> {
+            if (isInPanMode) {
+                CarToast.makeText(getCarContext(),
+                        "Press Select to exit the pan mode",
+                        CarToast.LENGTH_LONG).show();
+            }
+            mIsInPanMode = isInPanMode;
+            invalidate();
+        });
+
         if (mIsNavigating) {
             if (mDestinationTravelEstimate != null) {
                 builder.setDestinationTravelEstimate(mDestinationTravelEstimate);
diff --git a/car/app/app-samples/navigation/common/src/main/java/androidx/car/app/sample/navigation/common/car/SurfaceRenderer.java b/car/app/app-samples/navigation/common/src/main/java/androidx/car/app/sample/navigation/common/car/SurfaceRenderer.java
index bc9503b..dd92668 100644
--- a/car/app/app-samples/navigation/common/src/main/java/androidx/car/app/sample/navigation/common/car/SurfaceRenderer.java
+++ b/car/app/app-samples/navigation/common/src/main/java/androidx/car/app/sample/navigation/common/car/SurfaceRenderer.java
@@ -16,12 +16,16 @@
 
 package androidx.car.app.sample.navigation.common.car;
 
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
 import android.graphics.Canvas;
 import android.graphics.Color;
+import android.graphics.Matrix;
 import android.graphics.Paint;
 import android.graphics.Paint.Align;
 import android.graphics.Paint.Style;
 import android.graphics.Rect;
+import android.graphics.RectF;
 import android.util.Log;
 import android.view.Surface;
 
@@ -31,6 +35,7 @@
 import androidx.car.app.CarContext;
 import androidx.car.app.SurfaceCallback;
 import androidx.car.app.SurfaceContainer;
+import androidx.car.app.sample.navigation.common.R;
 import androidx.lifecycle.DefaultLifecycleObserver;
 import androidx.lifecycle.Lifecycle;
 import androidx.lifecycle.LifecycleOwner;
@@ -39,6 +44,15 @@
 public final class SurfaceRenderer implements DefaultLifecycleObserver {
     private static final String TAG = "SurfaceRenderer";
 
+    /** The maximum scale factor of the background map. */
+    private static final float MAX_SCALE_FACTOR = 5f;
+
+    /** The minimum scale factor of the background map. */
+    private static final float MIN_SCALE_FACTOR = 0.5f;
+
+    /** The scale factor to apply when initializing the background map. */
+    private static final float MAP_ENLARGE_FACTOR = 5f;
+
     @Nullable
     Surface mSurface;
     @Nullable
@@ -57,7 +71,37 @@
     private int mActiveMarker;
     private String mLocationString = "unknown";
 
-    private final SurfaceCallback mSurfaceCallback =
+    /** The bitmap that contains the background map image. */
+    private final Bitmap mBackgroundMap;
+
+    /**
+     * The transformation matrix for the background map image, to reflect the result of the user's
+     * pan and zoom actions.
+     */
+    final Matrix mBackgroundMapMatrix = new Matrix();
+
+    /** The cumulative scale factor for the background map image. */
+    float mCumulativeScaleFactor = 1f;
+
+    /**
+     * The current horizontal center point of the background map, used to prevent the map from
+     * disappearing from pan actions.
+     */
+    float mBackgroundMapCenterX = 0f;
+
+    /**
+     * The current vertical center point of the background map, used to prevent the map from
+     * disappearing from pan actions.
+     */
+    float mBackgroundMapCenterY = 0f;
+
+    /**
+     * The original clip bounds of the background map, used to prevent the map from disappearing
+     * from pan actions.
+     */
+    Rect mBackgroundMapClipBounds = new Rect();
+
+    public final SurfaceCallback mSurfaceCallback =
             new SurfaceCallback() {
                 @Override
                 public void onSurfaceAvailable(@NonNull SurfaceContainer surfaceContainer) {
@@ -101,6 +145,28 @@
                         mSurface = null;
                     }
                 }
+
+                @Override
+                public void onScroll(float distanceX, float distanceY) {
+                    synchronized (SurfaceRenderer.this) {
+                        float newBackgroundCenterX = mBackgroundMapCenterX - distanceX;
+                        float newBackgroundCenterY = mBackgroundMapCenterY - distanceY;
+
+                        // If the map stays within the clip bounds, pan the map.
+                        if (mBackgroundMapClipBounds.contains((int) newBackgroundCenterX,
+                                (int) newBackgroundCenterY)) {
+                            mBackgroundMapCenterX = newBackgroundCenterX;
+                            mBackgroundMapCenterY = newBackgroundCenterY;
+                            mBackgroundMapMatrix.postTranslate(-distanceX, -distanceY);
+                            renderFrame();
+                        }
+                    }
+                }
+
+                @Override
+                public void onScale(float focusX, float focusY, float scaleFactor) {
+                    handleScale(focusX, focusY, scaleFactor);
+                }
             };
 
     public SurfaceRenderer(@NonNull CarContext carContext, @NonNull Lifecycle lifecycle) {
@@ -124,6 +190,8 @@
         mMarkerPaint.setStyle(Style.STROKE);
         mMarkerPaint.setStrokeWidth(3);
 
+        mBackgroundMap = BitmapFactory.decodeResource(carContext.getResources(),
+                R.drawable.map);
         lifecycle.addObserver(this);
     }
 
@@ -138,6 +206,34 @@
         renderFrame();
     }
 
+    /** Handles a map zoom-in and zoom-out events. */
+    public void handleScale(float focusX, float focusY, float scaleFactor) {
+        synchronized (this) {
+            float x = focusX;
+            float y = focusY;
+
+            Rect visibleArea = mVisibleArea;
+            if (visibleArea != null) {
+                // If a focal point value is negative, use the center point of the visible area.
+                if (x < 0) {
+                    x = visibleArea.centerX();
+                }
+                if (y < 0) {
+                    y = visibleArea.centerY();
+                }
+            }
+
+            // If the map stays between the maximum and minimum scale factors, scale the map.
+            float newCumulativeScaleFactor = mCumulativeScaleFactor * scaleFactor;
+            if (newCumulativeScaleFactor > MIN_SCALE_FACTOR
+                    && newCumulativeScaleFactor < MAX_SCALE_FACTOR) {
+                mCumulativeScaleFactor = newCumulativeScaleFactor;
+                mBackgroundMapMatrix.postScale(scaleFactor, scaleFactor, x, y);
+                renderFrame();
+            }
+        }
+    }
+
     /** Updates the markers drawn on the surface. */
     public void updateMarkerVisibility(boolean showMarkers, int numMarkers, int activeMarker) {
         mShowMarkers = showMarkers;
@@ -162,6 +258,31 @@
         // Clear the background.
         canvas.drawColor(mCarContext.isDarkMode() ? Color.DKGRAY : Color.LTGRAY);
 
+        // Initialize the background map.
+        if (mBackgroundMapMatrix.isIdentity()) {
+            // Enlarge the original image.
+            RectF backgroundRect = new RectF(0, 0, mBackgroundMap.getWidth(),
+                    mBackgroundMap.getHeight());
+            RectF scaledBackgroundRect = new RectF(0, 0,
+                    backgroundRect.width() * MAP_ENLARGE_FACTOR,
+                    backgroundRect.height() * MAP_ENLARGE_FACTOR);
+
+            // Initialize the cumulative scale factor and map center points.
+            mCumulativeScaleFactor = 1f;
+            mBackgroundMapCenterX = scaledBackgroundRect.centerX();
+            mBackgroundMapCenterY = scaledBackgroundRect.centerY();
+
+            // Move to the center of the enlarged map.
+            mBackgroundMapMatrix.setRectToRect(backgroundRect, scaledBackgroundRect,
+                    Matrix.ScaleToFit.FILL);
+            mBackgroundMapMatrix.postTranslate(
+                    -mBackgroundMapCenterX + canvas.getClipBounds().centerX(),
+                    -mBackgroundMapCenterY + canvas.getClipBounds().centerY());
+            scaledBackgroundRect.round(mBackgroundMapClipBounds);
+        }
+        canvas.drawBitmap(mBackgroundMap, mBackgroundMapMatrix, null);
+
+
         final int horizontalTextMargin = 10;
         final int verticalTextMarginFromTop = 20;
         final int verticalTextMarginFromBottom = 10;
diff --git a/car/app/app-samples/navigation/common/src/main/res/drawable/ic_pan_24.xml b/car/app/app-samples/navigation/common/src/main/res/drawable/ic_pan_24.xml
new file mode 100644
index 0000000..1dfeed1
--- /dev/null
+++ b/car/app/app-samples/navigation/common/src/main/res/drawable/ic_pan_24.xml
@@ -0,0 +1,28 @@
+<!--
+  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.
+  -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+  <path
+      android:fillColor="@android:color/white"
+      android:pathData="M15.54,5.54L13.77,7.3 12,5.54 10.23,7.3 8.46,5.54 12,2zM18.46,15.54l-1.76,-1.77L18.46,12l-1.76,-1.77 1.76,-1.77L22,12zM8.46,18.46l1.77,-1.76L12,18.46l1.77,-1.76 1.77,1.76L12,22zM5.54,8.46l1.76,1.77L5.54,12l1.76,1.77 -1.76,1.77L2,12z"/>
+  <path
+      android:fillColor="@android:color/white"
+      android:pathData="M12,12m-3,0a3,3 0,1 1,6 0a3,3 0,1 1,-6 0"/>
+</vector>
diff --git a/car/app/app-samples/navigation/common/src/main/res/drawable/ic_zoom_in_24.xml b/car/app/app-samples/navigation/common/src/main/res/drawable/ic_zoom_in_24.xml
new file mode 100644
index 0000000..36d3282
--- /dev/null
+++ b/car/app/app-samples/navigation/common/src/main/res/drawable/ic_zoom_in_24.xml
@@ -0,0 +1,25 @@
+<!--
+  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.
+  -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+  <path
+      android:fillColor="@android:color/white"
+      android:pathData="M19,13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/>
+</vector>
diff --git a/car/app/app-samples/navigation/common/src/main/res/drawable/ic_zoom_out_24.xml b/car/app/app-samples/navigation/common/src/main/res/drawable/ic_zoom_out_24.xml
new file mode 100644
index 0000000..089d265
--- /dev/null
+++ b/car/app/app-samples/navigation/common/src/main/res/drawable/ic_zoom_out_24.xml
@@ -0,0 +1,25 @@
+<!--
+  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.
+  -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+  <path
+      android:fillColor="@android:color/white"
+      android:pathData="M19,13H5v-2h14v2z"/>
+</vector>
diff --git a/car/app/app-samples/navigation/common/src/main/res/drawable/map.png b/car/app/app-samples/navigation/common/src/main/res/drawable/map.png
new file mode 100644
index 0000000..3cfca26
--- /dev/null
+++ b/car/app/app-samples/navigation/common/src/main/res/drawable/map.png
Binary files differ
diff --git a/car/app/app-samples/navigation/mobile/src/main/AndroidManifest.xml b/car/app/app-samples/navigation/mobile/src/main/AndroidManifest.xml
index d0e6d5b..08b4f1f 100644
--- a/car/app/app-samples/navigation/mobile/src/main/AndroidManifest.xml
+++ b/car/app/app-samples/navigation/mobile/src/main/AndroidManifest.xml
@@ -30,7 +30,8 @@
 
   <application
       android:label="@string/app_name"
-      android:icon="@drawable/ic_launcher">
+      android:icon="@drawable/ic_launcher"
+      android:extractNativeLibs="false">
 
     <activity
         android:name="androidx.car.app.sample.navigation.common.app.MainActivity"
diff --git a/car/app/app-samples/places/automotive/src/main/AndroidManifest.xml b/car/app/app-samples/places/automotive/src/main/AndroidManifest.xml
index 5a2330d..679d8e5 100644
--- a/car/app/app-samples/places/automotive/src/main/AndroidManifest.xml
+++ b/car/app/app-samples/places/automotive/src/main/AndroidManifest.xml
@@ -31,7 +31,8 @@
 
   <application
       android:label="@string/app_name"
-      android:icon="@drawable/ic_launcher">
+      android:icon="@drawable/ic_launcher"
+      android:extractNativeLibs="false">
 
     <meta-data
         android:name="com.android.automotive"
diff --git a/car/app/app-samples/places/common/lint-baseline.xml b/car/app/app-samples/places/common/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/car/app/app-samples/places/common/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/car/app/app-samples/places/common/src/main/java/androidx/car/app/sample/places/common/PlaceDetailsScreen.java b/car/app/app-samples/places/common/src/main/java/androidx/car/app/sample/places/common/PlaceDetailsScreen.java
index 082558e..493552f 100644
--- a/car/app/app-samples/places/common/src/main/java/androidx/car/app/sample/places/common/PlaceDetailsScreen.java
+++ b/car/app/app-samples/places/common/src/main/java/androidx/car/app/sample/places/common/PlaceDetailsScreen.java
@@ -16,6 +16,7 @@
 
 package androidx.car.app.sample.places.common;
 
+import static androidx.car.app.CarToast.LENGTH_LONG;
 import static androidx.car.app.sample.places.common.Executors.BACKGROUND_EXECUTOR;
 import static androidx.car.app.sample.places.common.Executors.UI_EXECUTOR;
 
@@ -31,6 +32,8 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.car.app.CarContext;
+import androidx.car.app.CarToast;
+import androidx.car.app.HostException;
 import androidx.car.app.Screen;
 import androidx.car.app.model.Action;
 import androidx.car.app.model.CarColor;
@@ -152,7 +155,16 @@
     private void onClickNavigate() {
         Uri uri = Uri.parse("geo:0,0?q=" + mPlace.getAddress(mGeocoder).getAddressLine(0));
         Intent intent = new Intent(CarContext.ACTION_NAVIGATE, uri);
-        getCarContext().startCarApp(intent);
+
+        try {
+            getCarContext().startCarApp(intent);
+        } catch (HostException e) {
+            CarToast.makeText(
+                    getCarContext(),
+                    "Failure starting navigation",
+                    LENGTH_LONG)
+                    .show();
+        }
     }
 
     private static List<CharSequence> getAddressLines(Address address) {
diff --git a/car/app/app-samples/places/mobile/src/main/AndroidManifest.xml b/car/app/app-samples/places/mobile/src/main/AndroidManifest.xml
index 4a95aff..62d9faf 100644
--- a/car/app/app-samples/places/mobile/src/main/AndroidManifest.xml
+++ b/car/app/app-samples/places/mobile/src/main/AndroidManifest.xml
@@ -29,7 +29,8 @@
 
   <application
       android:label="@string/app_name"
-      android:icon="@drawable/ic_launcher">
+      android:icon="@drawable/ic_launcher"
+      android:extractNativeLibs="false">
 
     <meta-data android:name="com.google.android.gms.car.application"
       android:resource="@xml/automotive_app_desc"
diff --git a/car/app/app-samples/showcase/automotive/src/main/AndroidManifest.xml b/car/app/app-samples/showcase/automotive/src/main/AndroidManifest.xml
index a7770fa..45423e3 100644
--- a/car/app/app-samples/showcase/automotive/src/main/AndroidManifest.xml
+++ b/car/app/app-samples/showcase/automotive/src/main/AndroidManifest.xml
@@ -37,7 +37,8 @@
 
   <application
       android:label="@string/app_name"
-      android:icon="@drawable/ic_launcher">
+      android:icon="@drawable/ic_launcher"
+      android:extractNativeLibs="false">
 
     <meta-data
         android:name="com.android.automotive"
diff --git a/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/common/PlaceDetailsScreen.java b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/common/PlaceDetailsScreen.java
index f972036..6125f04 100644
--- a/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/common/PlaceDetailsScreen.java
+++ b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/common/PlaceDetailsScreen.java
@@ -16,6 +16,7 @@
 
 package androidx.car.app.sample.showcase.common.common;
 
+import static androidx.car.app.CarToast.LENGTH_LONG;
 import static androidx.car.app.model.Action.BACK;
 
 import android.content.Intent;
@@ -23,6 +24,8 @@
 
 import androidx.annotation.NonNull;
 import androidx.car.app.CarContext;
+import androidx.car.app.CarToast;
+import androidx.car.app.HostException;
 import androidx.car.app.Screen;
 import androidx.car.app.model.Action;
 import androidx.car.app.model.CarColor;
@@ -78,13 +81,31 @@
     private void onClickNavigate() {
         Uri uri = Uri.parse("geo:0,0?q=" + mPlace.address);
         Intent intent = new Intent(CarContext.ACTION_NAVIGATE, uri);
-        getCarContext().startCarApp(intent);
+
+        try {
+            getCarContext().startCarApp(intent);
+        } catch (HostException e) {
+            CarToast.makeText(
+                    getCarContext(),
+                    "Failure starting navigation",
+                    LENGTH_LONG)
+                    .show();
+        }
     }
 
     private void onClickDial() {
         Uri uri = Uri.parse("tel:" + mPlace.phoneNumber);
         Intent intent = new Intent(Intent.ACTION_DIAL, uri);
-        getCarContext().startCarApp(intent);
+
+        try {
+            getCarContext().startCarApp(intent);
+        } catch (HostException e) {
+            CarToast.makeText(
+                    getCarContext(),
+                    "Failure starting dialer",
+                    LENGTH_LONG)
+                    .show();
+        }
     }
 
     private PlaceDetailsScreen(@NonNull CarContext carContext, @NonNull PlaceInfo place) {
diff --git a/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/navigation/routing/NavigatingDemoScreen.java b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/navigation/routing/NavigatingDemoScreen.java
index 2eb638f..0f415845 100644
--- a/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/navigation/routing/NavigatingDemoScreen.java
+++ b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/navigation/routing/NavigatingDemoScreen.java
@@ -46,6 +46,7 @@
                                 .build())
                 .setDestinationTravelEstimate(RoutingDemoModels.getTravelEstimate())
                 .setActionStrip(RoutingDemoModels.getActionStrip(getCarContext(), this::finish))
+                .setMapActionStrip(RoutingDemoModels.getMapActionStrip(getCarContext()))
                 .setBackgroundColor(CarColor.SECONDARY)
                 .build();
     }
diff --git a/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/navigation/routing/RoutingDemoModels.java b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/navigation/routing/RoutingDemoModels.java
index b7aed8f..248a8ea 100644
--- a/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/navigation/routing/RoutingDemoModels.java
+++ b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/navigation/routing/RoutingDemoModels.java
@@ -151,6 +151,47 @@
                 .build();
     }
 
+    /**
+     * Returns the map action strip that contains pan and zoom buttons.
+     */
+    @NonNull
+    public static ActionStrip getMapActionStrip(
+            @NonNull CarContext carContext) {
+        return new ActionStrip.Builder()
+                .addAction(
+                        new Action.Builder()
+                                .setOnClickListener(
+                                        () -> CarToast.makeText(
+                                                carContext,
+                                                "Zoomed in",
+                                                CarToast.LENGTH_SHORT)
+                                                .show())
+                                .setIcon(
+                                        new CarIcon.Builder(
+                                                IconCompat.createWithResource(
+                                                        carContext,
+                                                        R.drawable.ic_zoom_in_24))
+                                                .build())
+                                .build())
+                .addAction(
+                        new Action.Builder()
+                                .setOnClickListener(
+                                        () -> CarToast.makeText(
+                                                carContext,
+                                                "Zoomed out",
+                                                CarToast.LENGTH_SHORT)
+                                                .show())
+                                .setIcon(
+                                        new CarIcon.Builder(
+                                                IconCompat.createWithResource(
+                                                        carContext,
+                                                        R.drawable.ic_zoom_out_24))
+                                                .build())
+                                .build())
+                .addAction(Action.PAN)
+                .build();
+    }
+
     /** Returns the {@link TravelEstimate} with time and distance information. */
     @NonNull
     public static TravelEstimate getTravelEstimate() {
diff --git a/car/app/app-samples/showcase/common/src/main/res/drawable/ic_zoom_in_24.xml b/car/app/app-samples/showcase/common/src/main/res/drawable/ic_zoom_in_24.xml
new file mode 100644
index 0000000..36d3282
--- /dev/null
+++ b/car/app/app-samples/showcase/common/src/main/res/drawable/ic_zoom_in_24.xml
@@ -0,0 +1,25 @@
+<!--
+  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.
+  -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+  <path
+      android:fillColor="@android:color/white"
+      android:pathData="M19,13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/>
+</vector>
diff --git a/car/app/app-samples/showcase/common/src/main/res/drawable/ic_zoom_out_24.xml b/car/app/app-samples/showcase/common/src/main/res/drawable/ic_zoom_out_24.xml
new file mode 100644
index 0000000..089d265
--- /dev/null
+++ b/car/app/app-samples/showcase/common/src/main/res/drawable/ic_zoom_out_24.xml
@@ -0,0 +1,25 @@
+<!--
+  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.
+  -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+  <path
+      android:fillColor="@android:color/white"
+      android:pathData="M19,13H5v-2h14v2z"/>
+</vector>
diff --git a/car/app/app-samples/showcase/mobile/src/main/AndroidManifest.xml b/car/app/app-samples/showcase/mobile/src/main/AndroidManifest.xml
index 9956fb8..5508c67 100644
--- a/car/app/app-samples/showcase/mobile/src/main/AndroidManifest.xml
+++ b/car/app/app-samples/showcase/mobile/src/main/AndroidManifest.xml
@@ -35,7 +35,8 @@
 
   <application
       android:label="@string/app_name"
-      android:icon="@drawable/ic_launcher">
+      android:icon="@drawable/ic_launcher"
+      android:extractNativeLibs="false">
 
     <meta-data
         android:name="com.google.android.gms.car.application"
diff --git a/car/app/app-testing/lint-baseline.xml b/car/app/app-testing/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/car/app/app-testing/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/car/app/app/src/main/java/androidx/car/app/CarContext.java b/car/app/app/src/main/java/androidx/car/app/CarContext.java
index 19ead04..9258d2a 100644
--- a/car/app/app/src/main/java/androidx/car/app/CarContext.java
+++ b/car/app/app/src/main/java/androidx/car/app/CarContext.java
@@ -284,7 +284,7 @@
      * @param intent the {@link Intent} to send to the target application
      * @throws SecurityException         if the app attempts to start a different app explicitly or
      *                                   does not have permissions for the requested action
-     * @throws InvalidParameterException if {@code intent} does not meet the criteria defined
+     * @throws HostException             if the remote call fails
      * @throws NullPointerException      if {@code intent} is {@code null}
      */
     public void startCarApp(@NonNull Intent intent) {
diff --git a/cardview/cardview/lint-baseline.xml b/cardview/cardview/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/cardview/cardview/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/cleanBuild.sh b/cleanBuild.sh
index c6ed4dd..5b330b9 100755
--- a/cleanBuild.sh
+++ b/cleanBuild.sh
@@ -17,7 +17,7 @@
   echo
   echo "For example:"
   echo
-  echo "  $0 assembleDebug # or any other arguments you would normally give to ./gradlew"
+  echo "  $0 assembleRelease # or any other arguments you would normally give to ./gradlew"
   echo
   echo
   echo "-y"
diff --git a/collection/collection-benchmark/lint-baseline.xml b/collection/collection-benchmark/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/collection/collection-benchmark/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/collection/collection-ktx/lint-baseline.xml b/collection/collection-ktx/lint-baseline.xml
deleted file mode 100644
index 8794ae8..0000000
--- a/collection/collection-ktx/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" version="4.2.0-beta06">
-
-</issues>
diff --git a/collection/integration-tests/testapp/lint-baseline.xml b/collection/integration-tests/testapp/lint-baseline.xml
deleted file mode 100644
index 8794ae8..0000000
--- a/collection/integration-tests/testapp/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" version="4.2.0-beta06">
-
-</issues>
diff --git a/compose/animation/animation-core/api/public_plus_experimental_1.0.0-beta07.txt b/compose/animation/animation-core/api/public_plus_experimental_1.0.0-beta07.txt
index 67580ac..459b61a 100644
--- a/compose/animation/animation-core/api/public_plus_experimental_1.0.0-beta07.txt
+++ b/compose/animation/animation-core/api/public_plus_experimental_1.0.0-beta07.txt
@@ -272,6 +272,9 @@
     method public static androidx.compose.animation.core.Easing getLinearOutSlowInEasing();
   }
 
+  @kotlin.RequiresOptIn(message="This is an experimental animation API for Transition. It may change in the future.") public @interface ExperimentalTransitionApi {
+  }
+
   public interface FiniteAnimationSpec<T> extends androidx.compose.animation.core.AnimationSpec<T> {
     method public <V extends androidx.compose.animation.core.AnimationVector> androidx.compose.animation.core.VectorizedFiniteAnimationSpec<V> vectorize(androidx.compose.animation.core.TwoWayConverter<T,V> converter);
   }
@@ -404,8 +407,10 @@
     ctor public MutableTransitionState(S? initialState);
     method public S! getCurrentState();
     method public S! getTargetState();
+    method @androidx.compose.animation.core.ExperimentalTransitionApi public boolean isIdle();
     method public void setTargetState(S! p);
     property public final S! currentState;
+    property @androidx.compose.animation.core.ExperimentalTransitionApi public final boolean isIdle;
     property public final S! targetState;
   }
 
@@ -518,6 +523,7 @@
     method @androidx.compose.runtime.Composable public static inline <S> androidx.compose.runtime.State<androidx.compose.ui.geometry.Rect> animateRect(androidx.compose.animation.core.Transition<S>, optional kotlin.jvm.functions.Function1<? super androidx.compose.animation.core.Transition.Segment<S>,? extends androidx.compose.animation.core.FiniteAnimationSpec<androidx.compose.ui.geometry.Rect>> transitionSpec, optional String label, kotlin.jvm.functions.Function1<? super S,androidx.compose.ui.geometry.Rect> targetValueByState);
     method @androidx.compose.runtime.Composable public static inline <S> androidx.compose.runtime.State<androidx.compose.ui.geometry.Size> animateSize(androidx.compose.animation.core.Transition<S>, optional kotlin.jvm.functions.Function1<? super androidx.compose.animation.core.Transition.Segment<S>,? extends androidx.compose.animation.core.FiniteAnimationSpec<androidx.compose.ui.geometry.Size>> transitionSpec, optional String label, kotlin.jvm.functions.Function1<? super S,androidx.compose.ui.geometry.Size> targetValueByState);
     method @androidx.compose.runtime.Composable public static inline <S, T, V extends androidx.compose.animation.core.AnimationVector> androidx.compose.runtime.State<T> animateValue(androidx.compose.animation.core.Transition<S>, androidx.compose.animation.core.TwoWayConverter<T,V> typeConverter, optional kotlin.jvm.functions.Function1<? super androidx.compose.animation.core.Transition.Segment<S>,? extends androidx.compose.animation.core.FiniteAnimationSpec<T>> transitionSpec, optional String label, kotlin.jvm.functions.Function1<? super S,? extends T> targetValueByState);
+    method @androidx.compose.animation.core.ExperimentalTransitionApi @androidx.compose.runtime.Composable public static inline <S, T> androidx.compose.animation.core.Transition<T> createChildTransition(androidx.compose.animation.core.Transition<S>, optional String label, kotlin.jvm.functions.Function1<? super S,? extends T> transformToChildState);
     method @androidx.compose.runtime.Composable public static <T> androidx.compose.animation.core.Transition<T> updateTransition(T? targetState, optional String? label);
     method @androidx.compose.runtime.Composable public static <T> androidx.compose.animation.core.Transition<T> updateTransition(androidx.compose.animation.core.MutableTransitionState<T> transitionState, optional String? label);
   }
diff --git a/compose/animation/animation-core/api/public_plus_experimental_current.txt b/compose/animation/animation-core/api/public_plus_experimental_current.txt
index 67580ac..459b61a 100644
--- a/compose/animation/animation-core/api/public_plus_experimental_current.txt
+++ b/compose/animation/animation-core/api/public_plus_experimental_current.txt
@@ -272,6 +272,9 @@
     method public static androidx.compose.animation.core.Easing getLinearOutSlowInEasing();
   }
 
+  @kotlin.RequiresOptIn(message="This is an experimental animation API for Transition. It may change in the future.") public @interface ExperimentalTransitionApi {
+  }
+
   public interface FiniteAnimationSpec<T> extends androidx.compose.animation.core.AnimationSpec<T> {
     method public <V extends androidx.compose.animation.core.AnimationVector> androidx.compose.animation.core.VectorizedFiniteAnimationSpec<V> vectorize(androidx.compose.animation.core.TwoWayConverter<T,V> converter);
   }
@@ -404,8 +407,10 @@
     ctor public MutableTransitionState(S? initialState);
     method public S! getCurrentState();
     method public S! getTargetState();
+    method @androidx.compose.animation.core.ExperimentalTransitionApi public boolean isIdle();
     method public void setTargetState(S! p);
     property public final S! currentState;
+    property @androidx.compose.animation.core.ExperimentalTransitionApi public final boolean isIdle;
     property public final S! targetState;
   }
 
@@ -518,6 +523,7 @@
     method @androidx.compose.runtime.Composable public static inline <S> androidx.compose.runtime.State<androidx.compose.ui.geometry.Rect> animateRect(androidx.compose.animation.core.Transition<S>, optional kotlin.jvm.functions.Function1<? super androidx.compose.animation.core.Transition.Segment<S>,? extends androidx.compose.animation.core.FiniteAnimationSpec<androidx.compose.ui.geometry.Rect>> transitionSpec, optional String label, kotlin.jvm.functions.Function1<? super S,androidx.compose.ui.geometry.Rect> targetValueByState);
     method @androidx.compose.runtime.Composable public static inline <S> androidx.compose.runtime.State<androidx.compose.ui.geometry.Size> animateSize(androidx.compose.animation.core.Transition<S>, optional kotlin.jvm.functions.Function1<? super androidx.compose.animation.core.Transition.Segment<S>,? extends androidx.compose.animation.core.FiniteAnimationSpec<androidx.compose.ui.geometry.Size>> transitionSpec, optional String label, kotlin.jvm.functions.Function1<? super S,androidx.compose.ui.geometry.Size> targetValueByState);
     method @androidx.compose.runtime.Composable public static inline <S, T, V extends androidx.compose.animation.core.AnimationVector> androidx.compose.runtime.State<T> animateValue(androidx.compose.animation.core.Transition<S>, androidx.compose.animation.core.TwoWayConverter<T,V> typeConverter, optional kotlin.jvm.functions.Function1<? super androidx.compose.animation.core.Transition.Segment<S>,? extends androidx.compose.animation.core.FiniteAnimationSpec<T>> transitionSpec, optional String label, kotlin.jvm.functions.Function1<? super S,? extends T> targetValueByState);
+    method @androidx.compose.animation.core.ExperimentalTransitionApi @androidx.compose.runtime.Composable public static inline <S, T> androidx.compose.animation.core.Transition<T> createChildTransition(androidx.compose.animation.core.Transition<S>, optional String label, kotlin.jvm.functions.Function1<? super S,? extends T> transformToChildState);
     method @androidx.compose.runtime.Composable public static <T> androidx.compose.animation.core.Transition<T> updateTransition(T? targetState, optional String? label);
     method @androidx.compose.runtime.Composable public static <T> androidx.compose.animation.core.Transition<T> updateTransition(androidx.compose.animation.core.MutableTransitionState<T> transitionState, optional String? label);
   }
diff --git a/compose/animation/animation-core/api/restricted_1.0.0-beta07.txt b/compose/animation/animation-core/api/restricted_1.0.0-beta07.txt
index 3062208..4d76908 100644
--- a/compose/animation/animation-core/api/restricted_1.0.0-beta07.txt
+++ b/compose/animation/animation-core/api/restricted_1.0.0-beta07.txt
@@ -484,13 +484,17 @@
   }
 
   public final class Transition<S> {
+    ctor @kotlin.PublishedApi internal Transition(androidx.compose.animation.core.MutableTransitionState<S> transitionState, optional String? label);
     method @kotlin.PublishedApi internal boolean addAnimation(androidx.compose.animation.core.Transition<S>.TransitionAnimationState<?,?> animation);
+    method @kotlin.PublishedApi internal boolean addTransition(androidx.compose.animation.core.Transition<?> transition);
     method public S! getCurrentState();
     method public String? getLabel();
     method public androidx.compose.animation.core.Transition.Segment<S> getSegment();
     method public S! getTargetState();
     method public boolean isRunning();
     method @kotlin.PublishedApi internal void removeAnimation(androidx.compose.animation.core.Transition<S>.TransitionAnimationState<?,?> animation);
+    method @kotlin.PublishedApi internal boolean removeTransition(androidx.compose.animation.core.Transition<?> transition);
+    method @androidx.compose.runtime.Composable @kotlin.PublishedApi internal void updateTarget(S? targetState);
     property public final S! currentState;
     property public final boolean isRunning;
     property public final String? label;
diff --git a/compose/animation/animation-core/api/restricted_current.txt b/compose/animation/animation-core/api/restricted_current.txt
index 3062208..4d76908 100644
--- a/compose/animation/animation-core/api/restricted_current.txt
+++ b/compose/animation/animation-core/api/restricted_current.txt
@@ -484,13 +484,17 @@
   }
 
   public final class Transition<S> {
+    ctor @kotlin.PublishedApi internal Transition(androidx.compose.animation.core.MutableTransitionState<S> transitionState, optional String? label);
     method @kotlin.PublishedApi internal boolean addAnimation(androidx.compose.animation.core.Transition<S>.TransitionAnimationState<?,?> animation);
+    method @kotlin.PublishedApi internal boolean addTransition(androidx.compose.animation.core.Transition<?> transition);
     method public S! getCurrentState();
     method public String? getLabel();
     method public androidx.compose.animation.core.Transition.Segment<S> getSegment();
     method public S! getTargetState();
     method public boolean isRunning();
     method @kotlin.PublishedApi internal void removeAnimation(androidx.compose.animation.core.Transition<S>.TransitionAnimationState<?,?> animation);
+    method @kotlin.PublishedApi internal boolean removeTransition(androidx.compose.animation.core.Transition<?> transition);
+    method @androidx.compose.runtime.Composable @kotlin.PublishedApi internal void updateTarget(S? targetState);
     property public final S! currentState;
     property public final boolean isRunning;
     property public final String? label;
diff --git a/compose/animation/animation-core/samples/src/main/java/androidx/compose/animation/core/samples/TransitionSamples.kt b/compose/animation/animation-core/samples/src/main/java/androidx/compose/animation/core/samples/TransitionSamples.kt
index 7d19409..7172064 100644
--- a/compose/animation/animation-core/samples/src/main/java/androidx/compose/animation/core/samples/TransitionSamples.kt
+++ b/compose/animation/animation-core/samples/src/main/java/androidx/compose/animation/core/samples/TransitionSamples.kt
@@ -18,11 +18,13 @@
 
 import androidx.annotation.Sampled
 import androidx.compose.animation.animateColor
+import androidx.compose.animation.core.ExperimentalTransitionApi
 import androidx.compose.animation.core.MutableTransitionState
 import androidx.compose.animation.core.Spring
 import androidx.compose.animation.core.Transition
 import androidx.compose.animation.core.animateDp
 import androidx.compose.animation.core.animateFloat
+import androidx.compose.animation.core.createChildTransition
 import androidx.compose.animation.core.keyframes
 import androidx.compose.animation.core.snap
 import androidx.compose.animation.core.spring
@@ -34,9 +36,12 @@
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.heightIn
 import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.widthIn
 import androidx.compose.foundation.layout.wrapContentSize
+import androidx.compose.foundation.shape.RoundedCornerShape
 import androidx.compose.material.Button
 import androidx.compose.material.Card
 import androidx.compose.material.Icon
@@ -51,6 +56,8 @@
 import androidx.compose.runtime.setValue
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.draw.scale
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.graphics.graphicsLayer
 import androidx.compose.ui.input.pointer.pointerInput
@@ -324,3 +331,118 @@
         }
     }
 }
+
+@Composable
+@Suppress("UNUSED_PARAMETER")
+@Sampled
+fun CreateChildTransitionSample() {
+    // enum class DialerState { DialerMinimized, NumberPad }
+    @OptIn(ExperimentalTransitionApi::class)
+    @Composable
+    fun DialerButton(visibilityTransition: Transition<Boolean>, modifier: Modifier) {
+        val scale by visibilityTransition.animateFloat { visible ->
+            if (visible) 1f else 2f
+        }
+        Box(modifier.scale(scale).background(Color.Black)) {
+            // Content goes here
+        }
+    }
+
+    @Composable
+    fun NumberPad(visibilityTransition: Transition<Boolean>) {
+        // Create animations using the provided Transition for visibility change here...
+    }
+
+    @OptIn(ExperimentalTransitionApi::class)
+    @Composable
+    fun childTransitionSample() {
+        var dialerState by remember { mutableStateOf(DialerState.NumberPad) }
+        Box(Modifier.fillMaxSize()) {
+            val parentTransition = updateTransition(dialerState)
+
+            // Animate to different corner radius based on target state
+            val cornerRadius by parentTransition.animateDp {
+                if (it == DialerState.NumberPad) 0.dp else 20.dp
+            }
+
+            Box(
+                Modifier.align(Alignment.BottomCenter).widthIn(50.dp).heightIn(50.dp)
+                    .clip(RoundedCornerShape(cornerRadius))
+            ) {
+                NumberPad(
+                    // Creates a child transition that derives its target state from the parent
+                    // transition, and the mapping from parent state to child state.
+                    // This will allow:
+                    // 1) Parent transition to account for additional animations in the child
+                    // Transitions before it considers itself finished. This is useful when you
+                    // have a subsequent action after all animations triggered by a state change
+                    // have finished.
+                    // 2) Separation of concerns. This allows the child composable (i.e.
+                    // NumberPad) to only care about its own visibility, rather than knowing about
+                    // DialerState.
+                    visibilityTransition = parentTransition.createChildTransition {
+                        // This is the lambda that defines how the parent target state maps to
+                        // child target state.
+                        it == DialerState.NumberPad
+                    }
+                    // Note: If it's not important for the animations within the child composable to
+                    // be observable, it's perfectly valid to not hoist the animations through
+                    // a Transition object and instead use animate*AsState.
+                )
+                DialerButton(
+                    visibilityTransition = parentTransition.createChildTransition {
+                        it == DialerState.DialerMinimized
+                    },
+                    modifier = Modifier.matchParentSize()
+                )
+            }
+        }
+    }
+}
+
+enum class DialerState {
+    DialerMinimized,
+    NumberPad
+}
+
+@OptIn(ExperimentalTransitionApi::class)
+@Composable
+fun TransitionStateIsIdleSample() {
+    @Composable
+    fun SelectableItem(selectedState: MutableTransitionState<Boolean>) {
+        val transition = updateTransition(selectedState)
+        val cornerRadius by transition.animateDp { selected -> if (selected) 10.dp else 0.dp }
+        val backgroundColor by transition.animateColor { selected ->
+            if (selected) Color.Red else Color.White
+        }
+        Box(Modifier.background(backgroundColor, RoundedCornerShape(cornerRadius))) {
+            // Item content goes here
+        }
+    }
+
+    @OptIn(ExperimentalTransitionApi::class)
+    @Composable
+    fun ItemsSample(selectedId: Int) {
+        Column {
+            repeat(3) { id ->
+                Box {
+                    // Initialize the selected state as false to produce a transition going from
+                    // false to true if `selected` parameter is true when entering composition.
+                    val selectedState = remember { MutableTransitionState(false) }
+                    // Mutate target state as needed.
+                    selectedState.targetState = id == selectedId
+                    // Now we pass the `MutableTransitionState` to the `Selectable` item and
+                    // observe state change.
+                    SelectableItem(selectedState)
+                    if (selectedState.isIdle && selectedState.targetState) {
+                        // If isIdle == true, it means the transition has arrived at its target state
+                        // and there is no pending animation.
+                        // Now we can do something after the selection transition is
+                        // finished:
+                        Text("Nice choice")
+                    }
+                }
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/compose/animation/animation-core/src/androidAndroidTest/kotlin/androidx/compose/animation/core/TransitionTest.kt b/compose/animation/animation-core/src/androidAndroidTest/kotlin/androidx/compose/animation/core/TransitionTest.kt
index 157183c9..8135f03 100644
--- a/compose/animation/animation-core/src/androidAndroidTest/kotlin/androidx/compose/animation/core/TransitionTest.kt
+++ b/compose/animation/animation-core/src/androidAndroidTest/kotlin/androidx/compose/animation/core/TransitionTest.kt
@@ -20,16 +20,17 @@
 import androidx.compose.animation.animateColor
 import androidx.compose.runtime.LaunchedEffect
 import androidx.compose.runtime.State
-import androidx.compose.runtime.withFrameNanos
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
 import androidx.compose.runtime.setValue
+import androidx.compose.runtime.withFrameNanos
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.test.junit4.createComposeRule
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.LargeTest
 import junit.framework.TestCase.assertEquals
+import junit.framework.TestCase.assertFalse
 import junit.framework.TestCase.assertTrue
 import kotlinx.coroutines.delay
 import org.junit.Rule
@@ -48,6 +49,7 @@
         To
     }
 
+    @OptIn(InternalAnimationApi::class)
     @Test
     fun transitionTest() {
         val target = mutableStateOf(AnimStates.From)
@@ -239,6 +241,7 @@
         assertTrue(playTime > 200 * MillisToNanos)
     }
 
+    @OptIn(InternalAnimationApi::class)
     @Test
     fun addNewAnimationInFlightTest() {
         val target = mutableStateOf(AnimStates.From)
@@ -339,4 +342,108 @@
         assertTrue(targetRecreated)
         assertTrue(playTime >= 800 * MillisToNanos)
     }
+
+    @OptIn(ExperimentalTransitionApi::class)
+    @Test
+    fun testMutableTransitionStateIsIdle() {
+        val mutableTransitionState = MutableTransitionState(false)
+        var transition: Transition<Boolean>? = null
+        rule.setContent {
+            transition = updateTransition(mutableTransitionState).apply {
+                animateFloat {
+                    if (it) 1f else 0f
+                }
+            }
+        }
+        rule.mainClock.autoAdvance = false
+        rule.runOnIdle {
+            assertTrue(mutableTransitionState.isIdle)
+            mutableTransitionState.targetState = true
+            assertFalse(mutableTransitionState.isIdle)
+        }
+
+        while (transition?.currentState != true) {
+            // Animation has not finished or even started from Transition's perspective
+            assertFalse(mutableTransitionState.isIdle)
+            rule.mainClock.advanceTimeByFrame()
+        }
+        assertTrue(mutableTransitionState.isIdle)
+
+        // Now that transition false -> true finished, go back to false
+        rule.runOnIdle {
+            assertTrue(mutableTransitionState.isIdle)
+            mutableTransitionState.targetState = false
+            assertFalse(mutableTransitionState.isIdle)
+        }
+
+        while (transition?.currentState == true) {
+            // Animation has not finished or even started from Transition's perspective
+            assertFalse(mutableTransitionState.isIdle)
+            rule.mainClock.advanceTimeByFrame()
+        }
+        assertTrue(mutableTransitionState.isIdle)
+    }
+
+    @OptIn(ExperimentalTransitionApi::class, InternalAnimationApi::class)
+    @Test
+    fun testCreateChildTransition() {
+        val intState = mutableStateOf(1)
+        val parentTransitionFloat = mutableStateOf(1f)
+        val childTransitionFloat = mutableStateOf(1f)
+        rule.setContent {
+            val transition = updateTransition(intState.value)
+            parentTransitionFloat.value = transition.animateFloat({ tween(100) }) {
+                when (it) {
+                    0 -> 0f
+                    1 -> 1f
+                    else -> 2f
+                }
+            }.value
+            val booleanTransition = transition.createChildTransition {
+                it == 1
+            }
+            childTransitionFloat.value = booleanTransition.animateFloat({ tween(500) }) {
+                if (it) 1f else 0f
+            }.value
+            LaunchedEffect(intState.value) {
+                while (true) {
+                    if (transition.targetState == transition.currentState) {
+                        break
+                    }
+                    withFrameNanos { it }
+                    if (intState.value == 0) {
+                        // 1 -> 0
+                        if (
+                            transition.playTimeNanos > 0 && transition.playTimeNanos <= 500_000_000L
+                        ) {
+                            assertTrue(transition.isRunning)
+                            assertTrue(booleanTransition.isRunning)
+                        }
+                    } else if (intState.value == 2) {
+                        // 0 -> 2
+                        assertFalse(booleanTransition.isRunning)
+                        if (transition.playTimeNanos > 120_000_000L) {
+                            assertFalse(transition.isRunning)
+                        } else if (transition.playTimeNanos > 0) {
+                            assertTrue(transition.isRunning)
+                        }
+                    }
+                }
+            }
+        }
+        rule.runOnIdle {
+            assertEquals(1f, parentTransitionFloat.value)
+            assertEquals(1f, childTransitionFloat.value)
+            intState.value = 0
+        }
+        rule.runOnIdle {
+            assertEquals(0f, parentTransitionFloat.value)
+            assertEquals(0f, childTransitionFloat.value)
+            intState.value = 2
+        }
+        rule.runOnIdle {
+            assertEquals(2f, parentTransitionFloat.value)
+            assertEquals(0f, childTransitionFloat.value)
+        }
+    }
 }
\ No newline at end of file
diff --git a/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/ExperimentalTransitionApi.kt b/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/ExperimentalTransitionApi.kt
new file mode 100644
index 0000000..9972083
--- /dev/null
+++ b/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/ExperimentalTransitionApi.kt
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.animation.core
+
+@RequiresOptIn(
+    message = "This is an experimental animation API for Transition. It may change in the future."
+)
+annotation class ExperimentalTransitionApi
\ No newline at end of file
diff --git a/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/Transition.kt b/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/Transition.kt
index 1251a0c..d0cce40 100644
--- a/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/Transition.kt
+++ b/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/Transition.kt
@@ -22,7 +22,6 @@
 import androidx.compose.runtime.DisposableEffect
 import androidx.compose.runtime.LaunchedEffect
 import androidx.compose.runtime.MutableState
-import androidx.compose.runtime.SideEffect
 import androidx.compose.runtime.State
 import androidx.compose.runtime.collection.mutableVectorOf
 import androidx.compose.runtime.getValue
@@ -66,7 +65,14 @@
     label: String? = null
 ): Transition<T> {
     val transition = remember { Transition(targetState, label = label) }
-    transition.updateTarget(targetState)
+    transition.animateTo(targetState)
+    DisposableEffect(transition) {
+        onDispose {
+            // Clean up on the way out, to ensure the observers are not stuck in an in-between
+            // state.
+            transition.onTransitionEnd()
+        }
+    }
     return transition
 }
 
@@ -101,6 +107,19 @@
      * animate from the current values to the new target.
      */
     var targetState: S by mutableStateOf(initialState)
+
+    /**
+     * [isIdle] returns whether the transition has finished running. This will return false once
+     * the [targetState] has been set to a different value than [currentState].
+     *
+     * @sample androidx.compose.animation.core.samples.TransitionStateIsIdleSample
+     */
+    @get:ExperimentalTransitionApi
+    val isIdle: Boolean
+        get() = (currentState == targetState) && !isRunning
+
+    // Updated from Transition
+    internal var isRunning: Boolean by mutableStateOf(false)
 }
 
 /**
@@ -130,7 +149,14 @@
     val transition = remember(transitionState) {
         Transition(transitionState = transitionState, label)
     }
-    transition.updateTarget(transitionState.targetState)
+    transition.animateTo(transitionState.targetState)
+    DisposableEffect(transition) {
+        onDispose {
+            // Clean up on the way out, to ensure the observers are not stuck in an in-between
+            // state.
+            transition.onTransitionEnd()
+        }
+    }
     return transition
 }
 
@@ -153,7 +179,7 @@
  * @see androidx.compose.animation.animateColor
  */
 // TODO: Support creating Transition outside of composition and support imperative use of Transition
-class Transition<S> internal constructor(
+class Transition<S> @PublishedApi internal constructor(
     private val transitionState: MutableTransitionState<S>,
     val label: String? = null
 ) {
@@ -196,15 +222,20 @@
     /**
      * Play time in nano-seconds. [playTimeNanos] is always non-negative. It starts from 0L at the
      * beginning of the transition and increment until all child animations have finished.
+     * @suppress
      */
-    internal var playTimeNanos by mutableStateOf(0L)
+    @InternalAnimationApi
+    var playTimeNanos by mutableStateOf(0L)
+    private var startTimeNanos by mutableStateOf(AnimationConstants.UnspecifiedTime)
 
     // This gets calculated every time child is updated/added
     internal var updateChildrenNeeded: Boolean by mutableStateOf(true)
-    private var startTimeNanos = AnimationConstants.UnspecifiedTime
 
     private val _animations = mutableVectorOf<TransitionAnimationState<*, *>>()
 
+    // TODO: Support this in animation tooling
+    private val _transitions = mutableVectorOf<Transition<*>>()
+
     /** @suppress **/
     @InternalAnimationApi
     val animations: List<TransitionAnimationState<*, *>> = _animations.asMutableList()
@@ -219,12 +250,9 @@
     var totalDurationNanos: Long by mutableStateOf(0L)
         private set
 
-    // Target state that is currently being animated to
-    private var currentTargetState: S = currentState
-
     internal fun onFrame(frameTimeNanos: Long) {
         if (startTimeNanos == AnimationConstants.UnspecifiedTime) {
-            startTimeNanos = frameTimeNanos
+            onTransitionStart(frameTimeNanos)
         }
         updateChildrenNeeded = false
 
@@ -241,11 +269,31 @@
                 allFinished = false
             }
         }
-        if (allFinished) {
-            startTimeNanos = AnimationConstants.UnspecifiedTime
-            currentState = targetState
-            playTimeNanos = 0
+        _transitions.forEach {
+            if (it.targetState != it.currentState) {
+                it.onFrame(playTimeNanos)
+            }
+            if (it.targetState != it.currentState) {
+                allFinished = false
+            }
         }
+        if (allFinished) {
+            onTransitionEnd()
+        }
+    }
+
+    // onTransitionStart and onTransitionEnd are symmetric. Both are called from onFrame
+    internal fun onTransitionStart(frameTimeNanos: Long) {
+        startTimeNanos = frameTimeNanos
+        transitionState.isRunning = true
+    }
+
+    // onTransitionStart and onTransitionEnd are symmetric. Both are called from onFrame
+    internal fun onTransitionEnd() {
+        startTimeNanos = AnimationConstants.UnspecifiedTime
+        currentState = targetState
+        playTimeNanos = 0
+        transitionState.isRunning = false
     }
 
     /**
@@ -256,6 +304,7 @@
     fun seek(initialState: S, targetState: S, playTimeNanos: Long) {
         // Reset running state
         startTimeNanos = AnimationConstants.UnspecifiedTime
+        transitionState.isRunning = false
         if (!isSeeking || this.currentState != initialState || this.targetState != targetState) {
             // Reset all child animations
             this.currentState = initialState
@@ -274,6 +323,12 @@
     }
 
     @PublishedApi
+    internal fun addTransition(transition: Transition<*>) = _transitions.add(transition)
+
+    @PublishedApi
+    internal fun removeTransition(transition: Transition<*>) = _transitions.remove(transition)
+
+    @PublishedApi
     internal fun addAnimation(
         @Suppress("HiddenTypeParameter")
         animation: TransitionAnimationState<*, *>
@@ -290,6 +345,7 @@
     // This target state should only be used to modify "mutableState"s, as it could potentially
     // roll back. The
     @Suppress("ComposableNaming")
+    @PublishedApi
     @Composable
     internal fun updateTarget(targetState: S) {
         if (!isSeeking) {
@@ -300,11 +356,25 @@
                 segment = Segment(this.targetState, targetState)
                 currentState = this.targetState
                 this.targetState = targetState
-            }
-            SideEffect {
-                animateTo(targetState)
-            }
+                if (!isRunning) {
+                    updateChildrenNeeded = true
+                }
 
+                // If target state is changed, reset all the animations to be re-created in the
+                // next frame w/ their new target value. Child animations target values are updated in
+                // the side effect that may not have happened when this function in invoked.
+                _animations.forEach { it.resetAnimation() }
+            }
+        }
+    }
+
+    // This should only be called if PlayTime comes from clock directly, instead of from a parent
+    // Transition.
+    @Suppress("ComposableNaming")
+    @Composable
+    internal fun animateTo(targetState: S) {
+        if (!isSeeking) {
+            updateTarget(targetState)
             // target != currentState adds LaunchedEffect into the tree in the same frame as
             // target change.
             if (targetState != currentState || isRunning || updateChildrenNeeded) {
@@ -319,22 +389,6 @@
         }
     }
 
-    internal fun animateTo(targetState: S) {
-        if (targetState != currentTargetState) {
-            if (isRunning) {
-                startTimeNanos += playTimeNanos
-                playTimeNanos = 0
-            } else {
-                updateChildrenNeeded = true
-            }
-            currentTargetState = targetState
-            // If target state is changed, reset all the animations to be re-created in the
-            // next frame w/ their new target value. Child animations target values are updated in
-            // the side effect that may not have happened when this function in invoked.
-            _animations.forEach { it.resetAnimation() }
-        }
-    }
-
     private fun onChildAnimationUpdated() {
         updateChildrenNeeded = true
         if (isSeeking) {
@@ -363,14 +417,9 @@
     ) : State<T> {
 
         // Changed during composition, may rollback
-        @Suppress("ShowingMemberInHiddenClass")
-        @PublishedApi
-        internal var targetValue: T by mutableStateOf(initialValue)
-            internal set
+        private var targetValue: T by mutableStateOf(initialValue)
+        private var animationSpec: FiniteAnimationSpec<T> by mutableStateOf(spring())
 
-        @Suppress("ShowingMemberInHiddenClass")
-        @PublishedApi
-        internal var animationSpec: FiniteAnimationSpec<T> by mutableStateOf(spring())
         private var animation: TargetBasedAnimation<T, V> by mutableStateOf(
             TargetBasedAnimation(
                 animationSpec, typeConverter, initialValue, targetValue,
@@ -379,11 +428,12 @@
         )
         internal var isFinished: Boolean by mutableStateOf(true)
         private var offsetTimeNanos by mutableStateOf(0L)
+        private var needsReset by mutableStateOf(false)
 
         // Changed during animation, no concerns of rolling back
         override var value by mutableStateOf(initialValue)
             internal set
-        internal var velocityVector: V = initialVelocityVector
+        private var velocityVector: V = initialVelocityVector
         internal val durationNanos
             get() = animation.durationNanos
 
@@ -404,9 +454,28 @@
             velocityVector = animation.getVelocityVectorFromNanos(playTimeNanos)
         }
 
-        private fun updateAnimation(initialValue: T = value) {
+        private val interruptionSpec: FiniteAnimationSpec<T>
+
+        init {
+            val visibilityThreshold: T? = visibilityThresholdMap.get(typeConverter)?.let {
+                val vector = typeConverter.convertToVector(initialValue)
+                for (id in 0 until vector.size) {
+                    vector[id] = it
+                }
+                typeConverter.convertFromVector(vector)
+            }
+            interruptionSpec = spring(visibilityThreshold = visibilityThreshold)
+        }
+
+        private fun updateAnimation(initialValue: T = value, isInterrupted: Boolean = false) {
+            val spec = if (isInterrupted) {
+                // When interrupted, use the default spring, unless the spec is also a spring.
+                if (animationSpec is SpringSpec<*>) animationSpec else interruptionSpec
+            } else {
+                animationSpec
+            }
             animation = TargetBasedAnimation(
-                animationSpec,
+                spec,
                 typeConverter,
                 initialValue,
                 targetValue,
@@ -416,29 +485,34 @@
         }
 
         internal fun resetAnimation() {
-            offsetTimeNanos = 0
-            isFinished = false
-            updateAnimation()
+            needsReset = true
         }
 
         @PublishedApi
         @Suppress("ShowingMemberInHiddenClass")
         // This gets called *during* composition
-        internal fun updateTargetValue(targetValue: T) {
-            if (this.targetValue != targetValue) {
+        internal fun updateTargetValue(targetValue: T, animationSpec: FiniteAnimationSpec<T>) {
+            if (this.targetValue != targetValue || needsReset) {
                 this.targetValue = targetValue
+                this.animationSpec = animationSpec
+                updateAnimation(isInterrupted = !isFinished)
                 isFinished = false
-                updateAnimation()
                 // This is needed because the target change could happen during a transition
                 offsetTimeNanos = playTimeNanos
+                needsReset = false
             }
         }
 
         @PublishedApi
         @Suppress("ControlFlowWithEmptyBody", "ShowingMemberInHiddenClass")
         // This gets called *during* composition
-        internal fun updateInitialAndTargetValue(initialValue: T, targetValue: T) {
+        internal fun updateInitialAndTargetValue(
+            initialValue: T,
+            targetValue: T,
+            animationSpec: FiniteAnimationSpec<T>
+        ) {
             this.targetValue = targetValue
+            this.animationSpec = animationSpec
             if (animation.initialValue == initialValue && animation.targetValue == targetValue) {
                 // TODO(b/178811102): we should be able to return early here.
             }
@@ -460,6 +534,160 @@
             return this == initialState && targetState == this@Segment.targetState
         }
     }
+
+    /**
+     * [DeferredAnimation] can be constructed using [Transition.createDeferredAnimation] during
+     * composition and initialized later. It is useful for animations, the target values for
+     * which are unknown at composition time (e.g. layout size/position, etc).
+     *
+     * Once a [DeferredAnimation] is created, it can be configured and updated as needed using
+     * [DeferredAnimation.animate] method.
+     *
+     * @suppress
+     */
+    @InternalAnimationApi
+    inner class DeferredAnimation<T, V : AnimationVector> internal constructor(
+        val typeConverter: TwoWayConverter<T, V>,
+        val label: String
+    ) {
+        internal var data: DeferredAnimationData<T, V>? = null
+
+        internal inner class DeferredAnimationData<T, V : AnimationVector>(
+            val animation: Transition<S>.TransitionAnimationState<T, V>,
+            var transitionSpec: Segment<S>.() -> FiniteAnimationSpec<T>,
+            var targetValueByState: (state: S) -> T,
+        ) : State<T> {
+            override val value: T
+                get() {
+                    animation.updateTargetValue(
+                        targetValueByState(targetState),
+                        segment.transitionSpec()
+                    )
+                    return animation.value
+                }
+        }
+
+        /**
+         * [DeferredAnimation] allows the animation setup to be deferred until a later time after
+         * composition. [animate] can be used to set up a [DeferredAnimation]. Like other
+         * Transition animations such as [Transition.animateFloat], [DeferredAnimation] also
+         * expects [transitionSpec] and [targetValueByState] for the mapping from target state
+         * to animation spec and target value, respectively.
+         */
+        fun animate(
+            transitionSpec: Segment<S>.() -> FiniteAnimationSpec<T>,
+            targetValueByState: (state: S) -> T
+        ): State<T> {
+            val animData: DeferredAnimationData<T, V> = data ?: DeferredAnimationData(
+                TransitionAnimationState(
+                    targetValueByState(currentState),
+                    typeConverter.createZeroVectorFrom(targetValueByState(currentState)),
+                    typeConverter,
+                    label
+                ),
+                transitionSpec,
+                targetValueByState
+            ).apply {
+                data = this
+                addAnimation(this.animation)
+            }
+            return animData.apply {
+                // Update animtion data with the latest mapping
+                this.targetValueByState = targetValueByState
+                this.transitionSpec = transitionSpec
+
+                animation.updateTargetValue(
+                    targetValueByState(targetState),
+                    segment.transitionSpec()
+                )
+            }
+        }
+
+        internal fun setupSeeking() {
+            data?.apply {
+                animation.updateInitialAndTargetValue(
+                    targetValueByState(segment.initialState),
+                    targetValueByState(segment.targetState),
+                    segment.transitionSpec()
+                )
+            }
+        }
+    }
+
+    internal fun removeAnimation(deferredAnimation: DeferredAnimation<*, *>) {
+        deferredAnimation.data?.animation?.let {
+            removeAnimation(it)
+        }
+    }
+}
+
+/**
+ * This creates a [DeferredAnimation], which will not animate until it is set up using
+ * [DeferredAnimation.animate]. Once the animation is set up, it will animate from the
+ * [currentState][Transition.currentState] to [targetState][Transition.targetState]. If the
+ * [Transition] has already arrived at its target state at the time when the animation added, there
+ * will be no animation.
+ *
+ * @param typeConverter A converter to convert any value of type [T] from/to an [AnimationVector]
+ * @param label A label for differentiating this animation from others in android studio.
+ *
+ * @suppress
+ */
+@InternalAnimationApi
+@Composable
+fun <S, T, V : AnimationVector> Transition<S>.createDeferredAnimation(
+    typeConverter: TwoWayConverter<T, V>,
+    label: String = "DeferredAnimation"
+): Transition<S>.DeferredAnimation<T, V> {
+    val lazyAnim = remember(this) { DeferredAnimation(typeConverter, label) }
+    DisposableEffect(lazyAnim) {
+        onDispose {
+            removeAnimation(lazyAnim)
+        }
+    }
+    if (isSeeking) {
+        lazyAnim.setupSeeking()
+    }
+    return lazyAnim
+}
+
+/**
+ * [createChildTransition] creates a child Transition based on the mapping between parent state to
+ * child state provided in [transformToChildState]. This serves the following purposes:
+ * 1) Hoist the child transition state into parent transition. Therefore the parent Transition
+ * will be aware of whether there's any on-going animation due to the same target state change.
+ * This will further allow sequential animation to be set up when all animations have finished.
+ * 2) Separation of concerns. The child transition can respresent a much more simplified state
+ * transition when, for example, mapping from an enum parent state to a Boolean visible state for
+ * passing further down the compose tree. The child composables hence can be designed around
+ * handling a more simple and a more relevant state change.
+ *
+ * [label] is used to differentiate from other animations in the same transition in Android Studio.
+ *
+ * @sample androidx.compose.animation.core.samples.CreateChildTransitionSample
+ */
+@ExperimentalTransitionApi
+@Composable
+inline fun <S, T> Transition<S>.createChildTransition(
+    label: String = "ChildTransition",
+    transformToChildState: @Composable (parentState: S) -> T,
+): Transition<T> {
+    val initialParentState = remember(this) { this.currentState }
+    val initialState = transformToChildState(initialParentState)
+    val transition = remember(this) {
+        Transition<T>(MutableTransitionState(initialState), label)
+    }
+
+    DisposableEffect(transition) {
+        addTransition(transition)
+        onDispose {
+            removeTransition(transition)
+        }
+    }
+
+    val targetState = transformToChildState(this.targetState)
+    transition.updateTarget(targetState)
+    return transition
 }
 
 /**
@@ -496,36 +724,30 @@
     targetValueByState: @Composable (state: S) -> T
 ): State<T> {
 
+    val initialValue = targetValueByState(currentState)
     val targetValue = targetValueByState(targetState)
-    // TODO: need a better way to store initial value.
-    val initNeeded = remember(this) { mutableStateOf(true) }
-    val initValue = if (initNeeded.value) targetValueByState(currentState) else targetValue
     val transitionAnimation = remember(this) {
         // Initialize the animation state to initialState value, so if it's added during a
         // transition run, it'll participate in the animation.
         // This is preferred because it's easy to opt out - Simply adding new animation once
         // currentState == targetState would opt out.
         TransitionAnimationState(
-            initValue,
+            initialValue,
             typeConverter.createZeroVectorFrom(targetValue),
             typeConverter,
             label
         )
     }
-    transitionAnimation.animationSpec = transitionSpec(segment)
-
-    if (initNeeded.value) {
-        SideEffect {
-            initNeeded.value = false
-        }
-    }
-
+    val animationSpec = transitionSpec(segment)
     if (isSeeking) {
         // In the case of seeking, we also need to update initial value as needed
-        val initialValue = targetValueByState(segment.initialState)
-        transitionAnimation.updateInitialAndTargetValue(initialValue, targetValue)
+        transitionAnimation.updateInitialAndTargetValue(
+            initialValue,
+            targetValue,
+            animationSpec
+        )
     } else {
-        transitionAnimation.updateTargetValue(targetValue)
+        transitionAnimation.updateTargetValue(targetValue, animationSpec)
     }
 
     DisposableEffect(transitionAnimation) {
diff --git a/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/VisibilityThresholds.kt b/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/VisibilityThresholds.kt
index 14f4ce0..5b27b547 100644
--- a/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/VisibilityThresholds.kt
+++ b/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/VisibilityThresholds.kt
@@ -99,4 +99,16 @@
 val Rect.Companion.VisibilityThreshold: Rect
     get() = rectVisibilityThreshold
 
-// TODO: Add Dp.DefaultAnimation = spring<Dp>(visibilityThreshold = Dp.VisibilityThreshold)
\ No newline at end of file
+// TODO: Add Dp.DefaultAnimation = spring<Dp>(visibilityThreshold = Dp.VisibilityThreshold)
+
+internal val visibilityThresholdMap: Map<TwoWayConverter<*, *>, Float> = mapOf(
+    Int.VectorConverter to 1f,
+    IntSize.VectorConverter to 1f,
+    IntOffset.VectorConverter to 1f,
+    Float.VectorConverter to 0.01f,
+    Rect.VectorConverter to PxVisibilityThreshold,
+    Size.VectorConverter to PxVisibilityThreshold,
+    Offset.VectorConverter to PxVisibilityThreshold,
+    Dp.VectorConverter to DpVisibilityThreshold,
+    DpOffset.VectorConverter to DpVisibilityThreshold
+)
\ No newline at end of file
diff --git a/compose/animation/animation/api/public_plus_experimental_1.0.0-beta07.txt b/compose/animation/animation/api/public_plus_experimental_1.0.0-beta07.txt
index 5c07774..0f00f36 100644
--- a/compose/animation/animation/api/public_plus_experimental_1.0.0-beta07.txt
+++ b/compose/animation/animation/api/public_plus_experimental_1.0.0-beta07.txt
@@ -6,9 +6,20 @@
   }
 
   public final class AnimatedVisibilityKt {
-    method @androidx.compose.animation.ExperimentalAnimationApi @androidx.compose.runtime.Composable public static void AnimatedVisibility(boolean visible, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.animation.EnterTransition enter, optional androidx.compose.animation.ExitTransition exit, optional boolean initiallyVisible, kotlin.jvm.functions.Function0<kotlin.Unit> content);
-    method @androidx.compose.animation.ExperimentalAnimationApi @androidx.compose.runtime.Composable public static void AnimatedVisibility(androidx.compose.foundation.layout.RowScope, boolean visible, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.animation.EnterTransition enter, optional androidx.compose.animation.ExitTransition exit, optional boolean initiallyVisible, kotlin.jvm.functions.Function0<kotlin.Unit> content);
-    method @androidx.compose.animation.ExperimentalAnimationApi @androidx.compose.runtime.Composable public static void AnimatedVisibility(androidx.compose.foundation.layout.ColumnScope, boolean visible, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.animation.EnterTransition enter, optional androidx.compose.animation.ExitTransition exit, optional boolean initiallyVisible, kotlin.jvm.functions.Function0<kotlin.Unit> content);
+    method @androidx.compose.animation.ExperimentalAnimationApi @androidx.compose.runtime.Composable public static void AnimatedVisibility(boolean visible, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.animation.EnterTransition enter, optional androidx.compose.animation.ExitTransition exit, kotlin.jvm.functions.Function1<? super androidx.compose.animation.AnimatedVisibilityScope,kotlin.Unit> content);
+    method @androidx.compose.animation.ExperimentalAnimationApi @androidx.compose.runtime.Composable public static void AnimatedVisibility(androidx.compose.foundation.layout.RowScope, boolean visible, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.animation.EnterTransition enter, optional androidx.compose.animation.ExitTransition exit, kotlin.jvm.functions.Function1<? super androidx.compose.animation.AnimatedVisibilityScope,kotlin.Unit> content);
+    method @androidx.compose.animation.ExperimentalAnimationApi @androidx.compose.runtime.Composable public static void AnimatedVisibility(androidx.compose.foundation.layout.ColumnScope, boolean visible, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.animation.EnterTransition enter, optional androidx.compose.animation.ExitTransition exit, kotlin.jvm.functions.Function1<? super androidx.compose.animation.AnimatedVisibilityScope,kotlin.Unit> content);
+    method @androidx.compose.animation.ExperimentalAnimationApi @androidx.compose.runtime.Composable public static void AnimatedVisibility(androidx.compose.animation.core.MutableTransitionState<java.lang.Boolean> visibleState, optional androidx.compose.ui.Modifier modifier, androidx.compose.animation.EnterTransition enter, androidx.compose.animation.ExitTransition exit, kotlin.jvm.functions.Function1<? super androidx.compose.animation.AnimatedVisibilityScope,kotlin.Unit> content);
+    method @androidx.compose.animation.ExperimentalAnimationApi @androidx.compose.runtime.Composable public static void AnimatedVisibility(androidx.compose.foundation.layout.RowScope, androidx.compose.animation.core.MutableTransitionState<java.lang.Boolean> visibleState, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.animation.EnterTransition enter, optional androidx.compose.animation.ExitTransition exit, kotlin.jvm.functions.Function1<? super androidx.compose.animation.AnimatedVisibilityScope,kotlin.Unit> content);
+    method @androidx.compose.animation.ExperimentalAnimationApi @androidx.compose.runtime.Composable public static void AnimatedVisibility(androidx.compose.foundation.layout.ColumnScope, androidx.compose.animation.core.MutableTransitionState<java.lang.Boolean> visibleState, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.animation.EnterTransition enter, optional androidx.compose.animation.ExitTransition exit, kotlin.jvm.functions.Function1<? super androidx.compose.animation.AnimatedVisibilityScope,kotlin.Unit> content);
+    method @androidx.compose.animation.ExperimentalAnimationApi @androidx.compose.runtime.Composable public static <T> void AnimatedVisibility(androidx.compose.animation.core.Transition<T>, kotlin.jvm.functions.Function1<? super T,java.lang.Boolean> visible, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.animation.EnterTransition enter, optional androidx.compose.animation.ExitTransition exit, kotlin.jvm.functions.Function1<? super androidx.compose.animation.AnimatedVisibilityScope,kotlin.Unit> content);
+    method @Deprecated @androidx.compose.animation.ExperimentalAnimationApi @androidx.compose.runtime.Composable public static void AnimatedVisibility(boolean visible, optional androidx.compose.ui.Modifier modifier, androidx.compose.animation.EnterTransition enter, androidx.compose.animation.ExitTransition exit, boolean initiallyVisible, kotlin.jvm.functions.Function0<kotlin.Unit> content);
+  }
+
+  @androidx.compose.animation.ExperimentalAnimationApi public final class AnimatedVisibilityScope {
+    method public androidx.compose.ui.Modifier animateEnterExit(androidx.compose.ui.Modifier, optional androidx.compose.animation.EnterTransition enter, optional androidx.compose.animation.ExitTransition exit);
+    method public androidx.compose.animation.core.Transition<androidx.compose.animation.EnterExitState> getTransition();
+    property public final androidx.compose.animation.core.Transition<androidx.compose.animation.EnterExitState> transition;
   }
 
   public final class AnimationModifierKt {
@@ -23,6 +34,12 @@
     method @androidx.compose.runtime.Composable public static <T> void Crossfade(T? targetState, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.animation.core.FiniteAnimationSpec<java.lang.Float> animationSpec, kotlin.jvm.functions.Function1<? super T,kotlin.Unit> content);
   }
 
+  @androidx.compose.animation.ExperimentalAnimationApi public enum EnterExitState {
+    enum_constant public static final androidx.compose.animation.EnterExitState PostExit;
+    enum_constant public static final androidx.compose.animation.EnterExitState PreEnter;
+    enum_constant public static final androidx.compose.animation.EnterExitState Visible;
+  }
+
   public final class EnterExitTransitionKt {
     method @androidx.compose.animation.ExperimentalAnimationApi @androidx.compose.runtime.Stable public static androidx.compose.animation.EnterTransition expandHorizontally(optional androidx.compose.ui.Alignment.Horizontal expandFrom, optional kotlin.jvm.functions.Function1<? super java.lang.Integer,java.lang.Integer> initialWidth, optional androidx.compose.animation.core.FiniteAnimationSpec<androidx.compose.ui.unit.IntSize> animationSpec, optional boolean clip);
     method @androidx.compose.animation.ExperimentalAnimationApi @androidx.compose.runtime.Stable public static androidx.compose.animation.EnterTransition expandIn(optional androidx.compose.ui.Alignment expandFrom, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.unit.IntSize,androidx.compose.ui.unit.IntSize> initialSize, optional androidx.compose.animation.core.FiniteAnimationSpec<androidx.compose.ui.unit.IntSize> animationSpec, optional boolean clip);
@@ -42,10 +59,22 @@
 
   @androidx.compose.animation.ExperimentalAnimationApi @androidx.compose.runtime.Immutable public abstract sealed class EnterTransition {
     method @androidx.compose.runtime.Stable public final operator androidx.compose.animation.EnterTransition plus(androidx.compose.animation.EnterTransition enter);
+    field public static final androidx.compose.animation.EnterTransition.Companion Companion;
+  }
+
+  public static final class EnterTransition.Companion {
+    method public androidx.compose.animation.EnterTransition getNone();
+    property public final androidx.compose.animation.EnterTransition None;
   }
 
   @androidx.compose.animation.ExperimentalAnimationApi @androidx.compose.runtime.Immutable public abstract sealed class ExitTransition {
     method @androidx.compose.runtime.Stable public final operator androidx.compose.animation.ExitTransition plus(androidx.compose.animation.ExitTransition exit);
+    field public static final androidx.compose.animation.ExitTransition.Companion Companion;
+  }
+
+  public static final class ExitTransition.Companion {
+    method public androidx.compose.animation.ExitTransition getNone();
+    property public final androidx.compose.animation.ExitTransition None;
   }
 
   @kotlin.RequiresOptIn(message="This is an experimental animation API.") @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget, kotlin.annotation.AnnotationTarget, kotlin.annotation.AnnotationTarget}) public @interface ExperimentalAnimationApi {
diff --git a/compose/animation/animation/api/public_plus_experimental_current.txt b/compose/animation/animation/api/public_plus_experimental_current.txt
index 5c07774..0f00f36 100644
--- a/compose/animation/animation/api/public_plus_experimental_current.txt
+++ b/compose/animation/animation/api/public_plus_experimental_current.txt
@@ -6,9 +6,20 @@
   }
 
   public final class AnimatedVisibilityKt {
-    method @androidx.compose.animation.ExperimentalAnimationApi @androidx.compose.runtime.Composable public static void AnimatedVisibility(boolean visible, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.animation.EnterTransition enter, optional androidx.compose.animation.ExitTransition exit, optional boolean initiallyVisible, kotlin.jvm.functions.Function0<kotlin.Unit> content);
-    method @androidx.compose.animation.ExperimentalAnimationApi @androidx.compose.runtime.Composable public static void AnimatedVisibility(androidx.compose.foundation.layout.RowScope, boolean visible, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.animation.EnterTransition enter, optional androidx.compose.animation.ExitTransition exit, optional boolean initiallyVisible, kotlin.jvm.functions.Function0<kotlin.Unit> content);
-    method @androidx.compose.animation.ExperimentalAnimationApi @androidx.compose.runtime.Composable public static void AnimatedVisibility(androidx.compose.foundation.layout.ColumnScope, boolean visible, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.animation.EnterTransition enter, optional androidx.compose.animation.ExitTransition exit, optional boolean initiallyVisible, kotlin.jvm.functions.Function0<kotlin.Unit> content);
+    method @androidx.compose.animation.ExperimentalAnimationApi @androidx.compose.runtime.Composable public static void AnimatedVisibility(boolean visible, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.animation.EnterTransition enter, optional androidx.compose.animation.ExitTransition exit, kotlin.jvm.functions.Function1<? super androidx.compose.animation.AnimatedVisibilityScope,kotlin.Unit> content);
+    method @androidx.compose.animation.ExperimentalAnimationApi @androidx.compose.runtime.Composable public static void AnimatedVisibility(androidx.compose.foundation.layout.RowScope, boolean visible, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.animation.EnterTransition enter, optional androidx.compose.animation.ExitTransition exit, kotlin.jvm.functions.Function1<? super androidx.compose.animation.AnimatedVisibilityScope,kotlin.Unit> content);
+    method @androidx.compose.animation.ExperimentalAnimationApi @androidx.compose.runtime.Composable public static void AnimatedVisibility(androidx.compose.foundation.layout.ColumnScope, boolean visible, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.animation.EnterTransition enter, optional androidx.compose.animation.ExitTransition exit, kotlin.jvm.functions.Function1<? super androidx.compose.animation.AnimatedVisibilityScope,kotlin.Unit> content);
+    method @androidx.compose.animation.ExperimentalAnimationApi @androidx.compose.runtime.Composable public static void AnimatedVisibility(androidx.compose.animation.core.MutableTransitionState<java.lang.Boolean> visibleState, optional androidx.compose.ui.Modifier modifier, androidx.compose.animation.EnterTransition enter, androidx.compose.animation.ExitTransition exit, kotlin.jvm.functions.Function1<? super androidx.compose.animation.AnimatedVisibilityScope,kotlin.Unit> content);
+    method @androidx.compose.animation.ExperimentalAnimationApi @androidx.compose.runtime.Composable public static void AnimatedVisibility(androidx.compose.foundation.layout.RowScope, androidx.compose.animation.core.MutableTransitionState<java.lang.Boolean> visibleState, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.animation.EnterTransition enter, optional androidx.compose.animation.ExitTransition exit, kotlin.jvm.functions.Function1<? super androidx.compose.animation.AnimatedVisibilityScope,kotlin.Unit> content);
+    method @androidx.compose.animation.ExperimentalAnimationApi @androidx.compose.runtime.Composable public static void AnimatedVisibility(androidx.compose.foundation.layout.ColumnScope, androidx.compose.animation.core.MutableTransitionState<java.lang.Boolean> visibleState, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.animation.EnterTransition enter, optional androidx.compose.animation.ExitTransition exit, kotlin.jvm.functions.Function1<? super androidx.compose.animation.AnimatedVisibilityScope,kotlin.Unit> content);
+    method @androidx.compose.animation.ExperimentalAnimationApi @androidx.compose.runtime.Composable public static <T> void AnimatedVisibility(androidx.compose.animation.core.Transition<T>, kotlin.jvm.functions.Function1<? super T,java.lang.Boolean> visible, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.animation.EnterTransition enter, optional androidx.compose.animation.ExitTransition exit, kotlin.jvm.functions.Function1<? super androidx.compose.animation.AnimatedVisibilityScope,kotlin.Unit> content);
+    method @Deprecated @androidx.compose.animation.ExperimentalAnimationApi @androidx.compose.runtime.Composable public static void AnimatedVisibility(boolean visible, optional androidx.compose.ui.Modifier modifier, androidx.compose.animation.EnterTransition enter, androidx.compose.animation.ExitTransition exit, boolean initiallyVisible, kotlin.jvm.functions.Function0<kotlin.Unit> content);
+  }
+
+  @androidx.compose.animation.ExperimentalAnimationApi public final class AnimatedVisibilityScope {
+    method public androidx.compose.ui.Modifier animateEnterExit(androidx.compose.ui.Modifier, optional androidx.compose.animation.EnterTransition enter, optional androidx.compose.animation.ExitTransition exit);
+    method public androidx.compose.animation.core.Transition<androidx.compose.animation.EnterExitState> getTransition();
+    property public final androidx.compose.animation.core.Transition<androidx.compose.animation.EnterExitState> transition;
   }
 
   public final class AnimationModifierKt {
@@ -23,6 +34,12 @@
     method @androidx.compose.runtime.Composable public static <T> void Crossfade(T? targetState, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.animation.core.FiniteAnimationSpec<java.lang.Float> animationSpec, kotlin.jvm.functions.Function1<? super T,kotlin.Unit> content);
   }
 
+  @androidx.compose.animation.ExperimentalAnimationApi public enum EnterExitState {
+    enum_constant public static final androidx.compose.animation.EnterExitState PostExit;
+    enum_constant public static final androidx.compose.animation.EnterExitState PreEnter;
+    enum_constant public static final androidx.compose.animation.EnterExitState Visible;
+  }
+
   public final class EnterExitTransitionKt {
     method @androidx.compose.animation.ExperimentalAnimationApi @androidx.compose.runtime.Stable public static androidx.compose.animation.EnterTransition expandHorizontally(optional androidx.compose.ui.Alignment.Horizontal expandFrom, optional kotlin.jvm.functions.Function1<? super java.lang.Integer,java.lang.Integer> initialWidth, optional androidx.compose.animation.core.FiniteAnimationSpec<androidx.compose.ui.unit.IntSize> animationSpec, optional boolean clip);
     method @androidx.compose.animation.ExperimentalAnimationApi @androidx.compose.runtime.Stable public static androidx.compose.animation.EnterTransition expandIn(optional androidx.compose.ui.Alignment expandFrom, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.unit.IntSize,androidx.compose.ui.unit.IntSize> initialSize, optional androidx.compose.animation.core.FiniteAnimationSpec<androidx.compose.ui.unit.IntSize> animationSpec, optional boolean clip);
@@ -42,10 +59,22 @@
 
   @androidx.compose.animation.ExperimentalAnimationApi @androidx.compose.runtime.Immutable public abstract sealed class EnterTransition {
     method @androidx.compose.runtime.Stable public final operator androidx.compose.animation.EnterTransition plus(androidx.compose.animation.EnterTransition enter);
+    field public static final androidx.compose.animation.EnterTransition.Companion Companion;
+  }
+
+  public static final class EnterTransition.Companion {
+    method public androidx.compose.animation.EnterTransition getNone();
+    property public final androidx.compose.animation.EnterTransition None;
   }
 
   @androidx.compose.animation.ExperimentalAnimationApi @androidx.compose.runtime.Immutable public abstract sealed class ExitTransition {
     method @androidx.compose.runtime.Stable public final operator androidx.compose.animation.ExitTransition plus(androidx.compose.animation.ExitTransition exit);
+    field public static final androidx.compose.animation.ExitTransition.Companion Companion;
+  }
+
+  public static final class ExitTransition.Companion {
+    method public androidx.compose.animation.ExitTransition getNone();
+    property public final androidx.compose.animation.ExitTransition None;
   }
 
   @kotlin.RequiresOptIn(message="This is an experimental animation API.") @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget, kotlin.annotation.AnnotationTarget, kotlin.annotation.AnnotationTarget}) public @interface ExperimentalAnimationApi {
diff --git a/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/AnimateContentSizeDemo.kt b/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/AnimateContentSizeDemo.kt
index 24e6a0f..31d9ea7 100644
--- a/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/AnimateContentSizeDemo.kt
+++ b/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/AnimateContentSizeDemo.kt
@@ -52,7 +52,7 @@
             .fillMaxWidth()
             .padding(50.dp)
     ) {
-        Text()
+        MyText()
         Spacer(Modifier.requiredHeight(20.dp))
         Button()
         Spacer(Modifier.requiredHeight(20.dp))
@@ -61,7 +61,7 @@
 }
 
 @Composable
-private fun Text() {
+private fun MyText() {
     val shortText = "Click me"
     val longText = "Very long text\nthat spans across\nmultiple lines"
     var short by remember { mutableStateOf(true) }
diff --git a/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/AnimatedVisibilityDemo.kt b/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/AnimatedVisibilityDemo.kt
index d4f9186..f1326e7 100644
--- a/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/AnimatedVisibilityDemo.kt
+++ b/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/AnimatedVisibilityDemo.kt
@@ -34,12 +34,13 @@
 import androidx.compose.animation.slideOutHorizontally
 import androidx.compose.animation.slideOutVertically
 import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Arrangement
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.Row
 import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.foundation.layout.requiredHeight
 import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.requiredHeight
 import androidx.compose.foundation.selection.selectable
 import androidx.compose.material.Button
 import androidx.compose.material.Checkbox
@@ -54,6 +55,8 @@
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.unit.dp
+import kotlin.math.max
+import kotlin.math.min
 
 @Composable
 fun AnimatedVisibilityDemo() {
@@ -68,60 +71,67 @@
 @OptIn(ExperimentalAnimationApi::class)
 @Composable
 fun AnimatedItems(animateContentSize: Boolean) {
-    var counter by remember { mutableStateOf(0) }
-    Box(
-        Modifier.padding(bottom = 20.dp)
-    ) {
-
-        val modifier = if (animateContentSize) Modifier.animateContentSize() else Modifier
-        Column(
-            Modifier.background(Color.LightGray).fillMaxWidth().then(modifier)
+    var itemNum by remember { mutableStateOf(0) }
+    Column {
+        Row(
+            Modifier.fillMaxWidth().padding(20.dp),
+            horizontalArrangement = Arrangement.SpaceBetween
         ) {
-            val itemNum = if (counter >= 7) 12 - counter else counter
-
-            AnimatedVisibility(visible = itemNum > 0) {
-                Item(
-                    pastelColors[0],
-                    "Expand Vertically + Fade In\nShrink " +
-                        "Vertically + Fade Out\n(Column Default)"
-                )
+            Button(onClick = { itemNum = min((itemNum + 1), 6) }) {
+                Text("Add")
             }
-            HorizontalTransition(visible = itemNum > 1) {
-                Item(pastelColors[1], "Expand Horizontally\nShrink Horizontally")
-            }
-            SlideTransition(visible = itemNum > 2) {
-                Item(
-                    pastelColors[2],
-                    "Slide In Horizontally + Fade In\nSlide Out Horizontally + " +
-                        "Fade Out"
-                )
-            }
-            AnimatedVisibility(
-                visible = itemNum > 3,
-                enter = expandVertically(),
-                exit = shrinkVertically()
-            ) {
-                Item(pastelColors[3], "Expand Vertically\nShrink Vertically")
-            }
-            FadeTransition(visible = itemNum > 4) {
-                Item(pastelColors[4], "Fade In\nFade Out")
-            }
-            FullyLoadedTransition(visible = itemNum > 5) {
-                Item(
-                    pastelColors[0],
-                    "Expand Vertically + Fade In + Slide In Vertically\n" +
-                        "Shrink Vertically + Fade Out + Slide Out Vertically"
-                )
+            Button(onClick = { itemNum = max((itemNum - 1), 0) }) {
+                Text("Remove")
             }
         }
-
-        Button(
-            modifier = Modifier.align(Alignment.TopEnd).padding(10.dp),
-            onClick = {
-                counter = (counter + 1) % 12
-            }
+        Box(
+            Modifier.padding(bottom = 20.dp)
         ) {
-            Text("Click Me")
+
+            val modifier = if (animateContentSize) Modifier.animateContentSize() else Modifier
+            Column(
+                Modifier.background(Color.LightGray).fillMaxWidth().then(modifier)
+            ) {
+
+                Column(
+                    Modifier.background(Color.LightGray).fillMaxWidth().then(modifier)
+                ) {
+                    AnimatedVisibility(visible = itemNum > 0) {
+                        Item(
+                            pastelColors[0],
+                            "Expand Vertically + Fade In\nShrink " +
+                                "Vertically + Fade Out\n(Column Default)"
+                        )
+                    }
+                    HorizontalTransition(visible = itemNum > 1) {
+                        Item(pastelColors[1], "Expand Horizontally\nShrink Horizontally")
+                    }
+                    SlideTransition(visible = itemNum > 2) {
+                        Item(
+                            pastelColors[2],
+                            "Slide In Horizontally + Fade In\nSlide Out Horizontally + " +
+                                "Fade Out"
+                        )
+                    }
+                    AnimatedVisibility(
+                        visible = itemNum > 3,
+                        enter = expandVertically(),
+                        exit = shrinkVertically()
+                    ) {
+                        Item(pastelColors[3], "Expand Vertically\nShrink Vertically")
+                    }
+                    FadeTransition(visible = itemNum > 4) {
+                        Item(pastelColors[4], "Fade In\nFade Out")
+                    }
+                    FullyLoadedTransition(visible = itemNum > 5) {
+                        Item(
+                            pastelColors[0],
+                            "Expand Vertically + Fade In + Slide In Vertically\n" +
+                                "Shrink Vertically + Fade Out + Slide Out Vertically"
+                        )
+                    }
+                }
+            }
         }
     }
 }
@@ -164,9 +174,10 @@
             targetWidth = { fullWidth -> fullWidth / 10 },
             // Overwrites the default animation with tween for this shrink animation.
             animationSpec = tween(durationMillis = 400)
-        ) + fadeOut(),
-        content = content
-    )
+        ) + fadeOut()
+    ) {
+        content()
+    }
 }
 
 @OptIn(ExperimentalAnimationApi::class)
@@ -187,9 +198,10 @@
             // Overwrites the ending position of the slide-out to 200 (pixels) to the right
             targetOffsetX = { 200 },
             animationSpec = spring(stiffness = Spring.StiffnessHigh)
-        ) + fadeOut(),
-        content = content
-    )
+        ) + fadeOut()
+    ) {
+        content()
+    }
 }
 
 @OptIn(ExperimentalAnimationApi::class)
@@ -204,9 +216,10 @@
         exit = fadeOut(
             // Overwrites the default animation with tween
             animationSpec = tween(durationMillis = 250)
-        ),
-        content = content
-    )
+        )
+    ) {
+        content()
+    }
 }
 
 @OptIn(ExperimentalAnimationApi::class)
@@ -221,7 +234,8 @@
         ) + expandVertically(
             expandFrom = Alignment.Top
         ) + fadeIn(initialAlpha = 0.3f),
-        exit = slideOutVertically() + shrinkVertically() + fadeOut(),
-        content = content
-    )
+        exit = slideOutVertically() + shrinkVertically() + fadeOut()
+    ) {
+        content()
+    }
 }
diff --git a/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/AnimatedVisiblilityLazyColumnDemo.kt b/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/AnimatedVisiblilityLazyColumnDemo.kt
index 96ed035..ef08514 100644
--- a/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/AnimatedVisiblilityLazyColumnDemo.kt
+++ b/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/AnimatedVisiblilityLazyColumnDemo.kt
@@ -18,65 +18,77 @@
 
 import androidx.compose.animation.AnimatedVisibility
 import androidx.compose.animation.ExperimentalAnimationApi
+import androidx.compose.animation.core.ExperimentalTransitionApi
+import androidx.compose.animation.core.MutableTransitionState
 import androidx.compose.animation.expandVertically
 import androidx.compose.animation.shrinkVertically
 import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.Row
-import androidx.compose.foundation.layout.Spacer
 import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.foundation.layout.requiredHeight
 import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.requiredHeight
 import androidx.compose.foundation.lazy.LazyColumn
-import androidx.compose.foundation.lazy.itemsIndexed
+import androidx.compose.foundation.lazy.items
 import androidx.compose.material.Button
 import androidx.compose.material.Text
 import androidx.compose.runtime.Composable
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.mutableStateListOf
 import androidx.compose.runtime.remember
-import androidx.compose.runtime.setValue
+import androidx.compose.runtime.snapshotFlow
+import androidx.compose.ui.Alignment.Companion.CenterEnd
 import androidx.compose.ui.Alignment.Companion.End
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.unit.dp
+import kotlinx.coroutines.flow.collect
 
-@OptIn(ExperimentalAnimationApi::class)
+@OptIn(ExperimentalAnimationApi::class, ExperimentalTransitionApi::class)
 @Composable
 fun AnimatedVisibilityLazyColumnDemo() {
-    var itemNum by remember { mutableStateOf(0) }
     Column {
+        val model = remember { MyModel() }
         Row(Modifier.fillMaxWidth()) {
             Button(
-                { itemNum = itemNum + 1 },
-                enabled = itemNum <= turquoiseColors.size - 1,
+                { model.addNewItem() },
                 modifier = Modifier.padding(15.dp).weight(1f)
             ) {
                 Text("Add")
             }
+        }
 
-            Button(
-                { itemNum = itemNum - 1 },
-                enabled = itemNum >= 1,
-                modifier = Modifier.padding(15.dp).weight(1f)
-            ) {
-                Text("Remove")
+        LaunchedEffect(model) {
+            snapshotFlow {
+                model.items.firstOrNull { it.visible.isIdle && !it.visible.targetState }
+            }.collect {
+                if (it != null) {
+                    model.pruneItems()
+                }
             }
         }
         LazyColumn {
-            itemsIndexed(turquoiseColors) { i, color ->
+            items(model.items, key = { it.itemId }) { item ->
                 AnimatedVisibility(
-                    (turquoiseColors.size - itemNum) <= i,
+                    item.visible,
                     enter = expandVertically(),
                     exit = shrinkVertically()
                 ) {
-                    Spacer(Modifier.fillMaxWidth().requiredHeight(90.dp).background(color))
+                    Box(Modifier.fillMaxWidth().requiredHeight(90.dp).background(item.color)) {
+                        Button(
+                            { model.removeItem(item) },
+                            modifier = Modifier.align(CenterEnd).padding(15.dp)
+                        ) {
+                            Text("Remove")
+                        }
+                    }
                 }
             }
         }
 
         Button(
-            { itemNum = 0 },
+            { model.removeAll() },
             modifier = Modifier.align(End).padding(15.dp)
         ) {
             Text("Clear All")
@@ -84,6 +96,44 @@
     }
 }
 
+private class MyModel {
+    private val _items: MutableList<ColoredItem> = mutableStateListOf()
+    private var lastItemId = 0
+    val items: List<ColoredItem> = _items
+
+    class ColoredItem(val visible: MutableTransitionState<Boolean>, val itemId: Int) {
+        val color: Color
+            get() = turquoiseColors.let {
+                it[itemId % it.size]
+            }
+    }
+
+    fun addNewItem() {
+        lastItemId++
+        _items.add(
+            ColoredItem(
+                MutableTransitionState(false).apply { targetState = true },
+                lastItemId
+            )
+        )
+    }
+
+    fun removeItem(item: ColoredItem) {
+        item.visible.targetState = false
+    }
+
+    @OptIn(ExperimentalTransitionApi::class)
+    fun pruneItems() {
+        _items.removeAll(items.filter { it.visible.isIdle && !it.visible.targetState })
+    }
+
+    fun removeAll() {
+        _items.forEach {
+            it.visible.targetState = false
+        }
+    }
+}
+
 private val turquoiseColors = listOf(
     Color(0xff07688C),
     Color(0xff1986AF),
diff --git a/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/AnimationDemos.kt b/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/AnimationDemos.kt
index a8aff2e..1160261 100644
--- a/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/AnimationDemos.kt
+++ b/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/AnimationDemos.kt
@@ -44,7 +44,10 @@
                     AnimatedVisibilityContentSizeChangeDemo()
                 },
                 ComposableDemo("Cross Fade") { CrossfadeDemo() },
-                ComposableDemo("Enter/Exit Transition Demo") { EnterExitTransitionDemo() },
+                ComposableDemo("Enter/ExitTransition Combo Demo") { EnterExitCombinationDemo() },
+                ComposableDemo("Sequential Enter/Exit Demo") {
+                    SequentialEnterExitDemo()
+                },
             )
         ),
         DemoCategory(
diff --git a/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/EnterExitTransitionDemo.kt b/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/EnterExitCombinationDemo.kt
similarity index 99%
rename from compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/EnterExitTransitionDemo.kt
rename to compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/EnterExitCombinationDemo.kt
index cccb234..f25a6fb 100644
--- a/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/EnterExitTransitionDemo.kt
+++ b/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/EnterExitCombinationDemo.kt
@@ -79,7 +79,7 @@
 import androidx.compose.ui.unit.dp
 
 @Composable
-fun EnterExitTransitionDemo() {
+fun EnterExitCombinationDemo() {
     Column(Modifier.fillMaxWidth().padding(top = 20.dp)) {
         val oppositeAlignment = remember { mutableStateOf(true) }
 
diff --git a/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/SequentialEnterExitDemo.kt b/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/SequentialEnterExitDemo.kt
new file mode 100644
index 0000000..e769f8b
--- /dev/null
+++ b/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/SequentialEnterExitDemo.kt
@@ -0,0 +1,153 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.animation.demos
+
+import androidx.compose.animation.AnimatedVisibility
+import androidx.compose.animation.AnimatedVisibilityScope
+import androidx.compose.animation.EnterExitState
+import androidx.compose.animation.ExitTransition
+import androidx.compose.animation.ExperimentalAnimationApi
+import androidx.compose.animation.core.ExperimentalTransitionApi
+import androidx.compose.animation.core.MutableTransitionState
+import androidx.compose.animation.core.animateFloat
+import androidx.compose.animation.core.tween
+import androidx.compose.animation.expandVertically
+import androidx.compose.animation.fadeIn
+import androidx.compose.animation.fadeOut
+import androidx.compose.animation.slideInVertically
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material.Button
+import androidx.compose.material.FloatingActionButton
+import androidx.compose.material.Icon
+import androidx.compose.material.MaterialTheme
+import androidx.compose.material.Text
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Favorite
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.graphicsLayer
+import androidx.compose.ui.text.font.FontWeight.Companion.Bold
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+
+/*
+ * This demo shows how to create a Transition<EnterExitState> with MutableTransitionState, and
+ * use the Transition<EnterExitState> to animate a few enter/exit transitions together. The
+ * MutableTransitionState is then used to add sequential enter/exit transitions.
+ *
+ *  APIs used:
+ * - updateTransition
+ * - MutableTransitionState
+ * - EnterExitState
+ */
+@OptIn(ExperimentalAnimationApi::class, ExperimentalTransitionApi::class)
+@Composable
+fun SequentialEnterExitDemo() {
+    Box {
+        var mainContentVisible by remember {
+            mutableStateOf(MutableTransitionState(true))
+        }
+        Column(Modifier.fillMaxSize()) {
+            Spacer(Modifier.size(40.dp))
+            Row(
+                modifier = Modifier.fillMaxWidth(),
+                horizontalArrangement = Arrangement.SpaceAround
+            ) {
+                // New MutableTransitionState instance here. This should reset the animation.
+                Button(onClick = { mainContentVisible = MutableTransitionState(false) }) {
+                    Text("Reset")
+                }
+
+                Button(
+                    onClick = { mainContentVisible.targetState = !mainContentVisible.targetState },
+                ) {
+                    Text("Toggle Visibility")
+                }
+            }
+            Spacer(Modifier.size(40.dp))
+            AnimatedVisibility(
+                visibleState = mainContentVisible,
+                modifier = Modifier.fillMaxSize(),
+                enter = fadeIn(),
+                exit = fadeOut()
+            ) {
+                Box {
+                    Column(Modifier.fillMaxSize()) {
+                        Item(Modifier.weight(1f), backgroundColor = Color(0xffff6f69))
+                        Item(Modifier.weight(1f), backgroundColor = Color(0xffffcc5c))
+                    }
+                    FloatingActionButton(
+                        onClick = {},
+                        modifier = Modifier.align(Alignment.BottomEnd).padding(20.dp),
+                        backgroundColor = MaterialTheme.colors.primary
+                    ) {
+                        Icon(Icons.Default.Favorite, contentDescription = null)
+                    }
+                }
+            }
+        }
+        AnimatedVisibility(
+            visible = mainContentVisible.targetState && mainContentVisible.isIdle,
+            modifier = Modifier.align(Alignment.Center),
+            enter = expandVertically(),
+            exit = fadeOut(animationSpec = tween(50))
+        ) {
+            Text("Transition Finished", color = Color.White, fontSize = 40.sp, fontWeight = Bold)
+        }
+    }
+}
+
+@OptIn(ExperimentalAnimationApi::class)
+@Composable
+private fun AnimatedVisibilityScope.Item(
+    modifier: Modifier,
+    backgroundColor: Color
+) {
+    val scale by transition.animateFloat { enterExitState ->
+        when (enterExitState) {
+            EnterExitState.PreEnter -> 0.9f
+            EnterExitState.Visible -> 1.0f
+            EnterExitState.PostExit -> 0.5f
+        }
+    }
+    Box(
+        modifier.fillMaxWidth().padding(5.dp).animateEnterExit(
+            enter = slideInVertically(initialOffsetY = { it }),
+            exit = ExitTransition.None
+        ).graphicsLayer {
+            scaleX = scale
+            scaleY = scale
+        }.clip(RoundedCornerShape(20.dp)).background(backgroundColor).fillMaxSize()
+    ) {}
+}
diff --git a/compose/animation/animation/samples/src/main/java/androidx/compose/animation/samples/AnimatedVisibilitySamples.kt b/compose/animation/animation/samples/src/main/java/androidx/compose/animation/samples/AnimatedVisibilitySamples.kt
index e065cf5..2999fd4 100644
--- a/compose/animation/animation/samples/src/main/java/androidx/compose/animation/samples/AnimatedVisibilitySamples.kt
+++ b/compose/animation/animation/samples/src/main/java/androidx/compose/animation/samples/AnimatedVisibilitySamples.kt
@@ -18,12 +18,21 @@
 
 import androidx.annotation.Sampled
 import androidx.compose.animation.AnimatedVisibility
+import androidx.compose.animation.AnimatedVisibilityScope
+import androidx.compose.animation.EnterExitState
+import androidx.compose.animation.ExitTransition
 import androidx.compose.animation.ExperimentalAnimationApi
+import androidx.compose.animation.animateColor
+import androidx.compose.animation.core.ExperimentalTransitionApi
 import androidx.compose.animation.core.FastOutSlowInEasing
 import androidx.compose.animation.core.LinearOutSlowInEasing
+import androidx.compose.animation.core.MutableTransitionState
 import androidx.compose.animation.core.Spring
+import androidx.compose.animation.core.animateDp
+import androidx.compose.animation.core.animateFloat
 import androidx.compose.animation.core.spring
 import androidx.compose.animation.core.tween
+import androidx.compose.animation.core.updateTransition
 import androidx.compose.animation.expandHorizontally
 import androidx.compose.animation.expandIn
 import androidx.compose.animation.expandVertically
@@ -38,32 +47,52 @@
 import androidx.compose.animation.slideOut
 import androidx.compose.animation.slideOutHorizontally
 import androidx.compose.animation.slideOutVertically
+import androidx.compose.foundation.BorderStroke
 import androidx.compose.foundation.background
 import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.Arrangement
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.ColumnScope
 import androidx.compose.foundation.layout.Row
 import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.foundation.layout.requiredHeight
+import androidx.compose.foundation.layout.height
 import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.requiredHeight
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.lazy.items
+import androidx.compose.foundation.shape.CircleShape
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material.Button
 import androidx.compose.material.FloatingActionButton
 import androidx.compose.material.Icon
+import androidx.compose.material.MaterialTheme
+import androidx.compose.material.Surface
 import androidx.compose.material.Text
 import androidx.compose.material.icons.Icons
 import androidx.compose.material.icons.filled.Favorite
 import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
 import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateListOf
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
 import androidx.compose.runtime.setValue
+import androidx.compose.runtime.snapshotFlow
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
 import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.graphicsLayer
+import androidx.compose.ui.text.style.TextAlign
 import androidx.compose.ui.unit.IntOffset
 import androidx.compose.ui.unit.IntSize
 import androidx.compose.ui.unit.dp
+import kotlinx.coroutines.flow.collect
 
 @OptIn(ExperimentalAnimationApi::class)
 @Sampled
@@ -163,6 +192,51 @@
 @OptIn(ExperimentalAnimationApi::class)
 @Sampled
 @Composable
+fun AnimatedVisibilityWithBooleanVisibleParamNoReceiver() {
+    var visible by remember { mutableStateOf(true) }
+    Box(modifier = Modifier.clickable { visible = !visible }) {
+        AnimatedVisibility(
+            visible = visible,
+            modifier = Modifier.align(Alignment.Center),
+            enter = slideInVertically(
+                // Start the slide from 40 (pixels) above where the content is supposed to go, to
+                // produce a parallax effect
+                initialOffsetY = { -40 }
+            ) + expandVertically(
+                expandFrom = Alignment.Top
+            ) + fadeIn(initialAlpha = 0.3f),
+            exit = shrinkVertically() + fadeOut(animationSpec = tween(200))
+        ) { // Content that needs to appear/disappear goes here:
+            // Here we can optionally define a custom enter/exit animation by creating an animation
+            // using the Transition<EnterExitState> object from AnimatedVisibilityScope:
+            val scale by transition.animateFloat {
+                when (it) {
+                    // Scale will be animating from 0.8f to 1.0f during enter transition.
+                    EnterExitState.PreEnter -> 0.8f
+                    EnterExitState.Visible -> 1f
+                    // Scale will be animating from 1.0f to 1.2f during exit animation. If the
+                    // targetValue specified for PreEnter is the same as PostExit, the enter and
+                    // exit animation for this property will be symmetric.
+                    EnterExitState.PostExit -> 1.2f
+                }
+            }
+            Text(
+                "Content to appear/disappear",
+                Modifier.fillMaxWidth().requiredHeight(100.dp).background(Color(0xffa1feff))
+                    .graphicsLayer {
+                        // Apply our custom enter/exit transition
+                        scaleX = scale
+                        scaleY = scale
+                    }.padding(top = 20.dp),
+                textAlign = TextAlign.Center
+            )
+        }
+    }
+}
+
+@OptIn(ExperimentalAnimationApi::class)
+@Sampled
+@Composable
 fun ColumnScope.AnimatedFloatingActionButton() {
     var expanded by remember { mutableStateOf(true) }
     FloatingActionButton(
@@ -290,3 +364,340 @@
         }
     }
 }
+
+@Sampled
+@OptIn(ExperimentalAnimationApi::class, ExperimentalTransitionApi::class)
+@Composable
+fun AVScopeAnimateEnterExit() {
+    @OptIn(ExperimentalAnimationApi::class)
+    @Composable
+    fun AnimatedVisibilityScope.Item(
+        modifier: Modifier,
+        backgroundColor: Color
+    ) {
+        // Creates a custom enter/exit animation for scale property.
+        val scale by transition.animateFloat { enterExitState ->
+            // Enter transition will be animating the scale from 0.9f to 1.0f
+            // (i.e. PreEnter -> Visible). Exit transition will be from 1.0f to
+            // 0.5f (i.e. Visible -> PostExit)
+            when (enterExitState) {
+                EnterExitState.PreEnter -> 0.9f
+                EnterExitState.Visible -> 1.0f
+                EnterExitState.PostExit -> 0.5f
+            }
+        }
+
+        // Since we defined `Item` as an extension function on AnimatedVisibilityScope, we can use
+        // the `animateEnterExit` modifier to produce an enter/exit animation for it. This will
+        // run simultaneously with the `AnimatedVisibility`'s enter/exit.
+        Box(
+            modifier.fillMaxWidth().padding(5.dp).animateEnterExit(
+                // Slide in from below,
+                enter = slideInVertically(initialOffsetY = { it }),
+                // No slide on the way out. So the exit animation will be scale (from the custom
+                // scale animation defined above) and fade (from AnimatedVisibility)
+                exit = ExitTransition.None
+            ).graphicsLayer {
+                scaleX = scale
+                scaleY = scale
+            }.clip(RoundedCornerShape(20.dp)).background(backgroundColor).fillMaxSize()
+        ) {
+            // Content of the item goes here...
+        }
+    }
+
+    @OptIn(ExperimentalAnimationApi::class, ExperimentalTransitionApi::class)
+    @Composable
+    fun AnimateMainContent(mainContentVisible: MutableTransitionState<Boolean>) {
+        Box {
+            // Use the `MutableTransitionState<Boolean>` to specify whether AnimatedVisibility
+            // should be visible. This will also allow AnimatedVisibility animation states to be
+            // observed externally.
+            AnimatedVisibility(
+                visibleState = mainContentVisible,
+                modifier = Modifier.fillMaxSize(),
+                enter = fadeIn(),
+                exit = fadeOut()
+            ) {
+                Box {
+                    Column(Modifier.fillMaxSize()) {
+                        // We have created `Item`s below as extension functions on
+                        // AnimatedVisibilityScope in this example. So they can define their own
+                        // enter/exit to run alongside the enter/exit defined in AnimatedVisibility.
+                        Item(Modifier.weight(1f), backgroundColor = Color(0xffff6f69))
+                        Item(Modifier.weight(1f), backgroundColor = Color(0xffffcc5c))
+                    }
+                    // This FAB will be simply fading in/out as specified by the AnimatedVisibility
+                    FloatingActionButton(
+                        onClick = {},
+                        modifier = Modifier.align(Alignment.BottomEnd).padding(20.dp),
+                        backgroundColor = MaterialTheme.colors.primary
+                    ) { Icon(Icons.Default.Favorite, contentDescription = null) }
+                }
+            }
+
+            // Here we can get a signal for when the Enter/Exit animation of the content above
+            // has finished by inspecting the MutableTransitionState passed to the
+            // AnimatedVisibility. This allows sequential animation after the enter/exit.
+            AnimatedVisibility(
+                // Once the main content is visible (i.e. targetState == true), and no pending
+                // animations. We will start another enter animation sequentially.
+                visible = mainContentVisible.targetState && mainContentVisible.isIdle,
+                modifier = Modifier.align(Alignment.Center),
+                enter = expandVertically(),
+                exit = fadeOut(animationSpec = tween(50))
+            ) {
+                Text("Transition Finished")
+            }
+        }
+    }
+}
+
+@OptIn(ExperimentalAnimationApi::class)
+@Composable
+@Sampled
+fun AddAnimatedVisibilityToGenericTransitionSample() {
+    @Composable
+    fun ItemMainContent() {
+        Row(Modifier.height(100.dp).fillMaxWidth(), Arrangement.SpaceEvenly) {
+            Box(
+                Modifier.size(60.dp).align(Alignment.CenterVertically)
+                    .background(Color(0xffcdb7f6), CircleShape)
+            )
+            Column(Modifier.align(Alignment.CenterVertically)) {
+                Box(Modifier.height(30.dp).width(300.dp).padding(5.dp).background(Color.LightGray))
+                Box(Modifier.height(30.dp).width(300.dp).padding(5.dp).background(Color.LightGray))
+            }
+        }
+    }
+
+    @OptIn(ExperimentalAnimationApi::class)
+    @Composable
+    fun SelectableItem() {
+        // This sample animates a number of properties, including AnimatedVisibility, as a part of
+        // the Transition going between selected and unselected.
+        Box(Modifier.padding(15.dp)) {
+            var selected by remember { mutableStateOf(false) }
+            // Creates a transition to animate visual changes when `selected` is changed.
+            val selectionTransition = updateTransition(selected)
+            // Animates the border color as a part of the transition
+            val borderColor by selectionTransition.animateColor { isSelected ->
+                if (isSelected) Color(0xff03a9f4) else Color.White
+            }
+            // Animates the background color when selected state changes
+            val contentBackground by selectionTransition.animateColor { isSelected ->
+                if (isSelected) Color(0xffdbf0fe) else Color.White
+            }
+            // Animates elevation as a part of the transition
+            val elevation by selectionTransition.animateDp { isSelected ->
+                if (isSelected) 10.dp else 2.dp
+            }
+            Surface(
+                shape = RoundedCornerShape(10.dp),
+                border = BorderStroke(2.dp, borderColor),
+                modifier = Modifier.clickable { selected = !selected },
+                color = contentBackground,
+                elevation = elevation,
+            ) {
+                Column(Modifier.fillMaxWidth()) {
+                    ItemMainContent()
+                    // Creates an AnimatedVisibility as a part of the transition, so that when
+                    // selected it's visible. This will hoist all the animations that are internal
+                    // to AnimatedVisibility (i.e. fade, slide, etc) to the transition. As a result,
+                    // `selectionTransition` will not finish until all the animations in
+                    // AnimatedVisibility as well as animations added directly to it have finished.
+                    selectionTransition.AnimatedVisibility(
+                        visible = { it },
+                        enter = expandVertically(),
+                        exit = shrinkVertically()
+                    ) {
+                        Box(Modifier.fillMaxWidth().padding(10.dp)) {
+                            Text(
+                                "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed" +
+                                    " eiusmod tempor incididunt labore et dolore magna aliqua. " +
+                                    "Ut enim ad minim veniam, quis nostrud exercitation ullamco " +
+                                    "laboris nisi ut aliquip ex ea commodo consequat. Duis aute " +
+                                    "irure dolor."
+                            )
+                        }
+                    }
+                }
+            }
+        }
+    }
+}
+
+@OptIn(ExperimentalAnimationApi::class, ExperimentalTransitionApi::class)
+@Sampled
+@Composable
+fun AnimatedVisibilityLazyColumnSample() {
+    val turquoiseColors = listOf(
+        Color(0xff07688C),
+        Color(0xff1986AF),
+        Color(0xff50B6CD),
+        Color(0xffBCF8FF),
+        Color(0xff8AEAE9),
+        Color(0xff46CECA)
+    )
+
+    // MyModel class handles the data change of the items that are displayed in LazyColumn.
+    class MyModel {
+        private val _items: MutableList<ColoredItem> = mutableStateListOf()
+        private var lastItemId = 0
+        val items: List<ColoredItem> = _items
+
+        // Each item has a MutableTransitionState field to track as well as to mutate the item's
+        // visibility. When the MutableTransitionState's targetState changes, corresponding
+        // transition will be fired. MutableTransitionState allows animation lifecycle to be
+        // observed through it's [currentState] and [isIdle]. See below for details.
+        inner class ColoredItem(val visible: MutableTransitionState<Boolean>, val itemId: Int) {
+            val color: Color
+                get() = turquoiseColors.let {
+                    it[itemId % it.size]
+                }
+        }
+
+        fun addNewItem() {
+            lastItemId++
+            _items.add(
+                ColoredItem(
+                    // Here the initial state of the MutableTransitionState is set to false, and
+                    // target state is set to true. This will result in an enter transition for
+                    // the newly added item.
+                    MutableTransitionState(false).apply { targetState = true },
+                    lastItemId
+                )
+            )
+        }
+
+        fun removeItem(item: ColoredItem) {
+            // By setting the targetState to false, this will effectively trigger an exit
+            // animation in AnimatedVisibility.
+            item.visible.targetState = false
+        }
+
+        @OptIn(ExperimentalTransitionApi::class)
+        fun pruneItems() {
+            // Inspect the animation status through MutableTransitionState. If isIdle == true,
+            // all animations have finished for the transition.
+            _items.removeAll(
+                items.filter {
+                    // This checks that the animations have finished && the animations are exit
+                    // transitions. In other words, the item has finished animating out.
+                    it.visible.isIdle && !it.visible.targetState
+                }
+            )
+        }
+
+        fun removeAll() {
+            _items.forEach {
+                it.visible.targetState = false
+            }
+        }
+    }
+
+    @OptIn(ExperimentalAnimationApi::class, ExperimentalTransitionApi::class)
+    @Composable
+    fun AnimatedVisibilityInLazyColumn() {
+        Column {
+            val model = remember { MyModel() }
+            Row(Modifier.fillMaxWidth()) {
+                Button({ model.addNewItem() }, modifier = Modifier.padding(15.dp).weight(1f)) {
+                    Text("Add")
+                }
+            }
+
+            // This sets up a flow to check whether any item has finished animating out. If yes,
+            // notify the model to prune the list.
+            LaunchedEffect(model) {
+                snapshotFlow {
+                    model.items.firstOrNull { it.visible.isIdle && !it.visible.targetState }
+                }.collect {
+                    if (it != null) {
+                        model.pruneItems()
+                    }
+                }
+            }
+            LazyColumn {
+                items(model.items, key = { it.itemId }) { item ->
+                    AnimatedVisibility(
+                        item.visible,
+                        enter = expandVertically(),
+                        exit = shrinkVertically()
+                    ) {
+                        Box(Modifier.fillMaxWidth().requiredHeight(90.dp).background(item.color)) {
+                            Button(
+                                { model.removeItem(item) },
+                                modifier = Modifier.align(Alignment.CenterEnd).padding(15.dp)
+                            ) {
+                                Text("Remove")
+                            }
+                        }
+                    }
+                }
+            }
+
+            Button(
+                { model.removeAll() },
+                modifier = Modifier.align(Alignment.End).padding(15.dp)
+            ) {
+                Text("Clear All")
+            }
+        }
+    }
+}
+
+@Sampled
+@OptIn(ExperimentalAnimationApi::class)
+@Composable
+fun AVColumnScopeWithMutableTransitionState() {
+    var visible by remember { mutableStateOf(true) }
+    val colors = remember { listOf(Color(0xff2a9d8f), Color(0xffe9c46a), Color(0xfff4a261)) }
+    Column {
+        repeat(3) {
+            AnimatedVisibility(
+                visibleState = remember {
+                    // This sets up the initial state of the AnimatedVisibility to false to
+                    // guarantee an initial enter transition. In contrast, initializing this as
+                    // `MutableTransitionState(visible)` would result in no initial enter
+                    // transition.
+                    MutableTransitionState(initialState = false)
+                }.apply {
+                    // This changes the target state of the visible state. If it's different than
+                    // the initial state, an enter/exit transition will be triggered.
+                    targetState = visible
+                },
+            ) { // Content that needs to appear/disappear goes here:
+                Box(Modifier.fillMaxWidth().height(100.dp).background(colors[it]))
+            }
+        }
+    }
+}
+
+@Sampled
+@OptIn(ExperimentalAnimationApi::class)
+@Composable
+fun AnimateEnterExitPartialContent() {
+    @OptIn(ExperimentalAnimationApi::class)
+    @Composable
+    fun FullScreenNotification(visible: Boolean) {
+        AnimatedVisibility(
+            visible = visible,
+            enter = fadeIn(), exit = fadeOut()
+        ) {
+            // Fade in/out the background and foreground
+            Box(Modifier.fillMaxSize().background(Color(0x88000000))) {
+                Box(
+                    Modifier.align(Alignment.TopStart).animateEnterExit(
+                        // Slide in/out the rounded rect
+                        enter = slideInVertically(),
+                        exit = slideOutVertically()
+                    ).clip(RoundedCornerShape(10.dp)).requiredHeight(100.dp)
+                        .fillMaxWidth().background(Color.White)
+                ) {
+                    // Content of the notification goes here
+                }
+            }
+        }
+    }
+}
diff --git a/compose/animation/animation/src/androidAndroidTest/kotlin/androidx/compose/animation/AnimatedVisibilityTest.kt b/compose/animation/animation/src/androidAndroidTest/kotlin/androidx/compose/animation/AnimatedVisibilityTest.kt
index 056774b..f879f76 100644
--- a/compose/animation/animation/src/androidAndroidTest/kotlin/androidx/compose/animation/AnimatedVisibilityTest.kt
+++ b/compose/animation/animation/src/androidAndroidTest/kotlin/androidx/compose/animation/AnimatedVisibilityTest.kt
@@ -16,29 +16,34 @@
 
 package androidx.compose.animation
 
+import androidx.compose.animation.EnterExitState.PostExit
+import androidx.compose.animation.EnterExitState.Visible
+import androidx.compose.animation.core.FastOutLinearInEasing
 import androidx.compose.animation.core.FastOutSlowInEasing
+import androidx.compose.animation.core.InternalAnimationApi
 import androidx.compose.animation.core.LinearOutSlowInEasing
+import androidx.compose.animation.core.MutableTransitionState
 import androidx.compose.animation.core.tween
+import androidx.compose.animation.core.updateTransition
 import androidx.compose.foundation.background
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.requiredSize
 import androidx.compose.foundation.layout.size
 import androidx.compose.runtime.CompositionLocalProvider
 import androidx.compose.runtime.DisposableEffect
+import androidx.compose.runtime.LaunchedEffect
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.setValue
+import androidx.compose.runtime.withFrameNanos
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.layout.onGloballyPositioned
 import androidx.compose.ui.platform.LocalDensity
-import androidx.compose.ui.platform.testTag
 import androidx.compose.ui.test.ExperimentalTestApi
-import androidx.compose.ui.test.captureToImage
 import androidx.compose.ui.test.junit4.createComposeRule
-import androidx.compose.ui.test.onNodeWithTag
 import androidx.compose.ui.unit.Density
 import androidx.compose.ui.unit.IntOffset
 import androidx.compose.ui.unit.IntSize
@@ -49,14 +54,13 @@
 import org.junit.Assert.assertEquals
 import org.junit.Assert.assertFalse
 import org.junit.Assert.assertTrue
-import org.junit.Ignore
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
 
 @RunWith(AndroidJUnit4::class)
 @LargeTest
-@OptIn(ExperimentalTestApi::class)
+@OptIn(ExperimentalTestApi::class, InternalAnimationApi::class)
 class AnimatedVisibilityTest {
 
     @get:Rule
@@ -325,60 +329,181 @@
         }
     }
 
-    @Ignore
-    @OptIn(ExperimentalAnimationApi::class)
+    // Test different animations for fade in and fade out, in a complete run without interruptions
+    @OptIn(ExperimentalAnimationApi::class, InternalAnimationApi::class)
     @Test
     fun animateVisibilityFadeTest() {
         var visible by mutableStateOf(false)
-        val colors = mutableListOf<Int>()
+        val easing = FastOutLinearInEasing
+        val easingOut = FastOutSlowInEasing
+        var alpha by mutableStateOf(0f)
         rule.setContent {
-            Box(Modifier.size(size = 20.dp).background(Color.Black)) {
-                AnimatedVisibility(
-                    visible,
-                    enter = fadeIn(animationSpec = tween(500)),
-                    exit = fadeOut(animationSpec = tween(500)),
-                    modifier = Modifier.testTag("AnimV")
-                ) {
-                    Box(modifier = Modifier.size(size = 20.dp).background(Color.White))
+            AnimatedVisibility(
+                visible,
+                enter = fadeIn(animationSpec = tween(500, easing = easing)),
+                exit = fadeOut(animationSpec = tween(300, easing = easingOut)),
+            ) {
+                Box(modifier = Modifier.size(size = 20.dp).background(Color.White))
+                LaunchedEffect(visible) {
+                    var exit = false
+                    val enterExit = transition
+                    while (true) {
+                        withFrameNanos {
+                            if (enterExit.targetState == Visible) {
+                                alpha = enterExit.animations.firstOrNull {
+                                    it.label == "alpha"
+                                }?.value as Float
+                                val fraction =
+                                    (enterExit.playTimeNanos / 1_000_000) / 500f
+                                if (enterExit.currentState != Visible) {
+                                    assertEquals(easing.transform(fraction), alpha, 0.01f)
+                                } else {
+                                    // When currentState = targetState, the playTime will be reset
+                                    // to 0. So compare alpha against expected visible value.
+                                    assertEquals(1f, alpha)
+                                    exit = true
+                                }
+                            } else if (enterExit.targetState == PostExit) {
+                                alpha = enterExit.animations.firstOrNull {
+                                    it.label == "alpha"
+                                }?.value as Float
+                                val fraction =
+                                    (enterExit.playTimeNanos / 1_000_000) / 300f
+                                if (enterExit.currentState != PostExit) {
+                                    assertEquals(
+                                        1f - easingOut.transform(fraction),
+                                        alpha,
+                                        0.01f
+                                    )
+                                } else {
+                                    // When currentState = targetState, the playTime will be reset
+                                    // to 0. So compare alpha against expected invisible value.
+                                    assertEquals(0f, alpha)
+                                    exit = true
+                                }
+                            } else {
+                                exit = enterExit.currentState == enterExit.targetState
+                            }
+                        }
+                        if (exit) break
+                    }
                 }
             }
         }
         rule.runOnIdle {
             visible = true
         }
-        rule.mainClock.autoAdvance = false
-        while (colors.isEmpty() || colors.last() != 0xffffffff.toInt()) {
-            rule.mainClock.advanceTimeByFrame()
-            rule.onNodeWithTag("AnimV").apply {
-                val data = IntArray(1)
-                data[0] = 0
-                captureToImage().readPixels(data, 10, 10, 1, 1)
-                colors.add(data[0])
-            }
-        }
-        for (i in 1 until colors.size) {
-            // Check every color against the previous one to ensure the alpha is non-decreasing
-            // during fade in.
-            assertTrue(colors[i] >= colors[i - 1])
-        }
-        assertTrue(colors[0] < 0xfffffffff)
-        colors.clear()
         rule.runOnIdle {
+            // At this point fade in has finished, expect alpha = 1
+            assertEquals(1f, alpha)
             visible = false
         }
-        while (colors.isEmpty() || colors.last() != 0xff000000.toInt()) {
-            rule.mainClock.advanceTimeByFrame()
-            rule.onNodeWithTag("AnimV").apply {
-                val data = IntArray(1)
-                data[0] = 0
-                captureToImage().readPixels(data, 10, 10, 1, 1)
-                colors.add(data[0])
+        rule.runOnIdle {
+            // At this point fade out has finished, expect alpha = 0
+            assertEquals(0f, alpha)
+        }
+    }
+
+    @OptIn(ExperimentalAnimationApi::class)
+    @Test
+    fun testEnterTransitionNoneAndExitTransitionNone() {
+        val testModifier by mutableStateOf(TestModifier())
+        val visible = MutableTransitionState(false)
+        var disposed by mutableStateOf(false)
+        rule.mainClock.autoAdvance = false
+        rule.setContent {
+            CompositionLocalProvider(LocalDensity provides Density(1f)) {
+                AnimatedVisibility(
+                    visible, testModifier,
+                    enter = EnterTransition.None,
+                    exit = ExitTransition.None
+                ) {
+                    Box(Modifier.requiredSize(100.dp, 100.dp)) {
+                        DisposableEffect(Unit) {
+                            onDispose {
+                                disposed = true
+                            }
+                        }
+                    }
+                }
             }
         }
-        for (i in 1 until colors.size) {
-            // Check every color against the previous one to ensure the alpha is non-increasing
-            // during fade out.
-            assertTrue(colors[i] <= colors[i - 1])
+
+        rule.runOnIdle {
+            assertEquals(0, testModifier.width)
+            assertEquals(0, testModifier.height)
+            visible.targetState = true
+        }
+        rule.mainClock.advanceTimeByFrame()
+        rule.mainClock.advanceTimeByFrame()
+        rule.runOnIdle {
+            assertEquals(100, testModifier.width)
+            assertEquals(100, testModifier.height)
+            assertFalse(disposed)
+            visible.targetState = false
+        }
+        rule.mainClock.advanceTimeByFrame()
+        rule.mainClock.advanceTimeByFrame()
+        rule.runOnIdle {
+            assertTrue(disposed)
+        }
+    }
+
+    private enum class TestState { State1, State2, State3 }
+
+    @OptIn(ExperimentalAnimationApi::class)
+    @Test
+    fun testTransitionExtensionAnimatedVisibility() {
+        val testModifier by mutableStateOf(TestModifier())
+        val testState = mutableStateOf(TestState.State1)
+        var currentState = TestState.State1
+        var disposed by mutableStateOf(false)
+        rule.mainClock.autoAdvance = false
+        rule.setContent {
+            CompositionLocalProvider(LocalDensity provides Density(1f)) {
+                val transition = updateTransition(testState.value)
+                currentState = transition.currentState
+                transition.AnimatedVisibility(
+                    // Only visible in State2
+                    visible = { it == TestState.State2 },
+                    modifier = testModifier,
+                    enter = expandIn(animationSpec = tween(100)),
+                    exit = shrinkOut(animationSpec = tween(100))
+                ) {
+                    Box(Modifier.requiredSize(100.dp, 100.dp)) {
+                        DisposableEffect(Unit) {
+                            onDispose {
+                                disposed = true
+                            }
+                        }
+                    }
+                }
+            }
+        }
+        rule.runOnIdle {
+            assertEquals(0, testModifier.width)
+            assertEquals(0, testModifier.height)
+            testState.value = TestState.State2
+        }
+        while (currentState != TestState.State2) {
+            assertTrue(testModifier.width < 100)
+            rule.mainClock.advanceTimeByFrame()
+        }
+        rule.runOnIdle {
+            assertEquals(100, testModifier.width)
+            assertEquals(100, testModifier.height)
+            testState.value = TestState.State3
+        }
+        while (currentState != TestState.State3) {
+            assertTrue(testModifier.width > 0)
+            assertFalse(disposed)
+            rule.mainClock.advanceTimeByFrame()
+        }
+        rule.mainClock.advanceTimeByFrame()
+        rule.runOnIdle {
+            assertEquals(0, testModifier.width)
+            assertEquals(0, testModifier.height)
+            assertTrue(disposed)
         }
     }
 }
diff --git a/compose/animation/animation/src/commonMain/kotlin/androidx/compose/animation/AnimatedVisibility.kt b/compose/animation/animation/src/commonMain/kotlin/androidx/compose/animation/AnimatedVisibility.kt
index 252b4b4..1a8e49a 100644
--- a/compose/animation/animation/src/commonMain/kotlin/androidx/compose/animation/AnimatedVisibility.kt
+++ b/compose/animation/animation/src/commonMain/kotlin/androidx/compose/animation/AnimatedVisibility.kt
@@ -16,63 +16,87 @@
 
 package androidx.compose.animation
 
+import androidx.compose.animation.EnterExitState.PostExit
+import androidx.compose.animation.EnterExitState.PreEnter
+import androidx.compose.animation.EnterExitState.Visible
+import androidx.compose.animation.core.ExperimentalTransitionApi
+import androidx.compose.animation.core.MutableTransitionState
+import androidx.compose.animation.core.Transition
+import androidx.compose.animation.core.createChildTransition
+import androidx.compose.animation.core.updateTransition
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.ColumnScope
 import androidx.compose.foundation.layout.Row
 import androidx.compose.foundation.layout.RowScope
 import androidx.compose.runtime.Composable
-import androidx.compose.runtime.getValue
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.key
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
-import androidx.compose.runtime.rememberCoroutineScope
-import androidx.compose.runtime.setValue
+import androidx.compose.runtime.snapshotFlow
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.composed
 import androidx.compose.ui.layout.Layout
-import androidx.compose.ui.unit.IntOffset
-import androidx.compose.ui.unit.IntSize
+import androidx.compose.ui.platform.debugInspectorInfo
 import androidx.compose.ui.util.fastForEach
 import androidx.compose.ui.util.fastMaxBy
+import kotlinx.coroutines.flow.collect
 
 /**
  * [AnimatedVisibility] composable animates the appearance and disappearance of its content, as
  * [visible] value changes. Different [EnterTransition]s and [ExitTransition]s can be defined in
  * [enter] and [exit] for the appearance and disappearance animation. There are 3 types of
- * [EnterTransition] and [ExitTransition]: Fade, Expand/Shrink and Slide. The enter transitions
+ * [EnterTransition] and [ExitTransition]: Fade, Expand/Shrink, and Slide. The enter transitions
  * and exit transitions can be combined using `+`. The order of the combination does not matter,
  * as the transition animations will start simultaneously. See [EnterTransition] and
- * [ExitTransition] for details on the three types of transition. Here's an example of combining
- * all three types of transitions together:
+ * [ExitTransition] for details on the three types of transition.
+ *
+ * Aside from these three types of [EnterTransition] and [ExitTransition], [AnimatedVisibility]
+ * also supports custom enter/exit animations. Some use cases may benefit from custom enter/exit
+ * animations on shape, scale, color, etc. Custom enter/exit animations can be created using the
+ * `Transition<EnterExitState>` object from the [AnimatedVisibilityScope] (i.e.
+ * [AnimatedVisibilityScope.transition]). See the second sample code snippet below for example.
+ * These custom animations will be running alongside of the built-in animations specified in
+ * [enter] and [exit]. In cases where the enter/exit animation needs to be completely customized,
+ * [enter] and/or [exit] can be specified as [EnterTransition.None] and/or [ExitTransition.None]
+ * as needed. [AnimatedVisibility] will wait until *all* of enter/exit animations to finish before
+ * it considers itself idle. [content] will only be removed after all the (built-in and custom)
+ * exit animations have finished.
+ *
+ * [AnimatedVisibility] creates a custom [Layout] for its content. The size of the custom
+ * layout is determined by the largest width and largest height of the children. All children
+ * will be aligned to the top start of the [Layout].
+ *
+ * __Note__: Once the exit transition is finished, the [content] composable will be removed
+ * from the tree, and disposed. If there's a need to observe the state change of the enter/exit
+ * transition and follow up additional action (e.g. remove data, sequential animation, etc),
+ * consider the AnimatedVisibility API variant that takes a [MutableTransitionState] parameter.
+ *
+ * By default, the enter transition will be a combination of [fadeIn] and [expandIn] of the
+ * content from the bottom end. And the exit transition will be shrinking the content towards the
+ * bottom end while fading out (i.e. [fadeOut] + [shrinkOut]). The expanding and shrinking will
+ * likely also animate the parent and siblings if they rely on the size of appearing/disappearing
+ * content. When the [AnimatedVisibility] composable is put in a [Row] or a [Column], the default
+ * enter and exit transitions are tailored to that particular container. See
+ * [RowScope.AnimatedVisibility] and [ColumnScope.AnimatedVisibility] for details.
+ *
+ * Here are two examples of [AnimatedVisibility]: one using the built-in enter/exit transition, the
+ * other using a custom enter/exit animation.
  *
  * @sample androidx.compose.animation.samples.FullyLoadedTransition
  *
- * This composable function creates a custom [Layout] for its content. The size of the custom
- * layout is determined by the largest width and largest height of the children. All children
- * will be arranged in a box (aligned to the top start of the [Layout]).
+ * The example blow shows how a custom enter/exit animation can be created using the Transition
+ * object (i.e. Transition<EnterExitState>) from [AnimatedVisibilityScope].
  *
- * __Note__: Once the exit transition is finished, the [content] composable will be skipped (i.e.
- * the content will be removed from the tree, and disposed).
- *
- * By default, the enter transition will be a combination of fading in and expanding the content in
- * from the bottom end. And the exit transition will be shrinking the content towards the bottom
- * end while fading out. The expanding and shrinking will likely also animate the parent and
- * siblings if they rely on the size of appearing/disappearing content. When the
- * [AnimatedVisibility] composable is put in a [Row] or a [Column], the default enter and exit
- * transitions are tailored to that particular container. See [RowScope.AnimatedVisibility] and
- * [ColumnScope.AnimatedVisibility] for details.
- *
- * [initiallyVisible] defaults to the same value as [visible]. This means when the
- * [AnimatedVisibility] is first added to the tree, there is no appearing animation. If it is
- * desired to show an appearing animation for the first appearance of the content,
- * [initiallyVisible] can be set to false and [visible] to true.
+ * @sample androidx.compose.animation.samples.AnimatedVisibilityWithBooleanVisibleParamNoReceiver
  *
  * @param visible defines whether the content should be visible
  * @param modifier modifier for the [Layout] created to contain the [content]
- * @param enter [EnterTransition]s used for the appearing animation, fading in while expanding by
+ * @param enter EnterTransition(s) used for the appearing animation, fading in while expanding by
  *              default
- * @param exit [ExitTransition](s) used for the disappearing animation, fading out while
+ * @param exit ExitTransition(s) used for the disappearing animation, fading out while
  *             shrinking by default
- * @param initiallyVisible controls whether the first appearance should be animated, defaulting
- *                         to match [visible] (i.e. not animating the first appearance)
+ * @param content Content to appear or disappear based on the value of [visible]
  *
  * @see EnterTransition
  * @see ExitTransition
@@ -80,8 +104,7 @@
  * @see expandIn
  * @see fadeOut
  * @see shrinkOut
- * @see RowScope.AnimatedVisibility
- * @see ColumnScope.AnimatedVisibility
+ * @see AnimatedVisibilityScope
  */
 @ExperimentalAnimationApi
 @Composable
@@ -90,51 +113,63 @@
     modifier: Modifier = Modifier,
     enter: EnterTransition = fadeIn() + expandIn(),
     exit: ExitTransition = shrinkOut() + fadeOut(),
-    initiallyVisible: Boolean = visible,
-    content: @Composable () -> Unit
+    content: @Composable() AnimatedVisibilityScope.() -> Unit
 ) {
-    AnimatedVisibilityImpl(visible, modifier, enter, exit, initiallyVisible, content)
+    val transition = updateTransition(visible)
+    AnimatedEnterExitImpl(transition, { it }, modifier, enter, exit, content)
 }
 
 /**
- * [AnimatedVisibility] composable animates the appearance and disappearance of its content, as
- * [visible] value changes. Different [EnterTransition]s and [ExitTransition]s can be defined in
+ * [RowScope.AnimatedVisibility] composable animates the appearance and disappearance of its
+ * content when the [AnimatedVisibility] is in a [Row]. The default animations are tailored
+ * specific to the [Row] layout. See more details below.
+ *
+ * Different [EnterTransition]s and [ExitTransition]s can be defined in
  * [enter] and [exit] for the appearance and disappearance animation. There are 3 types of
  * [EnterTransition] and [ExitTransition]: Fade, Expand/Shrink and Slide. The enter transitions
  * and exit transitions can be combined using `+`. The order of the combination does not matter,
  * as the transition animations will start simultaneously. See [EnterTransition] and
- * [ExitTransition] for details on the three types of transition. Here's an example of using
- * [RowScope.AnimatedVisibility] in a [Row]:
+ * [ExitTransition] for details on the three types of transition.
  *
- * @sample androidx.compose.animation.samples.AnimatedFloatingActionButton
- *
- * This composable function creates a custom [Layout] for its content. The size of the custom
- * layout is determined by the largest width and largest height of the children. All children
- * will be arranged in a box (aligned to the top start of the [Layout]).
- *
- * __Note__: Once the exit transition is finished, the [content] composable will be skipped (i.e.
- * the content will be removed from the tree, and disposed).
- *
- * By default, the enter transition will be a combination of fading in and expanding the content
- * horizontally. The end of the content will be the leading edge as the content expands to its
- * full width. And the exit transition will be shrinking the content with the end of the
+ * The default [enter] and [exit] transition is configured based on the horizontal layout of a
+ * [Row]. [enter] defaults to a combination of fading in and expanding the content horizontally.
+ * (The end of the content will be the leading edge as the content expands to its
+ * full width.) And [exit] defaults to shrinking the content horizontally with the end of the
  * content being the leading edge while fading out. The expanding and shrinking will likely also
  * animate the parent and siblings in the row since they rely on the size of appearing/disappearing
  * content.
  *
- * [initiallyVisible] defaults to the same value as [visible]. This means when the
- * [AnimatedVisibility] is first added to the tree, there is no appearing animation. If it is
- * desired to show an appearing animation for the first appearance of the content,
- * [initiallyVisible] can be set to false and [visible] to true.
+ * Aside from these three types of [EnterTransition] and [ExitTransition], [AnimatedVisibility]
+ * also supports custom enter/exit animations. Some use cases may benefit from custom enter/exit
+ * animations on shape, scale, color, etc. Custom enter/exit animations can be created using the
+ * `Transition<EnterExitState>` object from the [AnimatedVisibilityScope] (i.e.
+ * [AnimatedVisibilityScope.transition]). See [EnterExitState] for an example of custom animations.
+ * These custom animations will be running along side of the built-in animations specified in
+ * [enter] and [exit]. In cases where the enter/exit animation needs to be completely customized,
+ * [enter] and/or [exit] can be specified as [EnterTransition.None] and/or [ExitTransition.None]
+ * as needed. [AnimatedVisibility] will wait until *all* of enter/exit animations to finish
+ * before it considers itself idle. [content] will only be removed after all the (built-in and
+ * custom) exit animations have finished.
+ *
+ * [AnimatedVisibility] creates a custom [Layout] for its content. The size of the custom
+ * layout is determined by the largest width and largest height of the children. All children
+ * will be aligned to the top start of the [Layout].
+ *
+ * __Note__: Once the exit transition is finished, the [content] composable will be removed
+ * from the tree, and disposed. If there's a need to observe the state change of the enter/exit
+ * transition and follow up additional action (e.g. remove data, sequential animation, etc),
+ * consider the AnimatedVisibility API variant that takes a [MutableTransitionState] parameter.
+ *
+ * Here's an example of using [RowScope.AnimatedVisibility] in a [Row]:
+ * @sample androidx.compose.animation.samples.AnimatedFloatingActionButton
  *
  * @param visible defines whether the content should be visible
  * @param modifier modifier for the [Layout] created to contain the [content]
- * @param enter [EnterTransition]s used for the appearing animation, fading in while expanding
+ * @param enter EnterTransition(s) used for the appearing animation, fading in while expanding
  *              horizontally by default
- * @param exit [ExitTransition](s) used for the disappearing animation, fading out while
+ * @param exit ExitTransition(s) used for the disappearing animation, fading out while
  *             shrinking horizontally by default
- * @param initiallyVisible controls whether the first appearance should be animated, defaulting
- *                         to match [visible] (i.e. not animating the first appearance)
+ * @param content Content to appear or disappear based on the value of [visible]
  *
  * @see EnterTransition
  * @see ExitTransition
@@ -144,6 +179,7 @@
  * @see shrinkOut
  * @see AnimatedVisibility
  * @see ColumnScope.AnimatedVisibility
+ * @see AnimatedVisibilityScope
  */
 @ExperimentalAnimationApi
 @Composable
@@ -152,51 +188,62 @@
     modifier: Modifier = Modifier,
     enter: EnterTransition = fadeIn() + expandHorizontally(),
     exit: ExitTransition = fadeOut() + shrinkHorizontally(),
-    initiallyVisible: Boolean = visible,
-    content: @Composable () -> Unit
+    content: @Composable() AnimatedVisibilityScope.() -> Unit
 ) {
-    AnimatedVisibilityImpl(visible, modifier, enter, exit, initiallyVisible, content)
+    val transition = updateTransition(visible)
+    AnimatedEnterExitImpl(transition, { it }, modifier, enter, exit, content)
 }
 
 /**
- * [AnimatedVisibility] composable animates the appearance and disappearance of its content, as
- * [visible] value changes. Different [EnterTransition]s and [ExitTransition]s can be defined in
+ * [ColumnScope.AnimatedVisibility] composable animates the appearance and disappearance of its
+ * content when the [AnimatedVisibility] is in a [Column]. The default animations are tailored
+ * specific to the [Column] layout. See more details below.
+ *
+ * Different [EnterTransition]s and [ExitTransition]s can be defined in
  * [enter] and [exit] for the appearance and disappearance animation. There are 3 types of
  * [EnterTransition] and [ExitTransition]: Fade, Expand/Shrink and Slide. The enter transitions
  * and exit transitions can be combined using `+`. The order of the combination does not matter,
  * as the transition animations will start simultaneously. See [EnterTransition] and
- * [ExitTransition] for details on the three types of transition. Here's an example of using
- * [ColumnScope.AnimatedVisibility] in a [Column]:
+ * [ExitTransition] for details on the three types of transition.
  *
- * @sample androidx.compose.animation.samples.ColumnAnimatedVisibilitySample
+ * The default [enter] and [exit] transition is configured based on the vertical layout of a
+ * [Column]. [enter] defaults to a combination of fading in and expanding the content vertically.
+ * (The bottom of the content will be the leading edge as the content expands to its full height.)
+ * And the [exit] defaults to shrinking the content vertically with the bottom of the content being
+ * the leading edge while fading out. The expanding and shrinking will likely also animate the
+ * parent and siblings in the column since they rely on the size of appearing/disappearing content.
  *
- * This composable function creates a custom [Layout] for its content. The size of the custom
+ * Aside from these three types of [EnterTransition] and [ExitTransition], [AnimatedVisibility]
+ * also supports custom enter/exit animations. Some use cases may benefit from custom enter/exit
+ * animations on shape, scale, color, etc. Custom enter/exit animations can be created using the
+ * `Transition<EnterExitState>` object from the [AnimatedVisibilityScope] (i.e.
+ * [AnimatedVisibilityScope.transition]). See [EnterExitState] for an example of custom animations.
+ * These custom animations will be running along side of the built-in animations specified in
+ * [enter] and [exit]. In cases where the enter/exit animation needs to be completely customized,
+ * [enter] and/or [exit] can be specified as [EnterTransition.None] and/or [ExitTransition.None]
+ * as needed. [AnimatedVisibility] will wait until *all* of enter/exit animations to finish
+ * before it considers itself idle. [content] will only be removed after all the (built-in and
+ * custom) exit animations have finished.
+ *
+ * [AnimatedVisibility] creates a custom [Layout] for its content. The size of the custom
  * layout is determined by the largest width and largest height of the children. All children
- * will be arranged in a box (aligned to the top start of the [Layout]).
+ * will be aligned to the top start of the [Layout].
  *
- * __Note__: Once the exit transition is finished, the [content] composable will be skipped (i.e.
- * the content will be removed from the tree, and disposed).
+ * __Note__: Once the exit transition is finished, the [content] composable will be removed
+ * from the tree, and disposed. If there's a need to observe the state change of the enter/exit
+ * transition and follow up additional action (e.g. remove data, sequential animation, etc),
+ * consider the AnimatedVisibility API variant that takes a [MutableTransitionState] parameter.
  *
- * By default, the enter transition will be a combination of fading in and expanding the content
- * vertically in the [Column]. The bottom of the content will be the leading edge as the content
- * expands to its full height. And the exit transition will be shrinking the content with the
- * bottom of the content being the leading edge while fading out. The expanding and shrinking will
- * likely also animate the parent and siblings in the column since they rely on the size of
- * appearing/disappearing content.
- *
- * [initiallyVisible] defaults to the same value as [visible]. This means when the
- * [AnimatedVisibility] is first added to the tree, there is no appearing animation. If it is
- * desired to show an appearing animation for the first appearance of the content,
- * [initiallyVisible] can be set to false and [visible] to true.
+ * Here's an example of using [ColumnScope.AnimatedVisibility] in a [Column]:
+ * @sample androidx.compose.animation.samples.ColumnAnimatedVisibilitySample
  *
  * @param visible defines whether the content should be visible
  * @param modifier modifier for the [Layout] created to contain the [content]
- * @param enter [EnterTransition]s used for the appearing animation, fading in while expanding
+ * @param enter EnterTransition(s) used for the appearing animation, fading in while expanding
  *              vertically by default
- * @param exit [ExitTransition](s) used for the disappearing animation, fading out while
+ * @param exit ExitTransition(s) used for the disappearing animation, fading out while
  *             shrinking vertically by default
- * @param initiallyVisible controls whether the first appearance should be animated, defaulting
- *                         to match [visible] (i.e. not animating the first appearance)
+ * @param content Content to appear or disappear based on the value of [visible]
  *
  * @see EnterTransition
  * @see ExitTransition
@@ -205,7 +252,7 @@
  * @see fadeOut
  * @see shrinkOut
  * @see AnimatedVisibility
- * @see ColumnScope.AnimatedVisibility
+ * @see AnimatedVisibilityScope
  */
 @ExperimentalAnimationApi
 @Composable
@@ -214,97 +261,529 @@
     modifier: Modifier = Modifier,
     enter: EnterTransition = fadeIn() + expandVertically(),
     exit: ExitTransition = fadeOut() + shrinkVertically(),
-    initiallyVisible: Boolean = visible,
-    content: @Composable () -> Unit
+    content: @Composable AnimatedVisibilityScope.() -> Unit
 ) {
-    AnimatedVisibilityImpl(visible, modifier, enter, exit, initiallyVisible, content)
+    val transition = updateTransition(visible)
+    AnimatedEnterExitImpl(transition, { it }, modifier, enter, exit, content)
+}
+
+/**
+ * [EnterExitState] contains the three states that are involved in the enter and exit transition
+ * of [AnimatedVisibility]. More specifically, [PreEnter] and [Visible] defines the initial and
+ * target state of an *enter* transition, whereas [Visible] and [PostExit] are the initial and
+ * target state of an *exit* transition.
+ *
+ * See blow for an example of custom enter/exit animation in [AnimatedVisibility] using
+ * `Transition<EnterExitState>` (i.e. [AnimatedVisibilityScope.transition]):
+ *
+ * @sample androidx.compose.animation.samples.AnimatedVisibilityWithBooleanVisibleParamNoReceiver
+ * @see AnimatedVisibility
+ */
+@ExperimentalAnimationApi
+enum class EnterExitState {
+    /**
+     * The initial state of a custom enter animation in [AnimatedVisibility]..
+     */
+    PreEnter,
+
+    /**
+     * The `Visible` state is the target state of a custom *enter* animation, also the initial
+     * state of a custom *exit* animation in [AnimatedVisibility].
+     */
+    Visible,
+
+    /**
+     * Target state of a custom *exit* animation in [AnimatedVisibility].
+     */
+    PostExit
+}
+
+/**
+ * [AnimatedVisibility] composable animates the appearance and disappearance of its content, as
+ * [visibleState]'s [targetState][MutableTransitionState.targetState] changes. The [visibleState]
+ * can also be used to observe the state of [AnimatedVisibility]. For example:
+ * `visibleState.idIdle` indicates whether the all animations have finished in [AnimatedVisibility],
+ * and `visibleState.currentState` returns the initial state of the current animations.
+ *
+ * Different [EnterTransition]s and [ExitTransition]s can be defined in
+ * [enter] and [exit] for the appearance and disappearance animation. There are 3 types of
+ * [EnterTransition] and [ExitTransition]: Fade, Expand/Shrink and Slide. The enter transitions
+ * and exit transitions can be combined using `+`. The order of the combination does not matter,
+ * as the transition animations will start simultaneously. See [EnterTransition] and
+ * [ExitTransition] for details on the three types of transition.
+ *
+ * Aside from these three types of [EnterTransition] and [ExitTransition], [AnimatedVisibility]
+ * also supports custom enter/exit animations. Some use cases may benefit from custom enter/exit
+ * animations on shape, scale, color, etc. Custom enter/exit animations can be created using the
+ * `Transition<EnterExitState>` object from the [AnimatedVisibilityScope] (i.e.
+ * [AnimatedVisibilityScope.transition]). See [EnterExitState] for an example of custom animations.
+ * These custom animations will be running along side of the built-in animations specified in
+ * [enter] and [exit]. In cases where the enter/exit animation needs to be completely customized,
+ * [enter] and/or [exit] can be specified as [EnterTransition.None] and/or [ExitTransition.None]
+ * as needed. [AnimatedVisibility] will wait until *all* of enter/exit animations to finish
+ * before it considers itself idle. [content] will only be removed after all the (built-in and
+ * custom) exit animations have finished.
+ *
+ * [AnimatedVisibility] creates a custom [Layout] for its content. The size of the custom
+ * layout is determined by the largest width and largest height of the children. All children
+ * will be aligned to the top start of the [Layout].
+ *
+ * __Note__: Once the exit transition is finished, the [content] composable will be removed
+ * from the tree, and disposed. Both `currentState` and `targetState` will be `false` for
+ * [visibleState].
+ *
+ * By default, the enter transition will be a combination of [fadeIn] and [expandIn] of the
+ * content from the bottom end. And the exit transition will be shrinking the content towards the
+ * bottom end while fading out (i.e. [fadeOut] + [shrinkOut]). The expanding and shrinking will
+ * likely also animate the parent and siblings if they rely on the size of appearing/disappearing
+ * content. When the [AnimatedVisibility] composable is put in a [Row] or a [Column], the default
+ * enter and exit transitions are tailored to that particular container. See
+ * [RowScope.AnimatedVisibility] and [ColumnScope.AnimatedVisibility] for details.
+ *
+ * @sample androidx.compose.animation.samples.AnimatedVisibilityLazyColumnSample
+ *
+ * @param visibleState defines whether the content should be visible
+ * @param modifier modifier for the [Layout] created to contain the [content]
+ * @param enter EnterTransition(s) used for the appearing animation, fading in while expanding
+ *              vertically by default
+ * @param exit ExitTransition(s) used for the disappearing animation, fading out while
+ *             shrinking vertically by default
+ * @param content Content to appear or disappear based on the value of [visibleState]
+ *
+ * @see EnterTransition
+ * @see ExitTransition
+ * @see fadeIn
+ * @see expandIn
+ * @see fadeOut
+ * @see shrinkOut
+ * @see AnimatedVisibility
+ * @see Transition.AnimatedVisibility
+ * @see AnimatedVisibilityScope
+ */
+@ExperimentalAnimationApi
+@Composable
+fun AnimatedVisibility(
+    visibleState: MutableTransitionState<Boolean>,
+    modifier: Modifier = Modifier,
+    enter: EnterTransition,
+    exit: ExitTransition,
+    content: @Composable() AnimatedVisibilityScope.() -> Unit
+) {
+    val transition = updateTransition(visibleState)
+    AnimatedEnterExitImpl(transition, { it }, modifier, enter, exit, content)
+}
+
+/**
+ * [RowScope.AnimatedVisibility] composable animates the appearance and disappearance of its
+ * content as [visibleState]'s [targetState][MutableTransitionState.targetState] changes. The
+ * default [enter] and [exit] transitions are tailored specific to the [Row] layout. See more
+ * details below. The [visibleState] can also be used to observe the state of [AnimatedVisibility].
+ * For example: `visibleState.idIdle` indicates whether the all animations have finished in
+ * [AnimatedVisibility], and `visibleState.currentState` returns the initial state of the current
+ * animations.
+ *
+ * Different [EnterTransition]s and [ExitTransition]s can be defined in
+ * [enter] and [exit] for the appearance and disappearance animation. There are 3 types of
+ * [EnterTransition] and [ExitTransition]: Fade, Expand/Shrink and Slide. The enter transitions
+ * and exit transitions can be combined using `+`. The order of the combination does not matter,
+ * as the transition animations will start simultaneously. See [EnterTransition] and
+ * [ExitTransition] for details on the three types of transition.
+ *
+ * The default [enter] and [exit] transition is configured based on the horizontal layout of a
+ * [Row]. [enter] defaults to a combination of fading in and expanding the content horizontally.
+ * (The end of the content will be the leading edge as the content expands to its
+ * full width.) And [exit] defaults to shrinking the content horizontally with the end of the
+ * content being the leading edge while fading out. The expanding and shrinking will likely also
+ * animate the parent and siblings in the row since they rely on the size of appearing/disappearing
+ * content.
+ *
+ * Aside from these three types of [EnterTransition] and [ExitTransition], [AnimatedVisibility]
+ * also supports custom enter/exit animations. Some use cases may benefit from custom enter/exit
+ * animations on shape, scale, color, etc. Custom enter/exit animations can be created using the
+ * `Transition<EnterExitState>` object from the [AnimatedVisibilityScope] (i.e.
+ * [AnimatedVisibilityScope.transition]). See [EnterExitState] for an example of custom animations.
+ * These custom animations will be running along side of the built-in animations specified in
+ * [enter] and [exit]. In cases where the enter/exit animation needs to be completely customized,
+ * [enter] and/or [exit] can be specified as [EnterTransition.None] and/or [ExitTransition.None]
+ * as needed. [AnimatedVisibility] will wait until *all* of enter/exit animations to finish
+ * before it considers itself idle. [content] will only be removed after all the (built-in and
+ * custom) exit animations have finished.
+ *
+ * [AnimatedVisibility] creates a custom [Layout] for its content. The size of the custom
+ * layout is determined by the largest width and largest height of the children. All children
+ * will be aligned to the top start of the [Layout].
+ *
+ * __Note__: Once the exit transition is finished, the [content] composable will be removed
+ * from the tree, and disposed. Both `currentState` and `targetState` will be `false` for
+ * [visibleState].
+ *
+ * @param visibleState defines whether the content should be visible
+ * @param modifier modifier for the [Layout] created to contain the [content]
+ * @param enter EnterTransition(s) used for the appearing animation, fading in while expanding
+ *              vertically by default
+ * @param exit ExitTransition(s) used for the disappearing animation, fading out while
+ *             shrinking vertically by default
+ * @param content Content to appear or disappear based on the value of [visibleState]
+ *
+ * @see EnterTransition
+ * @see ExitTransition
+ * @see fadeIn
+ * @see expandIn
+ * @see fadeOut
+ * @see shrinkOut
+ * @see AnimatedVisibility
+ * @see Transition.AnimatedVisibility
+ * @see AnimatedVisibilityScope
+ */
+@ExperimentalAnimationApi
+@Composable
+fun RowScope.AnimatedVisibility(
+    visibleState: MutableTransitionState<Boolean>,
+    modifier: Modifier = Modifier,
+    enter: EnterTransition = expandHorizontally() + fadeIn(),
+    exit: ExitTransition = shrinkHorizontally() + fadeOut(),
+    content: @Composable() AnimatedVisibilityScope.() -> Unit
+) {
+    val transition = updateTransition(visibleState)
+    AnimatedEnterExitImpl(transition, { it }, modifier, enter, exit, content)
+}
+
+/**
+ * [ColumnScope.AnimatedVisibility] composable animates the appearance and disappearance of its
+ * content as [visibleState]'s [targetState][MutableTransitionState.targetState] changes. The
+ * default [enter] and [exit] transitions are tailored specific to the [Column] layout. See more
+ * details below. The [visibleState] can also be used to observe the state of [AnimatedVisibility].
+ * For example: `visibleState.idIdle` indicates whether the all animations have finished in
+ * [AnimatedVisibility], and `visibleState.currentState` returns the initial state of the current
+ * animations.
+ *
+ * Different [EnterTransition]s and [ExitTransition]s can be defined in
+ * [enter] and [exit] for the appearance and disappearance animation. There are 3 types of
+ * [EnterTransition] and [ExitTransition]: Fade, Expand/Shrink and Slide. The enter transitions
+ * and exit transitions can be combined using `+`. The order of the combination does not matter,
+ * as the transition animations will start simultaneously. See [EnterTransition] and
+ * [ExitTransition] for details on the three types of transition.
+ *
+ * The default [enter] and [exit] transition is configured based on the vertical layout of a
+ * [Column]. [enter] defaults to a combination of fading in and expanding the content vertically.
+ * (The bottom of the content will be the leading edge as the content expands to its full height.)
+ * And the [exit] defaults to shrinking the content vertically with the bottom of the content being
+ * the leading edge while fading out. The expanding and shrinking will likely also animate the
+ * parent and siblings in the column since they rely on the size of appearing/disappearing content.
+ *
+ * Aside from these three types of [EnterTransition] and [ExitTransition], [AnimatedVisibility]
+ * also supports custom enter/exit animations. Some use cases may benefit from custom enter/exit
+ * animations on shape, scale, color, etc. Custom enter/exit animations can be created using the
+ * `Transition<EnterExitState>` object from the [AnimatedVisibilityScope] (i.e.
+ * [AnimatedVisibilityScope.transition]). See [EnterExitState] for an example of custom animations.
+ * These custom animations will be running along side of the built-in animations specified in
+ * [enter] and [exit]. In cases where the enter/exit animation needs to be completely customized,
+ * [enter] and/or [exit] can be specified as [EnterTransition.None] and/or [ExitTransition.None]
+ * as needed. [AnimatedVisibility] will wait until *all* of enter/exit animations to finish
+ * before it considers itself idle. [content] will only be removed after all the (built-in and
+ * custom) exit animations have finished.
+ *
+ * [AnimatedVisibility] creates a custom [Layout] for its content. The size of the custom
+ * layout is determined by the largest width and largest height of the children. All children
+ * will be aligned to the top start of the [Layout].
+ *
+ * __Note__: Once the exit transition is finished, the [content] composable will be removed
+ * from the tree, and disposed. Both `currentState` and `targetState` will be `false` for
+ * [visibleState].
+ *
+ * @sample androidx.compose.animation.samples.AVColumnScopeWithMutableTransitionState
+ *
+ * @param visibleState defines whether the content should be visible
+ * @param modifier modifier for the [Layout] created to contain the [content]
+ * @param enter EnterTransition(s) used for the appearing animation, fading in while expanding
+ *              vertically by default
+ * @param exit ExitTransition(s) used for the disappearing animation, fading out while
+ *             shrinking vertically by default
+ * @param content Content to appear or disappear based on of [visibleState]
+ *
+ * @see EnterTransition
+ * @see ExitTransition
+ * @see fadeIn
+ * @see expandIn
+ * @see fadeOut
+ * @see shrinkOut
+ * @see AnimatedVisibility
+ * @see Transition.AnimatedVisibility
+ * @see AnimatedVisibilityScope
+ */
+@ExperimentalAnimationApi
+@Composable
+fun ColumnScope.AnimatedVisibility(
+    visibleState: MutableTransitionState<Boolean>,
+    modifier: Modifier = Modifier,
+    enter: EnterTransition = expandVertically() + fadeIn(),
+    exit: ExitTransition = shrinkVertically() + fadeOut(),
+    content: @Composable() AnimatedVisibilityScope.() -> Unit
+) {
+    val transition = updateTransition(visibleState)
+    AnimatedEnterExitImpl(transition, { it }, modifier, enter, exit, content)
+}
+
+/**
+ * This extension function creates an [AnimatedVisibility] composable as a child Transition of
+ * the given Transition. This means: 1) the enter/exit transition is now triggered by the provided
+ * [Transition]'s [targetState][Transition.targetState] change. When the targetState changes, the
+ * visibility will be derived using the [visible] lambda and [Transition.targetState]. 2)
+ * The enter/exit transitions, as well as any custom enter/exit animations defined in
+ * [AnimatedVisibility] are now hoisted to the parent Transition. The parent Transition will wait
+ * for all of them to finish before it considers itself finished (i.e. [Transition.currentState]
+ * = [Transition.targetState]), and subsequently removes the content in the exit case.
+ *
+ * Different [EnterTransition]s and [ExitTransition]s can be defined in
+ * [enter] and [exit] for the appearance and disappearance animation. There are 3 types of
+ * [EnterTransition] and [ExitTransition]: Fade, Expand/Shrink and Slide. The enter transitions
+ * and exit transitions can be combined using `+`. The order of the combination does not matter,
+ * as the transition animations will start simultaneously. See [EnterTransition] and
+ * [ExitTransition] for details on the three types of transition.
+ *
+ * Aside from these three types of [EnterTransition] and [ExitTransition], [AnimatedVisibility]
+ * also supports custom enter/exit animations. Some use cases may benefit from custom enter/exit
+ * animations on shape, scale, color, etc. Custom enter/exit animations can be created using the
+ * `Transition<EnterExitState>` object from the [AnimatedVisibilityScope] (i.e.
+ * [AnimatedVisibilityScope.transition]). See [EnterExitState] for an example of custom animations.
+ * These custom animations will be running along side of the built-in animations specified in
+ * [enter] and [exit]. In cases where the enter/exit animation needs to be completely customized,
+ * [enter] and/or [exit] can be specified as [EnterTransition.None] and/or [ExitTransition.None]
+ * as needed. [AnimatedVisibility] will wait until *all* of enter/exit animations to finish
+ * before it considers itself idle. [content] will only be removed after all the (built-in and
+ * custom) exit animations have finished.
+ *
+ * [AnimatedVisibility] creates a custom [Layout] for its content. The size of the custom
+ * layout is determined by the largest width and largest height of the children. All children
+ * will be aligned to the top start of the [Layout].
+ *
+ * __Note__: Once the exit transition is finished, the [content] composable will be removed
+ * from the tree, and disposed.
+ *
+ * By default, the enter transition will be a combination of [fadeIn] and [expandIn] of the
+ * content from the bottom end. And the exit transition will be shrinking the content towards the
+ * bottom end while fading out (i.e. [fadeOut] + [shrinkOut]). The expanding and shrinking will
+ * likely also animate the parent and siblings if they rely on the size of appearing/disappearing
+ * content.
+ *
+ * @sample androidx.compose.animation.samples.AddAnimatedVisibilityToGenericTransitionSample
+ *
+ * @param visible defines whether the content should be visible based on transition state T
+ * @param modifier modifier for the [Layout] created to contain the [content]
+ * @param enter EnterTransition(s) used for the appearing animation, fading in while expanding
+ *              vertically by default
+ * @param exit ExitTransition(s) used for the disappearing animation, fading out while
+ *             shrinking vertically by default
+ * @param content Content to appear or disappear based on the visibility derived from the
+ *                [Transition.targetState] and the provided [visible] lambda
+ *
+ * @see EnterTransition
+ * @see ExitTransition
+ * @see fadeIn
+ * @see expandIn
+ * @see fadeOut
+ * @see shrinkOut
+ * @see AnimatedVisibilityScope
+ * @see Transition.AnimatedVisibility
+ */
+@ExperimentalAnimationApi
+@Composable
+fun <T> Transition<T>.AnimatedVisibility(
+    visible: (T) -> Boolean,
+    modifier: Modifier = Modifier,
+    enter: EnterTransition = fadeIn() + expandIn(),
+    exit: ExitTransition = shrinkOut() + fadeOut(),
+    content: @Composable() AnimatedVisibilityScope.() -> Unit
+) = AnimatedEnterExitImpl(this, visible, modifier, enter, exit, content)
+
+/**
+ * This is the scope for the content of [AnimatedVisibility]. In this scope, direct and
+ * indirect children of [AnimatedVisibility] will be able to define their own enter/exit
+ * transitions using the built-in options via [Modifier.animateEnterExit]. They will also be able
+ * define custom enter/exit animations using the [transition] object. [AnimatedVisibility] will
+ * ensure both custom and built-in enter/exit animations finish before it considers itself idle,
+ * and subsequently removes its content in the case of exit.
+ *
+ * __Note:__ Custom enter/exit animations that are created *independent* of the
+ * [AnimatedVisibilityScope.transition] will have no guarantee to finish when
+ * exiting, as [AnimatedVisibility] would have no visibility of such animations.
+ *
+ * @sample androidx.compose.animation.samples.AVScopeAnimateEnterExit
+ */
+@ExperimentalAnimationApi
+class AnimatedVisibilityScope internal constructor(transition: Transition<EnterExitState>) {
+    /**
+     * [transition] allows custom enter/exit animations to be specified. It will run simultaneously
+     * with the built-in enter/exit transitions specified in [AnimatedVisibility].
+     */
+    var transition: Transition<EnterExitState> = transition
+        internal set
+
+    /**
+     * [animateEnterExit] modifier can be used for any direct or indirect children of
+     * [AnimatedVisibility] to create a different enter/exit animation than what's specified in
+     * [AnimatedVisibility]. The visual effect of these children will be a combination of the
+     * [AnimatedVisibility]'s animation and their own enter/exit animations.
+     *
+     * [enter] and [exit] defines different [EnterTransition]s and [ExitTransition]s that will be
+     * used for the appearance and disappearance animation. There are 3 types of
+     * [EnterTransition] and [ExitTransition]: Fade, Expand/Shrink, and Slide. The enter transitions
+     * and exit transitions can be combined using `+`. The order of the combination does not matter,
+     * as the transition animations will start simultaneously. See [EnterTransition] and
+     * [ExitTransition] for details on the three types of transition.
+     *
+     * By default, the enter transition will be a combination of [fadeIn] and [expandIn] of the
+     * content from the bottom end. And the exit transition will be shrinking the content towards
+     * the bottom end while fading out (i.e. [fadeOut] + [shrinkOut]). The expanding and shrinking
+     * will likely also animate the parent and siblings if they rely on the size of
+     * appearing/disappearing content.
+     *
+     * In some cases it may be desirable to have [AnimatedVisibility] apply no animation at all for
+     * enter and/or exit, such that children of [AnimatedVisibility] can each have their distinct
+     * animations. To achieve this, [EnterTransition.None] and/or [ExitTransition.None] can be
+     * used for [AnimatedVisibility].
+     *
+     * @sample androidx.compose.animation.samples.AnimateEnterExitPartialContent
+     */
+    fun Modifier.animateEnterExit(
+        enter: EnterTransition = fadeIn() + expandIn(),
+        exit: ExitTransition = fadeOut() + shrinkOut()
+    ): Modifier = composed(
+        inspectorInfo = debugInspectorInfo {
+            name = "animateEnterExit"
+            properties["enter"] = enter
+            properties["exit"] = exit
+        }
+    ) {
+        this.then(transition.createModifier(enter, exit))
+    }
 }
 
 @ExperimentalAnimationApi
 @Composable
-private fun AnimatedVisibilityImpl(
+@Deprecated(
+    "AnimatedVisibility no longer accepts initiallyVisible as a parameter",
+    replaceWith = ReplaceWith(
+        "AnimatedVisibility(" +
+            "transitionState = remember { MutableTransitionState(initiallyVisible) }\n" +
+            ".apply { targetState = visible },\n" +
+            "modifier = modifier,\n" +
+            "enter = enter,\n" +
+            "exit = exit) {\n" +
+            "content() \n" +
+            "}",
+        "androidx.compose.animation.core.MutableTransitionState"
+    )
+)
+fun AnimatedVisibility(
     visible: Boolean,
-    modifier: Modifier,
+    modifier: Modifier = Modifier,
     enter: EnterTransition,
     exit: ExitTransition,
     initiallyVisible: Boolean,
     content: @Composable () -> Unit
+) = AnimatedVisibility(
+    visibleState = remember { MutableTransitionState(initiallyVisible) }
+        .apply { targetState = visible },
+    modifier = modifier,
+    enter = enter,
+    exit = exit
 ) {
+    content()
+}
 
-    // Set up initial transition states, based on the initial visibility.
-    var transitionState by remember {
-        mutableStateOf(if (initiallyVisible) AnimStates.Visible else AnimStates.Gone)
+// RowScope and ColumnScope AnimatedEnterExit extensions and AnimatedEnterExit without a receiver
+// converge here.
+@OptIn(ExperimentalTransitionApi::class)
+@ExperimentalAnimationApi
+@Composable
+private fun <T> AnimatedEnterExitImpl(
+    transition: Transition<T>,
+    visible: (T) -> Boolean,
+    modifier: Modifier,
+    enter: EnterTransition,
+    exit: ExitTransition,
+    content: @Composable() AnimatedVisibilityScope.() -> Unit
+) {
+    val isAnimationVisible = remember(transition) {
+        mutableStateOf(visible(transition.currentState))
     }
-
-    var isAnimating by remember { mutableStateOf(false) }
-
-    // Update transition states, based on the current visibility.
-    if (visible) {
-        if (transitionState == AnimStates.Gone ||
-            transitionState == AnimStates.Exiting
-        ) {
-            transitionState = AnimStates.Entering
-            isAnimating = true
+    if (visible(transition.targetState) || isAnimationVisible.value) {
+        val childTransition = transition.createChildTransition {
+            transition.targetEnterExit(visible, it)
         }
-    } else {
-        if (transitionState == AnimStates.Visible ||
-            transitionState == AnimStates.Entering
-        ) {
-            transitionState = AnimStates.Exiting
-            isAnimating = true
+        LaunchedEffect(childTransition) {
+            snapshotFlow {
+                childTransition.currentState == EnterExitState.Visible ||
+                    childTransition.targetState == EnterExitState.Visible
+            }.collect {
+                isAnimationVisible.value = it
+            }
         }
-    }
 
-    val scope = rememberCoroutineScope()
-    val animations = remember(scope, enter, exit) {
-        // TODO: Should we delay changing enter/exit after on-going animations are finished?
-        TransitionAnimations(enter, exit, scope) {
-            isAnimating = false
-        }
-    }
-    animations.updateState(transitionState)
-
-    // If the exit animation has finished, skip the child composable altogether
-    if (transitionState == AnimStates.Gone) {
-        return
-    }
-
-    Layout(
-        content = content,
-        modifier = modifier.then(animations.modifier)
-    ) { measureables, constraints ->
-
-        val placeables = measureables.map { it.measure(constraints) }
-        val maxWidth: Int = placeables.fastMaxBy { it.width }?.width ?: 0
-        val maxHeight = placeables.fastMaxBy { it.height }?.height ?: 0
-
-        val offset: IntOffset
-        val animatedSize: IntSize
-        val animSize = animations.getAnimatedSize(
-            IntSize(maxWidth, maxHeight)
+        AnimatedEnterExitImpl(
+            childTransition,
+            modifier,
+            enter = enter,
+            exit = exit,
+            content = content
         )
-        if (animSize != null) {
-            offset = animSize.first
-            animatedSize = animSize.second
+    }
+}
+
+@OptIn(ExperimentalTransitionApi::class)
+@ExperimentalAnimationApi
+@Composable
+private inline fun AnimatedEnterExitImpl(
+    transition: Transition<EnterExitState>,
+    modifier: Modifier,
+    enter: EnterTransition,
+    exit: ExitTransition,
+    content: @Composable() AnimatedVisibilityScope.() -> Unit
+) {
+    // TODO: Get some feedback on whether there's a need to observe this state change in user
+    //  code. If there is, this if check will need to be moved to measure stage, along with some
+    //  structural changes.
+    if (transition.currentState == EnterExitState.Visible ||
+        transition.targetState == EnterExitState.Visible
+    ) {
+        val scope = remember(transition) { AnimatedVisibilityScope(transition) }
+        Layout(
+            content = { scope.content() },
+            modifier = modifier.then(transition.createModifier(enter, exit))
+        ) { measureables, constraints ->
+            val placeables = measureables.map { it.measure(constraints) }
+            val maxWidth: Int = placeables.fastMaxBy { it.width }?.width ?: 0
+            val maxHeight = placeables.fastMaxBy { it.height }?.height ?: 0
+            // Position the children.
+            layout(maxWidth, maxHeight) {
+                placeables.fastForEach {
+                    it.place(0, 0)
+                }
+            }
+        }
+    }
+}
+
+// This converts Boolean visible to EnterExitState
+@ExperimentalAnimationApi
+@Composable
+private fun <T> Transition<T>.targetEnterExit(
+    visible: (T) -> Boolean,
+    targetState: T
+): EnterExitState = key(this) {
+    val hasBeenVisible = remember { mutableStateOf(false) }
+    if (visible(currentState)) {
+        hasBeenVisible.value = true
+    }
+    if (visible(targetState)) {
+        EnterExitState.Visible
+    } else {
+        // If never been visible, visible = false means PreEnter, otherwise PostExit
+        if (hasBeenVisible.value) {
+            EnterExitState.PostExit
         } else {
-            offset = IntOffset.Zero
-            animatedSize = IntSize(maxWidth, maxHeight)
-        }
-
-        // If animation has finished update state
-        if (!isAnimating) {
-            if (transitionState == AnimStates.Exiting) {
-                transitionState = AnimStates.Gone
-            } else if (transitionState == AnimStates.Entering) {
-                transitionState = AnimStates.Visible
-            }
-        }
-
-        // Position the children.
-        layout(animatedSize.width, animatedSize.height) {
-            placeables.fastForEach {
-                it.place(offset.x, offset.y)
-            }
+            EnterExitState.PreEnter
         }
     }
 }
diff --git a/compose/animation/animation/src/commonMain/kotlin/androidx/compose/animation/EnterExitTransition.kt b/compose/animation/animation/src/commonMain/kotlin/androidx/compose/animation/EnterExitTransition.kt
index e1f756b..b869a5e 100644
--- a/compose/animation/animation/src/commonMain/kotlin/androidx/compose/animation/EnterExitTransition.kt
+++ b/compose/animation/animation/src/commonMain/kotlin/androidx/compose/animation/EnterExitTransition.kt
@@ -14,16 +14,30 @@
  * limitations under the License.
  */
 
+@file:OptIn(InternalAnimationApi::class, ExperimentalAnimationApi::class)
+
 package androidx.compose.animation
 
-import androidx.compose.animation.core.Animatable
-import androidx.compose.animation.core.AnimationEndReason
+import android.annotation.SuppressLint
 import androidx.compose.animation.core.AnimationVector2D
 import androidx.compose.animation.core.FiniteAnimationSpec
+import androidx.compose.animation.core.InternalAnimationApi
+import androidx.compose.animation.core.Transition
 import androidx.compose.animation.core.VectorConverter
+import androidx.compose.animation.core.animateFloat
+import androidx.compose.animation.core.createDeferredAnimation
 import androidx.compose.animation.core.spring
+import androidx.compose.animation.core.VisibilityThreshold
+import androidx.compose.runtime.Composable
 import androidx.compose.runtime.Immutable
 import androidx.compose.runtime.Stable
+import androidx.compose.runtime.State
+import androidx.compose.runtime.key
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberUpdatedState
+import androidx.compose.runtime.setValue
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.composed
@@ -37,10 +51,6 @@
 import androidx.compose.ui.unit.IntOffset
 import androidx.compose.ui.unit.IntSize
 import androidx.compose.ui.unit.LayoutDirection
-import androidx.compose.ui.util.fastFirstOrNull
-import androidx.compose.ui.util.fastForEach
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.launch
 
 @RequiresOptIn(message = "This is an experimental animation API.")
 @Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY)
@@ -92,13 +102,24 @@
             )
         )
     }
-    // TODO: Support EnterTransition.None
 
     override fun equals(other: Any?): Boolean {
         return other is EnterTransition && other.data == data
     }
 
     override fun hashCode(): Int = data.hashCode()
+
+    companion object {
+        /**
+         * This can be used when no enter transition is desired. It can be useful in cases where
+         * there are other forms of enter animation defined indirectly for an
+         * [AnimatedVisibility]. e.g.The children of the [AnimatedVisibility] have all defined
+         * their own [EnterTransition], or when the parent is fading in, etc.
+         *
+         * @see [ExitTransition.None]
+         */
+        val None: EnterTransition = EnterTransitionImpl(TransitionData())
+    }
 }
 
 /**
@@ -150,12 +171,26 @@
         )
     }
 
-    // TODO: Support ExitTransition.None
     override fun equals(other: Any?): Boolean {
         return other is ExitTransition && other.data == data
     }
 
     override fun hashCode(): Int = data.hashCode()
+
+    companion object {
+        /**
+         * This can be used when no built-in [ExitTransition] (i.e. fade/slide, etc) is desired for
+         * the [AnimatedVisibility], but rather the children are defining their own exit
+         * animation using the [Transition] scope.
+         *
+         * __Note:__ If [None] is used, and nothing is animating in the Transition<EnterExitState>
+         * scope that [AnimatedVisibility] provided, the content will be removed from
+         * [AnimatedVisibility] right away.
+         *
+         * @sample androidx.compose.animation.samples.AVScopeAnimateEnterExit
+         */
+        val None: ExitTransition = ExitTransitionImpl(TransitionData())
+    }
 }
 
 /**
@@ -556,7 +591,7 @@
  *
  * [targetOffsetX] is a lambda that takes the full width of the content and returns an
  * offset. This allows the target offset to be defined proportional to the full size, or as an
- * absolute value. It defaults to return half of negaive width, which would slide the content to
+ * absolute value. It defaults to return half of negative width, which would slide the content to
  * the left by half of its width.
  *
  * @sample androidx.compose.animation.samples.SlideTransition
@@ -615,7 +650,7 @@
 @Immutable
 internal data class ChangeSize(
     val alignment: Alignment,
-    val startSize: (fullSize: IntSize) -> IntSize = { IntSize(0, 0) },
+    val size: (fullSize: IntSize) -> IntSize = { IntSize(0, 0) },
     val animationSpec: FiniteAnimationSpec<IntSize> = spring(),
     val clip: Boolean = true
 )
@@ -651,517 +686,285 @@
     val changeSize: ChangeSize? = null
 )
 
-/**
- * Alignment does NOT stay consistent in enter vs. exit animations. For example, the enter could be
- * expanding from top, but exit could be shrinking towards the bottom. As a result, when such an
- * animation is interrupted, it becomes very tricky to handle that. This is why there needs to be
- * two types of size animations: alignment based and rect based. When alignment stays the same,
- * size is the only value that needs to be animated. When alignment changes, however, the only
- * sensible solution is to fall back to rect based solution. Namely, this calculates the current
- * clip rect based on alignment and size, and the final rect based on the new alignment and the
- * ending size. In rect based animations, the size will still be animated using the provided size
- * animation, and the offset will be animated using physics as a part of the interruption
- * handling logic.
- */
-internal interface SizeAnimation {
-    val anim: Animatable<IntSize, AnimationVector2D>
-    var clip: Boolean
-    val size: IntSize
-        get() = anim.value
-
-    val offset: (IntSize) -> IntOffset
-    val isAnimating: Boolean
-    val listener: (AnimationEndReason, Any) -> Unit
-
-    /**
-     * The instance returned may be different than the caller if the alignment has changed. Either
-     * way the returned animation will be configured to animate to the new target.
-     */
-    fun animateTo(
-        target: IntSize,
-        alignment: Alignment,
-        fullSize: IntSize,
-        spec: FiniteAnimationSpec<IntSize>,
-        scope: CoroutineScope,
-    ): SizeAnimation
-
-    /**
-     * Returns what the offset will be once the target size is applied.
-     */
-    fun snapTo(target: IntSize, scope: CoroutineScope): IntOffset
-
-    val alignment: Alignment
-}
-
-/**
- * This is the animation class used to animate content size. However, this animation may get
- * interrupted before it finishes. If the new size target is based on the same alignment, this
- * instance can be re-used to handle that interruption. A more complicated and hairy case is when
- * the alignment changes (from top aligned to bottom aligned), in which case we have to fall back
- * to Rect based animation to properly handle the alignment change.
- */
-private class AlignmentBasedSizeAnimation(
-    override val anim: Animatable<IntSize, AnimationVector2D>,
-    override val alignment: Alignment,
-    override var clip: Boolean,
-    override val listener: (AnimationEndReason, Any) -> Unit
-) : SizeAnimation {
-
-    override val offset: (IntSize) -> IntOffset
-        get() = {
-            alignment.align(it, anim.value, LayoutDirection.Ltr)
-        }
-
-    override fun animateTo(
-        target: IntSize,
-        alignment: Alignment,
-        fullSize: IntSize,
-        spec: FiniteAnimationSpec<IntSize>,
-        scope: CoroutineScope,
-    ): SizeAnimation {
-        if (anim.targetValue != target) {
-            scope.launch {
-                anim.animateTo(target, spec)
-                listener(AnimationEndReason.Finished, anim.value)
-            }
-        }
-
-        if (alignment == this.alignment) {
-            return this
-        } else {
-            // Alignment changed
-            val offset = this.offset(fullSize)
-            return RectBasedSizeAnimation(anim, offset, clip, scope, listener)
-        }
-    }
-
-    override fun snapTo(target: IntSize, scope: CoroutineScope): IntOffset {
-        scope.launch {
-            anim.snapTo(target)
-        }
-        return alignment.align(target, target, LayoutDirection.Ltr)
-    }
-
-    override val isAnimating: Boolean
-        get() = anim.isRunning
-}
-
-/**
- * This class animates the rect of the clip bounds, as a fallback for when enter and exit size
- * change animations have different alignment.
- */
-private class RectBasedSizeAnimation(
-    override val anim: Animatable<IntSize, AnimationVector2D>,
-    targetOffset: IntOffset,
-    override var clip: Boolean,
-    val scope: CoroutineScope,
-    override val listener: (AnimationEndReason, Any) -> Unit
-) : SizeAnimation {
-    private val offsetAnim: Animatable<IntOffset, AnimationVector2D>
-
-    init {
-        offsetAnim = Animatable(
-            IntOffset(0, 0), IntOffset.VectorConverter,
-            IntOffset(1, 1)
-        )
-        scope.launch {
-            offsetAnim.animateTo(targetOffset)
-            listener(AnimationEndReason.Finished, offsetAnim.value)
-        }
-    }
-
-    override val alignment: Alignment
-        get() = Alignment.TopStart
-
-    override val offset: (IntSize) -> IntOffset
-        get() = {
-            offsetAnim.value
-        }
-
-    override fun animateTo(
-        target: IntSize,
-        alignment: Alignment,
-        fullSize: IntSize,
-        spec: FiniteAnimationSpec<IntSize>,
-        scope: CoroutineScope,
-    ): SizeAnimation {
-        val targetOffSet = alignment.align(fullSize, target, LayoutDirection.Ltr)
-        if (offsetAnim.targetValue != targetOffSet) {
-            scope.launch {
-                offsetAnim.animateTo(targetOffSet)
-                listener(AnimationEndReason.Finished, offsetAnim.value)
-            }
-        }
-        if (target != anim.targetValue) {
-            scope.launch {
-                anim.animateTo(target, spec)
-                listener(AnimationEndReason.Finished, anim.value)
-            }
-        }
-        return this
-    }
-
-    override fun snapTo(target: IntSize, scope: CoroutineScope): IntOffset {
-        val targetOffSet = alignment.align(target, target, LayoutDirection.Ltr)
-        scope.launch {
-            offsetAnim.snapTo(targetOffSet)
-            anim.snapTo(target)
-        }
-        return targetOffSet
-    }
-
-    override val isAnimating: Boolean
-        get() = (anim.isRunning || offsetAnim.isRunning)
-}
-
-private operator fun IntSize.minus(b: IntSize) =
-    IntSize(width - b.width, height - b.height)
-
-internal interface TransitionAnimation {
-    val isRunning: Boolean
-    var state: AnimStates
-    val modifier: Modifier
-    fun getAnimatedSize(fullSize: IntSize): Pair<IntOffset, IntSize>? = null
-    val listener: (AnimationEndReason, Any) -> Unit
-}
-
-/**
- * This class animates alpha through a graphics layer modifier.
- */
-private class FadeTransition(
-    val enter: Fade? = null,
-    val exit: Fade? = null,
-    val scope: CoroutineScope,
-    override val listener: (AnimationEndReason, Any) -> Unit
-) : TransitionAnimation {
-    override val isRunning: Boolean
-        get() = alphaAnim.isRunning
-    override val modifier: Modifier
-        get() = Modifier.graphicsLayer {
-            alpha = alphaAnim.value
-        }
-
-    override var state: AnimStates = AnimStates.Gone
-        set(value) {
-            if (value == field) {
-                return
-            }
-            // Animation state has changed if we get here.
-            if (value == AnimStates.Entering) {
-                // Animation is interrupted from fade out, now fade in
-                if (alphaAnim.isRunning) {
-                    enter?.apply {
-                        // If fade in animation specified, use that. Otherwise use default.
-                        animateTo(1f, animationSpec, listener)
-                    } ?: animateTo(1f, listener = listener)
-                } else {
-                    // set up initial values for alphaAnimation
-                    enter?.apply {
-                        // If fade in is defined start from pre-defined `alphaFrom`. If no fade in is defined,
-                        // snap the alpha to 1f
-                        alphaAnim = Animatable(alpha, 0.02f)
-                        scope.launch {
-                            alphaAnim.animateTo(1f, animationSpec)
-                            listener(AnimationEndReason.Finished, alphaAnim.value)
-                        }
-                        // If no enter is defined and animation isn't running, snap to alpha = 1
-                    } ?: scope.launch {
-                        alphaAnim.snapTo(1f)
-                    }
-                }
-            } else if (value == AnimStates.Exiting) {
-                if (alphaAnim.isRunning) {
-                    // interrupting alpha animation: directly animating to out value if defined,
-                    // otherwise let the fade-in finish
-                    exit?.apply {
-                        animateTo(alpha, animationSpec, listener)
-                    }
-                } else {
-                    // set up alpha animation to fade out, if fade out is defined
-                    exit?.apply {
-                        animateTo(alpha, animationSpec, listener)
-                    }
-                }
-            }
-            field = value
-        }
-
-    private fun animateTo(
-        target: Float,
-        animationSpec: FiniteAnimationSpec<Float> = spring(visibilityThreshold = 0.02f),
-        listener: (AnimationEndReason, Any) -> Unit
-    ) {
-        scope.launch {
-            alphaAnim.animateTo(target, animationSpec)
-            listener(AnimationEndReason.Finished, alphaAnim.value)
-        }
-    }
-
-    var alphaAnim = Animatable(1f, visibilityThreshold = 0.02f)
-}
-
-private class SlideTransition(
-    val enter: Slide? = null,
-    val exit: Slide? = null,
-    val scope: CoroutineScope,
-    override val listener: (AnimationEndReason, Any) -> Unit
-) : TransitionAnimation {
-    override val isRunning: Boolean
-        get() {
-            if (slideAnim?.isRunning == true) {
-                return true
-            }
-            if (state != currentState) {
-                if (state == AnimStates.Entering && enter != null) {
-                    return true
-                } else if (state == AnimStates.Exiting && exit != null) {
-                    return true
-                }
-            }
-            return false
-        }
-    override var state: AnimStates = AnimStates.Gone
-    var currentState: AnimStates = AnimStates.Gone
-    override val modifier: Modifier = Modifier.composed {
-        SlideModifier()
-    }
-
-    inner class SlideModifier : LayoutModifier {
-        override fun MeasureScope.measure(
-            measurable: Measurable,
-            constraints: Constraints
-        ): MeasureResult {
-            val placeable = measurable.measure(constraints)
-
-            updateAnimation(IntSize(placeable.width, placeable.height))
-            return layout(placeable.width, placeable.height) {
-                placeable.place(slideAnim?.value ?: IntOffset.Zero)
-            }
-        }
-    }
-
-    fun updateAnimation(fullSize: IntSize) {
-        if (state == currentState) {
-            return
-        }
-        // state changed
-        if (state == AnimStates.Entering) {
-            // Animation is interrupted from slide out, now slide in
-            enter?.apply {
-                // If slide in animation specified, use that. Otherwise use default.
-                val anim = if (slideAnim?.isRunning != true) {
-                    Animatable(
-                        slideOffset(fullSize), IntOffset.VectorConverter, IntOffset(1, 1)
-                    )
-                } else {
-                    slideAnim
-                }
-                scope.launch {
-                    anim!!.animateTo(IntOffset.Zero, animationSpec)
-                    listener(AnimationEndReason.Finished, anim.value)
-                }
-                slideAnim = anim
-            } ?: slideAnim?.also {
-                scope.launch {
-                    it.animateTo(IntOffset.Zero)
-                    listener(AnimationEndReason.Finished, it.value)
-                }
-            }
-        } else if (state == AnimStates.Exiting) {
-            // interrupting alpha animation: directly animating to out value if defined,
-            // otherwise let it finish
-            exit?.apply {
-                val anim = slideAnim
-                    ?: Animatable(
-                        IntOffset.Zero, IntOffset.VectorConverter, IntOffset(1, 1)
-                    )
-                scope.launch {
-                    anim.animateTo(slideOffset(fullSize), animationSpec)
-                    listener(AnimationEndReason.Finished, anim.value)
-                }
-                slideAnim = anim
-            }
-        }
-        currentState = state
-    }
-
-    var slideAnim: Animatable<IntOffset, AnimationVector2D>? = null
-}
-
-private class ChangeSizeTransition(
-    val enter: ChangeSize? = null,
-    val exit: ChangeSize? = null,
-    val scope: CoroutineScope,
-    override val listener: (AnimationEndReason, Any) -> Unit
-) : TransitionAnimation {
-
-    override val isRunning: Boolean
-        get() {
-            if (sizeAnim?.isAnimating == true) {
-                return true
-            }
-
-            // If the state has changed, and corresponding animations are defined, then animation
-            // will be running in this current frame in the layout stage.
-            if (state != currentState) {
-                if (state == AnimStates.Entering && enter != null) {
-                    return true
-                } else if (state == AnimStates.Exiting && exit != null) {
-                    return true
-                }
-            }
-            return false
-        }
-
-    // This is the pending state, which sets currentState in layout stage
-    override var state: AnimStates = AnimStates.Gone
-
-    // This tracks the current resolved state. State change happens in composition, but the
-    // resolution happens during layout, since we won't know the size until then.
-    var currentState: AnimStates = AnimStates.Gone
-
-    override fun getAnimatedSize(fullSize: IntSize): Pair<IntOffset, IntSize> {
-        sizeAnim?.apply {
-            if (state == currentState) {
-                // If no state change, return the current size animation value.
-                if (state == AnimStates.Entering) {
-                    animateTo(fullSize, alignment, fullSize, spring(), scope)
-                } else if (state == AnimStates.Visible) {
-                    return snapTo(fullSize, scope) to fullSize
-                }
-                return offset(fullSize) to size
-            }
-        }
-
-        // If we get here, animate state has changed.
-        if (state == AnimStates.Entering) {
-            if (enter != null) {
-                // if no on-going size animation, create a new one.
-                val anim = sizeAnim?.run {
-                    // If the animation is not running and the alignment isn't the same, prefer
-                    // AlignmentBasedSizeAnimation over rect based animation.
-                    if (!isAnimating) {
-                        null
-                    } else {
-                        this
-                    }
-                } ?: AlignmentBasedSizeAnimation(
-                    Animatable(
-                        enter.startSize.invoke(fullSize),
-                        IntSize.VectorConverter,
-                        visibilityThreshold = IntSize(1, 1)
-                    ),
-                    enter.alignment, enter.clip, listener
-                )
-                // Animate to full size
-                sizeAnim = anim.animateTo(
-                    fullSize, enter.alignment, fullSize, enter.animationSpec, scope
-                )
-            } else {
-                // If enter isn't defined for size change, re-target the current animation, if any
-                sizeAnim?.apply {
-                    animateTo(fullSize, alignment, fullSize, spring(), scope)
-                }
-            }
-        } else if (state == AnimStates.Exiting) {
-            exit?.apply {
-                // If a size change exit animation is defined, re-target on-going animation if
-                // any, otherwise create a new one.
-                val anim = sizeAnim?.run {
-                    // If the current size animation is idling, switch to AlignmentBasedAnimation if
-                    // needed.
-                    if (isRunning && alignment != exit.alignment) {
-                        null
-                    } else {
-                        this
-                    }
-                } ?: AlignmentBasedSizeAnimation(
-                    Animatable(fullSize, IntSize.VectorConverter, IntSize(1, 1)),
-                    alignment, clip, listener
-                )
-
-                sizeAnim = anim.animateTo(
-                    startSize(fullSize), alignment, fullSize, animationSpec, scope
-                )
-            }
-            // If exit isn't defined, but the enter animation is still on-going, let it finish
-        }
-        currentState = state
-        return sizeAnim?.run { offset(fullSize) to size } ?: IntOffset.Zero to fullSize
-    }
-
-    override val modifier: Modifier
-        get() {
-            val clip: Boolean = sizeAnim?.clip
-                ?: if (state == AnimStates.Entering) {
-                    enter?.clip
-                } else {
-                    exit?.clip
-                } ?: false
-            return if (clip) Modifier.clipToBounds() else Modifier
-        }
-
-    var sizeAnim: SizeAnimation? = null
-}
-
-@OptIn(ExperimentalAnimationApi::class)
-internal class TransitionAnimations constructor(
+@SuppressLint("ModifierFactoryExtensionFunction", "ComposableModifierFactory")
+@Composable
+internal fun Transition<EnterExitState>.createModifier(
     enter: EnterTransition,
-    exit: ExitTransition,
-    scope: CoroutineScope,
-    onFinished: () -> Unit
-) {
-    // This happens during composition.
-    fun updateState(state: AnimStates) {
-        animations.fastForEach { it.state = state }
-    }
+    exit: ExitTransition
+): Modifier {
 
-    val listener: (AnimationEndReason, Any) -> Unit = { reason, _ ->
-        if (reason == AnimationEndReason.Finished && !isAnimating) {
-            onFinished()
+    // Generates up to 3 modifiers, one for each type of enter/exit transition in the order:
+    // slide then shrink/expand then alpha.
+    var modifier: Modifier = Modifier
+
+    modifier = modifier.slideInOut(
+        this,
+        rememberUpdatedState(enter.data.slide),
+        rememberUpdatedState(exit.data.slide)
+    ).shrinkExpand(
+        this,
+        rememberUpdatedState(enter.data.changeSize),
+        rememberUpdatedState(exit.data.changeSize)
+    )
+
+    // Fade - it's important to put fade in the end. Otherwise fade will clip slide.
+    // We'll animate if at any point during the transition fadeIn/fadeOut becomes non-null. This
+    // would ensure the removal of fadeIn/Out amid a fade animation doesn't result in a jump.
+    var shouldAnimateAlpha by remember(this) { mutableStateOf(false) }
+    if (currentState == targetState) {
+        shouldAnimateAlpha = false
+    } else {
+        if (enter.data.fade != null || exit.data.fade != null) {
+            shouldAnimateAlpha = true
         }
     }
 
-    // This is called after measure before placement.
-    fun getAnimatedSize(fullSize: IntSize): Pair<IntOffset, IntSize>? {
-        animations.fastForEach {
-            val animSize = it.getAnimatedSize(fullSize)
-            if (animSize != null) {
-                return animSize
+    if (shouldAnimateAlpha) {
+        val alpha by animateFloat(
+            transitionSpec = {
+                when {
+                    EnterExitState.PreEnter isTransitioningTo EnterExitState.Visible ->
+                        enter.data.fade?.animationSpec ?: spring()
+                    EnterExitState.Visible isTransitioningTo EnterExitState.PostExit ->
+                        exit.data.fade?.animationSpec ?: spring()
+                    else -> spring()
+                }
+            },
+            label = "alpha"
+        ) {
+            when (it) {
+                EnterExitState.Visible -> 1f
+                EnterExitState.PreEnter -> enter.data.fade?.alpha ?: 1f
+                EnterExitState.PostExit -> exit.data.fade?.alpha ?: 1f
             }
         }
-        return null
+        modifier = modifier.graphicsLayer {
+            this.alpha = alpha
+        }
     }
+    return modifier
+}
 
-    val isAnimating: Boolean
-        get() = animations.fastFirstOrNull { it.isRunning }?.isRunning ?: false
-
-    val animations: List<TransitionAnimation>
-
-    init {
-        animations = mutableListOf()
-        // Only set up animations when either enter or exit transition is defined.
-        if (enter.data.slide != null || exit.data.slide != null) {
-            animations.add(
-                SlideTransition(enter.data.slide, exit.data.slide, scope, listener)
-            )
-        }
-        if (enter.data.changeSize != null || exit.data.changeSize != null) {
-            animations.add(
-                ChangeSizeTransition(enter.data.changeSize, exit.data.changeSize, scope, listener)
-            )
-        }
-        if (enter.data.fade != null || exit.data.fade != null) {
-            animations.add(
-                FadeTransition(enter.data.fade, exit.data.fade, scope, listener)
-            )
+@SuppressLint("ModifierInspectorInfo")
+private fun Modifier.slideInOut(
+    transition: Transition<EnterExitState>,
+    slideIn: State<Slide?>,
+    slideOut: State<Slide?>
+): Modifier = composed {
+    // We'll animate if at any point during the transition slideIn/slideOut becomes non-null. This
+    // would ensure the removal of slideIn/Out amid a slide animation doesn't result in a jump.
+    var shouldAnimate by remember(transition) { mutableStateOf(false) }
+    if (transition.currentState == transition.targetState) {
+        shouldAnimate = false
+    } else {
+        if (slideIn.value != null || slideOut.value != null) {
+            shouldAnimate = true
         }
     }
 
-    val modifier: Modifier
-        get() {
-            var modifier: Modifier = Modifier
-            animations.fastForEach { modifier = modifier.then(it.modifier) }
-            return modifier
+    if (shouldAnimate) {
+        val animation = transition.createDeferredAnimation(IntOffset.VectorConverter, "slide")
+        val modifier = remember(transition) {
+            SlideModifier(animation, slideIn, slideOut)
         }
+        this.then(modifier)
+    } else {
+        this
+    }
+}
+
+private val defaultOffsetAnimationSpec = spring(visibilityThreshold = IntOffset.VisibilityThreshold)
+
+private class SlideModifier(
+    val lazyAnimation: Transition<EnterExitState>.DeferredAnimation<IntOffset, AnimationVector2D>,
+    val slideIn: State<Slide?>,
+    val slideOut: State<Slide?>
+) : LayoutModifier {
+    val transitionSpec: Transition.Segment<EnterExitState>.() -> FiniteAnimationSpec<IntOffset> =
+        {
+            when {
+                EnterExitState.PreEnter isTransitioningTo EnterExitState.Visible -> {
+                    slideIn.value?.animationSpec ?: defaultOffsetAnimationSpec
+                }
+                EnterExitState.Visible isTransitioningTo EnterExitState.PostExit -> {
+                    slideOut.value?.animationSpec ?: defaultOffsetAnimationSpec
+                }
+                else -> defaultOffsetAnimationSpec
+            }
+        }
+
+    fun targetValueByState(targetState: EnterExitState, fullSize: IntSize): IntOffset {
+        val preEnter = slideIn.value?.slideOffset?.invoke(fullSize) ?: IntOffset.Zero
+        val postExit = slideOut.value?.slideOffset?.invoke(fullSize) ?: IntOffset.Zero
+        return when (targetState) {
+            EnterExitState.Visible -> IntOffset.Zero
+            EnterExitState.PreEnter -> preEnter
+            EnterExitState.PostExit -> postExit
+        }
+    }
+
+    override fun MeasureScope.measure(
+        measurable: Measurable,
+        constraints: Constraints
+    ): MeasureResult {
+        val placeable = measurable.measure(constraints)
+
+        val measuredSize = IntSize(placeable.width, placeable.height)
+        return layout(placeable.width, placeable.height) {
+            val slideOffset = lazyAnimation.animate(
+                transitionSpec
+            ) {
+                targetValueByState(it, measuredSize)
+            }
+            placeable.placeWithLayer(slideOffset.value)
+        }
+    }
+}
+
+@SuppressLint("ModifierInspectorInfo")
+private fun Modifier.shrinkExpand(
+    transition: Transition<EnterExitState>,
+    expand: State<ChangeSize?>,
+    shrink: State<ChangeSize?>
+): Modifier = composed {
+    // We'll animate if at any point during the transition shrink/expand becomes non-null. This
+    // would ensure the removal of shrink/expand amid a size change animation doesn't result in a
+    // jump.
+    var shouldAnimate by remember(transition) { mutableStateOf(false) }
+    if (transition.currentState == transition.targetState) {
+        shouldAnimate = false
+    } else {
+        if (expand.value != null || shrink.value != null) {
+            shouldAnimate = true
+        }
+    }
+
+    if (shouldAnimate) {
+        val alignment: State<Alignment?> = rememberUpdatedState(
+            with(transition.segment) {
+                EnterExitState.PreEnter isTransitioningTo EnterExitState.Visible
+            }.let {
+                if (it) {
+                    expand.value?.alignment ?: shrink.value?.alignment
+                } else {
+                    shrink.value?.alignment ?: expand.value?.alignment
+                }
+            }
+        )
+        val sizeAnimation = transition.createDeferredAnimation(
+            IntSize.VectorConverter,
+            "shrink/expand"
+        )
+        val offsetAnimation = key(transition.currentState == transition.targetState) {
+            transition.createDeferredAnimation(
+                IntOffset.VectorConverter,
+                "InterruptionHandlingOffset"
+            )
+        }
+
+        val expandShrinkModifier = remember(transition) {
+            ExpandShrinkModifier(
+                sizeAnimation,
+                offsetAnimation,
+                expand,
+                shrink,
+                alignment
+            )
+        }
+
+        if (transition.currentState == transition.targetState) {
+            expandShrinkModifier.currentAlignment = null
+        } else if (expandShrinkModifier.currentAlignment == null) {
+            expandShrinkModifier.currentAlignment = alignment.value ?: Alignment.TopStart
+        }
+        this.clipToBounds().then(expandShrinkModifier)
+    } else {
+        this
+    }
+}
+
+private val defaultSizeAnimationSpec = spring(visibilityThreshold = IntSize.VisibilityThreshold)
+
+private class ExpandShrinkModifier(
+    val sizeAnimation: Transition<EnterExitState>.DeferredAnimation<IntSize, AnimationVector2D>,
+    val offsetAnimation: Transition<EnterExitState>.DeferredAnimation<IntOffset,
+        AnimationVector2D>,
+    val expand: State<ChangeSize?>,
+    val shrink: State<ChangeSize?>,
+    val alignment: State<Alignment?>
+) : LayoutModifier {
+    var currentAlignment: Alignment? = null
+    val sizeTransitionSpec: Transition.Segment<EnterExitState>.() -> FiniteAnimationSpec<IntSize> =
+        {
+            when {
+                EnterExitState.PreEnter isTransitioningTo EnterExitState.Visible ->
+                    expand.value?.animationSpec
+                EnterExitState.Visible isTransitioningTo EnterExitState.PostExit ->
+                    shrink.value?.animationSpec
+                else -> defaultSizeAnimationSpec
+            } ?: defaultSizeAnimationSpec
+        }
+
+    fun sizeByState(targetState: EnterExitState, fullSize: IntSize): IntSize {
+        val preEnterSize = expand.value?.let { it.size(fullSize) } ?: fullSize
+        val postExitSize = shrink.value?.let { it.size(fullSize) } ?: fullSize
+
+        return when (targetState) {
+            EnterExitState.Visible -> fullSize
+            EnterExitState.PreEnter -> preEnterSize
+            EnterExitState.PostExit -> postExitSize
+        }
+    }
+
+    // This offset is only needed when the alignment value changes during the shrink/expand
+    // animation. For example, if user specify an enter that expands from the left, and an exit
+    // that shrinks towards the right, the asymmetric enter/exit will be brittle to interruption.
+    // Hence the following offset animation to smooth over such interruption.
+    fun targetOffsetByState(targetState: EnterExitState, fullSize: IntSize): IntOffset =
+        when {
+            currentAlignment == null -> IntOffset.Zero
+            alignment.value == null -> IntOffset.Zero
+            currentAlignment == alignment.value -> IntOffset.Zero
+            else -> when (targetState) {
+                EnterExitState.Visible -> IntOffset.Zero
+                EnterExitState.PreEnter -> IntOffset.Zero
+                EnterExitState.PostExit -> shrink.value?.let {
+                    val endSize = it.size(fullSize)
+                    val targetOffset = alignment.value!!.align(
+                        fullSize,
+                        endSize,
+                        LayoutDirection.Ltr
+                    )
+                    val currentOffset = currentAlignment!!.align(
+                        fullSize,
+                        endSize,
+                        LayoutDirection.Ltr
+                    )
+                    targetOffset - currentOffset
+                } ?: IntOffset.Zero
+            }
+        }
+
+    override fun MeasureScope.measure(
+        measurable: Measurable,
+        constraints: Constraints
+    ): MeasureResult {
+        val placeable = measurable.measure(constraints)
+
+        val measuredSize = IntSize(placeable.width, placeable.height)
+        val currentSize = sizeAnimation.animate(sizeTransitionSpec) {
+            sizeByState(it, measuredSize)
+        }.value
+
+        val offsetDelta = offsetAnimation.animate({ defaultOffsetAnimationSpec }) {
+            targetOffsetByState(it, measuredSize)
+        }.value
+
+        val offset =
+            currentAlignment?.align(measuredSize, currentSize, LayoutDirection.Ltr)
+                ?: IntOffset.Zero
+        return layout(currentSize.width, currentSize.height) {
+            placeable.place(offset.x + offsetDelta.x, offset.y + offsetDelta.y)
+        }
+    }
 }
diff --git a/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/AbstractIrTransformTest.kt b/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/AbstractIrTransformTest.kt
index a7e6062..5f0aad8 100644
--- a/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/AbstractIrTransformTest.kt
+++ b/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/AbstractIrTransformTest.kt
@@ -206,6 +206,11 @@
                     "${it.groupValues[1]}<>"
                 }
             }
+            .replace(
+                Regex("(sourceInformationMarkerStart\\(%composer, )([-\\d]+)")
+            ) {
+                "${it.groupValues[1]}<>"
+            }
             // replace source information with source it references
             .replace(
                 Regex(
@@ -214,10 +219,15 @@
                 )
             ) {
                 "${it.groupValues[1]}\"${
-                generateSourceInfo(it.groupValues[3], source)
+                generateSourceInfo(it.groupValues[4], source)
                 }\")"
             }
             .replace(
+                Regex("(sourceInformation(MarkerStart)?\\(.*)\"(.*)\"\\)")
+            ) {
+                "${it.groupValues[1]}\"${generateSourceInfo(it.groupValues[3], source)}\")"
+            }
+            .replace(
                 Regex(
                     "(composableLambda[N]?\\" +
                         "([^\"\\n]*)\"(.*)\"\\)"
@@ -235,16 +245,15 @@
             ) {
                 "${it.groupValues[1]}<>"
             }
-            // composableLambdaInstance(<>, true, )
+            // composableLambdaInstance(<>, true)
             .replace(
                 Regex(
-                    "(composableLambdaInstance\\()([-\\d]+, (true|false), (null|\"(.*)\")\\))"
+                    "(composableLambdaInstance\\()([-\\d]+, (true|false))"
                 )
             ) {
                 val callStart = it.groupValues[1]
                 val tracked = it.groupValues[3]
-                val sourceInfo = it.groupValues[5]
-                "$callStart<>, $tracked, \"${generateSourceInfo(sourceInfo, source)}\")"
+                "$callStart<>, $tracked"
             }
             // composableLambda(%composer, <>, true)
             .replace(
diff --git a/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/ClassStabilityTransformTests.kt b/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/ClassStabilityTransformTests.kt
index 0c6ce92..9cddb39 100644
--- a/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/ClassStabilityTransformTests.kt
+++ b/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/ClassStabilityTransformTests.kt
@@ -543,7 +543,8 @@
         """
             @Composable
             fun A(y: Any?, %composer: Composer?, %changed: Int, %default: Int) {
-              %composer = %composer.startRestartGroup(<>, "C(A)<A()>,<A(Empt...>,<A(Sing...>,<A(Sing...>,<A(Sing...>,<A(Sing...>,<A(Sing...>,<A(Sing...>,<A(Doub...>,<A(Doub...>,<A(Doub...>,<A(Doub...>,<A(X(li...>,<A(X(li...>,<A(NonB...>,<A(NonB...>,<A(Stab...>,<A(Unst...>:Test.kt")
+              %composer = %composer.startRestartGroup(<>)
+              sourceInformation(%composer, "C(A)<A()>,<A(Empt...>,<A(Sing...>,<A(Sing...>,<A(Sing...>,<A(Sing...>,<A(Sing...>,<A(Sing...>,<A(Doub...>,<A(Doub...>,<A(Doub...>,<A(Doub...>,<A(X(li...>,<A(X(li...>,<A(NonB...>,<A(NonB...>,<A(Stab...>,<A(Unst...>:Test.kt")
               val %dirty = %changed
               if (%default and 0b0001 !== 0) {
                 %dirty = %dirty or 0b0010
@@ -674,7 +675,8 @@
             }
             @Composable
             fun A(y: Any, %composer: Composer?, %changed: Int) {
-              %composer = %composer.startRestartGroup(<>, "C(A)<A(X(li...>,<A(Stab...>,<A(Unst...>:Test.kt")
+              %composer = %composer.startRestartGroup(<>)
+              sourceInformation(%composer, "C(A)<A(X(li...>,<A(Stab...>,<A(Unst...>:Test.kt")
               used(y)
               A(X(listOf(StableClass())), %composer, 0b1000)
               A(StableDelegateProp(), %composer, 0)
@@ -708,7 +710,8 @@
         """
             @Composable
             fun A(y: Any, %composer: Composer?, %changed: Int) {
-              %composer = %composer.startRestartGroup(<>, "C(A)<A(Wrap...>:Test.kt")
+              %composer = %composer.startRestartGroup(<>)
+              sourceInformation(%composer, "C(A)<A(Wrap...>:Test.kt")
               used(y)
               A(Wrapper(Foo()), %composer, Wrapper.%stable)
               %composer.endRestartGroup()?.updateScope { %composer: Composer?, %force: Int ->
@@ -746,7 +749,8 @@
         """
             @Composable
             fun <V> B(value: V, %composer: Composer?, %changed: Int) {
-              %composer = %composer.startRestartGroup(<>, "C(B)<A(Wrap...>:Test.kt")
+              %composer = %composer.startRestartGroup(<>)
+              sourceInformation(%composer, "C(B)<A(Wrap...>:Test.kt")
               val %dirty = %changed
               if (%changed and 0b1110 === 0) {
                 %dirty = %dirty or if (%composer.changed(value)) 0b0100 else 0b0010
@@ -762,7 +766,8 @@
             }
             @Composable
             fun <T> X(items: List<T>, itemContent: Function3<T, Composer, Int, Unit>, %composer: Composer?, %changed: Int) {
-              %composer = %composer.startRestartGroup(<>, "C(X)P(1)*<itemCo...>:Test.kt")
+              %composer = %composer.startRestartGroup(<>)
+              sourceInformation(%composer, "C(X)P(1)*<itemCo...>:Test.kt")
               val %dirty = %changed
               val tmp0_iterator = items.iterator()
               while (tmp0_iterator.hasNext()) {
@@ -775,14 +780,16 @@
             }
             @Composable
             fun C(items: List<String>, %composer: Composer?, %changed: Int) {
-              %composer = %composer.startRestartGroup(<>, "C(C)<X(item...>:Test.kt")
+              %composer = %composer.startRestartGroup(<>)
+              sourceInformation(%composer, "C(C)<X(item...>:Test.kt")
               X(items, ComposableSingletons%TestKt.lambda-1, %composer, 0b1000)
               %composer.endRestartGroup()?.updateScope { %composer: Composer?, %force: Int ->
                 C(items, %composer, %changed or 0b0001)
               }
             }
             internal object ComposableSingletons%TestKt {
-              val lambda-1: Function3<String, Composer, Int, Unit> = composableLambdaInstance(<>, false, "C<A(item...>,<A(Wrap...>:Test.kt") { item: String, %composer: Composer?, %changed: Int ->
+              val lambda-1: Function3<String, Composer, Int, Unit> = composableLambdaInstance(<>, false) { item: String, %composer: Composer?, %changed: Int ->
+                sourceInformation(%composer, "C<A(item...>,<A(Wrap...>:Test.kt")
                 val %dirty = %changed
                 if (%changed and 0b1110 === 0) {
                   %dirty = %dirty or if (%composer.changed(item)) 0b0100 else 0b0010
@@ -837,7 +844,8 @@
             }
             @Composable
             fun A(y: Int, x: Any, %composer: Composer?, %changed: Int) {
-              %composer = %composer.startRestartGroup(<>, "C(A)P(1)<B(x)>:Test.kt")
+              %composer = %composer.startRestartGroup(<>)
+              sourceInformation(%composer, "C(A)P(1)<B(x)>:Test.kt")
               used(y)
               B(x, %composer, 0b1000)
               %composer.endRestartGroup()?.updateScope { %composer: Composer?, %force: Int ->
@@ -846,7 +854,8 @@
             }
             @Composable
             fun B(x: Any, %composer: Composer?, %changed: Int) {
-              %composer = %composer.startRestartGroup(<>, "C(B):Test.kt")
+              %composer = %composer.startRestartGroup(<>)
+              sourceInformation(%composer, "C(B):Test.kt")
               used(x)
               %composer.endRestartGroup()?.updateScope { %composer: Composer?, %force: Int ->
                 B(x, %composer, %changed or 0b0001)
@@ -876,7 +885,8 @@
             }
             @Composable
             fun A(y: Int, x: Foo, %composer: Composer?, %changed: Int) {
-              %composer = %composer.startRestartGroup(<>, "C(A)P(1)<B(x)>:Test.kt")
+              %composer = %composer.startRestartGroup(<>)
+              sourceInformation(%composer, "C(A)P(1)<B(x)>:Test.kt")
               used(y)
               B(x, %composer, 0b1000)
               %composer.endRestartGroup()?.updateScope { %composer: Composer?, %force: Int ->
@@ -885,7 +895,8 @@
             }
             @Composable
             fun B(x: Any, %composer: Composer?, %changed: Int) {
-              %composer = %composer.startRestartGroup(<>, "C(B):Test.kt")
+              %composer = %composer.startRestartGroup(<>)
+              sourceInformation(%composer, "C(B):Test.kt")
               used(x)
               %composer.endRestartGroup()?.updateScope { %composer: Composer?, %force: Int ->
                 B(x, %composer, %changed or 0b0001)
diff --git a/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/ComposerParamTransformTests.kt b/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/ComposerParamTransformTests.kt
index 26c88f32..ae972e1 100644
--- a/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/ComposerParamTransformTests.kt
+++ b/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/ComposerParamTransformTests.kt
@@ -67,7 +67,8 @@
             val bar: Int
               @Composable @JvmName(name = "getBar")
               get() {
-                %composer.startReplaceableGroup(<>, "C:Test.kt#2487m")
+                %composer.startReplaceableGroup(<>)
+                sourceInformation(%composer, "C:Test.kt#2487m")
                 val tmp0 = 123
                 %composer.endReplaceableGroup()
                 return tmp0
@@ -75,7 +76,8 @@
             @NonRestartableComposable
             @Composable
             fun Example(%composer: Composer?, %changed: Int) {
-              %composer.startReplaceableGroup(<>, "C(Example)<bar>:Test.kt#2487m")
+              %composer.startReplaceableGroup(<>)
+              sourceInformation(%composer, "C(Example)<bar>:Test.kt#2487m")
               bar
               %composer.endReplaceableGroup()
             }
@@ -110,7 +112,8 @@
               @NonRestartableComposable
               @Composable
               override fun bar(%composer: Composer?, %changed: Int) {
-                %composer.startReplaceableGroup(<>, "C(bar):Test.kt#2487m")
+                %composer.startReplaceableGroup(<>)
+                sourceInformation(%composer, "C(bar):Test.kt#2487m")
                 %composer.endReplaceableGroup()
               }
               static val %stable: Int = 0
@@ -143,18 +146,21 @@
             @NonRestartableComposable
             @Composable
             fun Wat(%composer: Composer?, %changed: Int) {
-              %composer.startReplaceableGroup(<>, "C(Wat):Test.kt#2487m")
+              %composer.startReplaceableGroup(<>)
+              sourceInformation(%composer, "C(Wat):Test.kt#2487m")
               %composer.endReplaceableGroup()
             }
             @NonRestartableComposable
             @Composable
             fun Foo(x: Int, %composer: Composer?, %changed: Int) {
-              %composer.startReplaceableGroup(<>, "C(Foo)<Wat()>,<goo()>,<baz()>:Test.kt#2487m")
+              %composer.startReplaceableGroup(<>)
+              sourceInformation(%composer, "C(Foo)<Wat()>,<goo()>,<baz()>:Test.kt#2487m")
               Wat(%composer, 0)
               @NonRestartableComposable
               @Composable
               fun goo(%composer: Composer?, %changed: Int) {
-                %composer.startReplaceableGroup(<>, "C(goo)<Wat()>:Test.kt#2487m")
+                %composer.startReplaceableGroup(<>)
+                sourceInformation(%composer, "C(goo)<Wat()>:Test.kt#2487m")
                 Wat(%composer, 0)
                 %composer.endReplaceableGroup()
               }
@@ -162,7 +168,8 @@
                 @NonRestartableComposable
                 @Composable
                 fun baz(%composer: Composer?, %changed: Int) {
-                  %composer.startReplaceableGroup(<>, "C(baz)<Wat()>:Test.kt#2487m")
+                  %composer.startReplaceableGroup(<>)
+                  sourceInformation(%composer, "C(baz)<Wat()>:Test.kt#2487m")
                   Wat(%composer, 0)
                   %composer.endReplaceableGroup()
                 }
@@ -245,7 +252,8 @@
             @NonRestartableComposable
             @Composable
             fun Example(%composer: Composer?, %changed: Int) {
-              %composer.startReplaceableGroup(<>, "C(Example)<Exampl...>:Test.kt#2487m")
+              %composer.startReplaceableGroup(<>)
+              sourceInformation(%composer, "C(Example)<Exampl...>:Test.kt#2487m")
               Example(%composer, 0)
               %composer.endReplaceableGroup()
             }
@@ -267,16 +275,19 @@
         """
             @Composable
             fun Example(content: Function2<Composer, Int, Unit>, %composer: Composer?, %changed: Int) {
-              %composer.startReplaceableGroup(<>, "C(Example)<conten...>:Test.kt#2487m")
+              %composer.startReplaceableGroup(<>)
+              sourceInformation(%composer, "C(Example)<conten...>:Test.kt#2487m")
               content(%composer, 0b1110 and %changed)
               %composer.endReplaceableGroup()
             }
             @NonRestartableComposable
             @Composable
             fun Test(%composer: Composer?, %changed: Int) {
-              %composer.startReplaceableGroup(<>, "C(Test)<Exampl...>:Test.kt#2487m")
+              %composer.startReplaceableGroup(<>)
+              sourceInformation(%composer, "C(Test)<Exampl...>:Test.kt#2487m")
               Example({ %composer: Composer?, %changed: Int ->
-                %composer.startReplaceableGroup(<>, "C:Test.kt#2487m")
+                %composer.startReplaceableGroup(<>)
+                sourceInformation(%composer, "C:Test.kt#2487m")
                 if (%changed and 0b1011 xor 0b0010 !== 0 || !%composer.skipping) {
                   Unit
                 } else {
@@ -300,7 +311,8 @@
             val myProperty: Function0<Unit>
               @Composable @JvmName(name = "getMyProperty")
               get() {
-                %composer.startReplaceableGroup(<>, "C:Test.kt#2487m")
+                %composer.startReplaceableGroup(<>)
+                sourceInformation(%composer, "C:Test.kt#2487m")
                 val tmp0 = {
                 }
                 %composer.endReplaceableGroup()
@@ -369,7 +381,8 @@
             """
                 @Composable
                 fun Wrapper(block: Function2<Composer, Int, Unit>, %composer: Composer?, %changed: Int) {
-                  %composer = %composer.startRestartGroup(<>, "C(Wrapper)<block(...>:Test.kt#2487m")
+                  %composer = %composer.startRestartGroup(<>)
+                  sourceInformation(%composer, "C(Wrapper)<block(...>:Test.kt#2487m")
                   val %dirty = %changed
                   if (%changed and 0b1110 === 0) {
                     %dirty = %dirty or if (%composer.changed(block)) 0b0100 else 0b0010
@@ -385,7 +398,8 @@
                 }
                 @Composable
                 fun Leaf(text: String, %composer: Composer?, %changed: Int) {
-                  %composer = %composer.startRestartGroup(<>, "C(Leaf):Test.kt#2487m")
+                  %composer = %composer.startRestartGroup(<>)
+                  sourceInformation(%composer, "C(Leaf):Test.kt#2487m")
                   val %dirty = %changed
                   if (%changed and 0b1110 === 0) {
                     %dirty = %dirty or if (%composer.changed(text)) 0b0100 else 0b0010
@@ -401,14 +415,17 @@
                 }
                 @Composable
                 fun Test(value: Int, %composer: Composer?, %changed: Int) {
-                  %composer = %composer.startRestartGroup(<>, "C(Test):Test.kt#2487m")
+                  %composer = %composer.startRestartGroup(<>)
+                  sourceInformation(%composer, "C(Test):Test.kt#2487m")
                   val %dirty = %changed
                   if (%changed and 0b1110 === 0) {
                     %dirty = %dirty or if (%composer.changed(value)) 0b0100 else 0b0010
                   }
                   if (%dirty and 0b1011 xor 0b0010 !== 0 || !%composer.skipping) {
-                    %composer.startMovableGroup(<>, value, "<Wrappe...>")
-                    Wrapper(composableLambda(%composer, <>, true, "C<Leaf("...>:Test.kt#2487m") { %composer: Composer?, %changed: Int ->
+                    %composer.startMovableGroup(<>, value)
+                    sourceInformation(%composer, "<Wrappe...>")
+                    Wrapper(composableLambda(%composer, <>, true) { %composer: Composer?, %changed: Int ->
+                      sourceInformation(%composer, "C<Leaf("...>:Test.kt#2487m")
                       if (%changed and 0b1011 xor 0b0010 !== 0 || !%composer.skipping) {
                         Leaf("Value %value", %composer, 0)
                       } else {
@@ -486,17 +503,20 @@
             """
                 @Composable
                 fun composeVector(composable: Function2<Composer, Int, Unit>, %composer: Composer?, %changed: Int) {
-                  %composer = %composer.startRestartGroup(<>, "C(composeVector)<emit>:Test.kt#2487m")
+                  %composer = %composer.startRestartGroup(<>)
+                  sourceInformation(%composer, "C(composeVector)<emit>:Test.kt#2487m")
                   val %dirty = %changed
                   if (%changed and 0b1110 === 0) {
                     %dirty = %dirty or if (%composer.changed(composable)) 0b0100 else 0b0010
                   }
                   if (%dirty and 0b1011 xor 0b0010 !== 0 || !%composer.skipping) {
                     emit({ %composer: Composer?, %changed: Int ->
-                      %composer.startReplaceableGroup(<>, "C<emit>:Test.kt#2487m")
+                      %composer.startReplaceableGroup(<>)
+                      sourceInformation(%composer, "C<emit>:Test.kt#2487m")
                       if (%changed and 0b1011 xor 0b0010 !== 0 || !%composer.skipping) {
                         emit({ %composer: Composer?, %changed: Int ->
-                          %composer.startReplaceableGroup(<>, "C<compos...>:Test.kt#2487m")
+                          %composer.startReplaceableGroup(<>)
+                          sourceInformation(%composer, "C<compos...>:Test.kt#2487m")
                           if (%changed and 0b1011 xor 0b0010 !== 0 || !%composer.skipping) {
                             composable(%composer, 0b1110 and %dirty)
                           } else {
@@ -518,7 +538,8 @@
                 }
                 @Composable
                 fun emit(composable: Function2<Composer, Int, Unit>, %composer: Composer?, %changed: Int) {
-                  %composer.startReplaceableGroup(<>, "C(emit)<compos...>:Test.kt#2487m")
+                  %composer.startReplaceableGroup(<>)
+                  sourceInformation(%composer, "C(emit)<compos...>:Test.kt#2487m")
                   composable(%composer, 0b1110 and %changed)
                   %composer.endReplaceableGroup()
                 }
diff --git a/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/ControlFlowTransformTests.kt b/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/ControlFlowTransformTests.kt
index 53823d7..943df22 100644
--- a/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/ControlFlowTransformTests.kt
+++ b/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/ControlFlowTransformTests.kt
@@ -35,7 +35,8 @@
             @NonRestartableComposable
             @Composable
             fun Example(x: Int, %composer: Composer?, %changed: Int) {
-              %composer.startReplaceableGroup(<>, "C(Example):Test.kt")
+              %composer.startReplaceableGroup(<>)
+              sourceInformation(%composer, "C(Example):Test.kt")
               if (x > 0) {
                 NA()
               }
@@ -61,9 +62,11 @@
             @NonRestartableComposable
             @Composable
             fun Example(x: Int, %composer: Composer?, %changed: Int) {
-              %composer.startReplaceableGroup(<>, "C(Example):Test.kt")
+              %composer.startReplaceableGroup(<>)
+              sourceInformation(%composer, "C(Example):Test.kt")
               if (x > 0) {
-                %composer.startReplaceableGroup(<>, "<A()>")
+                %composer.startReplaceableGroup(<>)
+                sourceInformation(%composer, "<A()>")
                 A(%composer, 0)
                 %composer.endReplaceableGroup()
               } else {
@@ -94,13 +97,16 @@
             @NonRestartableComposable
             @Composable
             fun Example(x: Int, %composer: Composer?, %changed: Int) {
-              %composer.startReplaceableGroup(<>, "C(Example):Test.kt")
+              %composer.startReplaceableGroup(<>)
+              sourceInformation(%composer, "C(Example):Test.kt")
               if (x > 0) {
-                %composer.startReplaceableGroup(<>, "<A(a)>")
+                %composer.startReplaceableGroup(<>)
+                sourceInformation(%composer, "<A(a)>")
                 A(a, %composer, 0)
                 %composer.endReplaceableGroup()
               } else {
-                %composer.startReplaceableGroup(<>, "<A(b)>")
+                %composer.startReplaceableGroup(<>)
+                sourceInformation(%composer, "<A(b)>")
                 A(b, %composer, 0)
                 %composer.endReplaceableGroup()
               }
@@ -128,7 +134,8 @@
             @NonRestartableComposable
             @Composable
             fun Example(x: Int, %composer: Composer?, %changed: Int) {
-              %composer.startReplaceableGroup(<>, "C(Example)<B()>:Test.kt")
+              %composer.startReplaceableGroup(<>)
+              sourceInformation(%composer, "C(Example)<B()>:Test.kt")
               if (B(%composer, 0)) {
                 NA()
               } else {
@@ -162,13 +169,16 @@
             @NonRestartableComposable
             @Composable
             fun Example(x: Int, %composer: Composer?, %changed: Int) {
-              %composer.startReplaceableGroup(<>, "C(Example):Test.kt")
-              if (%composer.startReplaceableGroup(<>, "<B(a)>")
+              %composer.startReplaceableGroup(<>)
+              sourceInformation(%composer, "C(Example):Test.kt")
+              if (%composer.startReplaceableGroup(<>)
+              sourceInformation(%composer, "<B(a)>")
               val tmp0_group = B(a, %composer, 0)
               %composer.endReplaceableGroup()
               tmp0_group) {
                 NA()
-              } else if (%composer.startReplaceableGroup(<>, "<B(b)>")
+              } else if (%composer.startReplaceableGroup(<>)
+              sourceInformation(%composer, "<B(b)>")
               val tmp1_group = B(b, %composer, 0)
               %composer.endReplaceableGroup()
               tmp1_group) {
@@ -198,7 +208,8 @@
             @NonRestartableComposable
             @Composable
             fun Example(x: Int, %composer: Composer?, %changed: Int) {
-              %composer.startReplaceableGroup(<>, "C(Example):Test.kt")
+              %composer.startReplaceableGroup(<>)
+              sourceInformation(%composer, "C(Example):Test.kt")
               val tmp0_subject = x
               when {
                 tmp0_subject == 0 -> {
@@ -235,21 +246,25 @@
             @NonRestartableComposable
             @Composable
             fun Example(x: Int, %composer: Composer?, %changed: Int) {
-              %composer.startReplaceableGroup(<>, "C(Example):Test.kt")
+              %composer.startReplaceableGroup(<>)
+              sourceInformation(%composer, "C(Example):Test.kt")
               val tmp0_subject = x
               when {
                 tmp0_subject == 0 -> {
-                  %composer.startReplaceableGroup(<>, "<A(a)>")
+                  %composer.startReplaceableGroup(<>)
+                  sourceInformation(%composer, "<A(a)>")
                   A(a, %composer, 0)
                   %composer.endReplaceableGroup()
                 }
                 tmp0_subject == 0b0001 -> {
-                  %composer.startReplaceableGroup(<>, "<A(b)>")
+                  %composer.startReplaceableGroup(<>)
+                  sourceInformation(%composer, "<A(b)>")
                   A(b, %composer, 0)
                   %composer.endReplaceableGroup()
                 }
                 else -> {
-                  %composer.startReplaceableGroup(<>, "<A(c)>")
+                  %composer.startReplaceableGroup(<>)
+                  sourceInformation(%composer, "<A(c)>")
                   A(c, %composer, 0)
                   %composer.endReplaceableGroup()
                 }
@@ -278,23 +293,27 @@
             @NonRestartableComposable
             @Composable
             fun Example(x: Int, %composer: Composer?, %changed: Int) {
-              %composer.startReplaceableGroup(<>, "C(Example):Test.kt")
+              %composer.startReplaceableGroup(<>)
+              sourceInformation(%composer, "C(Example):Test.kt")
               val y = val tmp0_subject = x
               when {
                 tmp0_subject == 0 -> {
-                  %composer.startReplaceableGroup(<>, "<R(a)>")
+                  %composer.startReplaceableGroup(<>)
+                  sourceInformation(%composer, "<R(a)>")
                   val tmp0_group = R(a, %composer, 0)
                   %composer.endReplaceableGroup()
                   tmp0_group
                 }
                 tmp0_subject == 0b0001 -> {
-                  %composer.startReplaceableGroup(<>, "<R(b)>")
+                  %composer.startReplaceableGroup(<>)
+                  sourceInformation(%composer, "<R(b)>")
                   val tmp1_group = R(b, %composer, 0)
                   %composer.endReplaceableGroup()
                   tmp1_group
                 }
                 else -> {
-                  %composer.startReplaceableGroup(<>, "<R(c)>")
+                  %composer.startReplaceableGroup(<>)
+                  sourceInformation(%composer, "<R(c)>")
                   val tmp2_group = R(c, %composer, 0)
                   %composer.endReplaceableGroup()
                   tmp2_group
@@ -324,20 +343,24 @@
             @NonRestartableComposable
             @Composable
             fun Example(x: Int, %composer: Composer?, %changed: Int) {
-              %composer.startReplaceableGroup(<>, "C(Example):Test.kt")
+              %composer.startReplaceableGroup(<>)
+              sourceInformation(%composer, "C(Example):Test.kt")
               when {
                 x < 0 -> {
-                  %composer.startReplaceableGroup(<>, "<A(a)>")
+                  %composer.startReplaceableGroup(<>)
+                  sourceInformation(%composer, "<A(a)>")
                   A(a, %composer, 0)
                   %composer.endReplaceableGroup()
                 }
                 x > 30 -> {
-                  %composer.startReplaceableGroup(<>, "<A(b)>")
+                  %composer.startReplaceableGroup(<>)
+                  sourceInformation(%composer, "<A(b)>")
                   A(b, %composer, 0)
                   %composer.endReplaceableGroup()
                 }
                 else -> {
-                  %composer.startReplaceableGroup(<>, "<A(c)>")
+                  %composer.startReplaceableGroup(<>)
+                  sourceInformation(%composer, "<A(c)>")
                   A(c, %composer, 0)
                   %composer.endReplaceableGroup()
                 }
@@ -366,10 +389,12 @@
             @NonRestartableComposable
             @Composable
             fun Example(x: Int, %composer: Composer?, %changed: Int) {
-              %composer.startReplaceableGroup(<>, "C(Example):Test.kt")
+              %composer.startReplaceableGroup(<>)
+              sourceInformation(%composer, "C(Example):Test.kt")
               when {
                 x < 0 -> {
-                  %composer.startReplaceableGroup(<>, "<A(a)>")
+                  %composer.startReplaceableGroup(<>)
+                  sourceInformation(%composer, "<A(a)>")
                   A(a, %composer, 0)
                   %composer.endReplaceableGroup()
                 }
@@ -379,7 +404,8 @@
                   NA()
                 }
                 else -> {
-                  %composer.startReplaceableGroup(<>, "<A(b)>")
+                  %composer.startReplaceableGroup(<>)
+                  sourceInformation(%composer, "<A(b)>")
                   A(b, %composer, 0)
                   %composer.endReplaceableGroup()
                 }
@@ -409,15 +435,18 @@
             @NonRestartableComposable
             @Composable
             fun Example(x: Int, %composer: Composer?, %changed: Int) {
-              %composer.startReplaceableGroup(<>, "C(Example):Test.kt")
+              %composer.startReplaceableGroup(<>)
+              sourceInformation(%composer, "C(Example):Test.kt")
               when {
-                %composer.startReplaceableGroup(<>, "<R(a)>")
+                %composer.startReplaceableGroup(<>)
+                sourceInformation(%composer, "<R(a)>")
                 val tmp0_group = x == R(a, %composer, 0)
                 %composer.endReplaceableGroup()
                 tmp0_group -> {
                   NA()
                 }
-                %composer.startReplaceableGroup(<>, "<R(b)>")
+                %composer.startReplaceableGroup(<>)
+                sourceInformation(%composer, "<R(b)>")
                 val tmp1_group = x > R(b, %composer, 0)
                 %composer.endReplaceableGroup()
                 tmp1_group -> {
@@ -452,16 +481,20 @@
             @NonRestartableComposable
             @Composable
             fun Example(x: Int, %composer: Composer?, %changed: Int) {
-              %composer.startReplaceableGroup(<>, "C(Example)<A()>:Test.kt")
-              %composer.startReplaceableGroup(<>, "")
+              %composer.startReplaceableGroup(<>)
+              sourceInformation(%composer, "C(Example)<A()>:Test.kt")
+              %composer.startReplaceableGroup(<>)
+              sourceInformation(%composer, "")
               when {
-                %composer.startReplaceableGroup(<>, "<R(a)>")
+                %composer.startReplaceableGroup(<>)
+                sourceInformation(%composer, "<R(a)>")
                 val tmp0_group = x == R(a, %composer, 0)
                 %composer.endReplaceableGroup()
                 tmp0_group -> {
                   NA()
                 }
-                %composer.startReplaceableGroup(<>, "<R(b)>")
+                %composer.startReplaceableGroup(<>)
+                sourceInformation(%composer, "<R(b)>")
                 val tmp1_group = x > R(b, %composer, 0)
                 %composer.endReplaceableGroup()
                 tmp1_group -> {
@@ -492,7 +525,8 @@
             @NonRestartableComposable
             @Composable
             fun Example(x: Int?, %composer: Composer?, %changed: Int) {
-              %composer.startReplaceableGroup(<>, "C(Example):Test.kt")
+              %composer.startReplaceableGroup(<>)
+              sourceInformation(%composer, "C(Example):Test.kt")
               val tmp0_safe_receiver = x
               when {
                 tmp0_safe_receiver == null -> {
@@ -501,7 +535,8 @@
                   null
                 }
                 else -> {
-                  %composer.startReplaceableGroup(<>, "<A()>")
+                  %composer.startReplaceableGroup(<>)
+                  sourceInformation(%composer, "<A()>")
                   tmp0_safe_receiver.A(%composer, 0b1110 and %changed)
                   %composer.endReplaceableGroup()
                 }
@@ -525,11 +560,13 @@
             @NonRestartableComposable
             @Composable
             fun Example(x: Int?, %composer: Composer?, %changed: Int) {
-              %composer.startReplaceableGroup(<>, "C(Example):Test.kt")
+              %composer.startReplaceableGroup(<>)
+              sourceInformation(%composer, "C(Example):Test.kt")
               val y = val tmp0_elvis_lhs = x
               when {
                 tmp0_elvis_lhs == null -> {
-                  %composer.startReplaceableGroup(<>, "<R()>")
+                  %composer.startReplaceableGroup(<>)
+                  sourceInformation(%composer, "<R()>")
                   val tmp0_group = R(%composer, 0)
                   %composer.endReplaceableGroup()
                   tmp0_group
@@ -562,7 +599,8 @@
             @NonRestartableComposable
             @Composable
             fun Example(items: List<Int>, %composer: Composer?, %changed: Int) {
-              %composer.startReplaceableGroup(<>, "C(Example)*<P(i)>:Test.kt")
+              %composer.startReplaceableGroup(<>)
+              sourceInformation(%composer, "C(Example)*<P(i)>:Test.kt")
               val tmp0_iterator = items.iterator()
               while (tmp0_iterator.hasNext()) {
                 val i = tmp0_iterator.next()
@@ -590,8 +628,10 @@
             @NonRestartableComposable
             @Composable
             fun Example(items: List<Int>, %composer: Composer?, %changed: Int) {
-              %composer.startReplaceableGroup(<>, "C(Example)<A()>:Test.kt")
-              %composer.startReplaceableGroup(<>, "*<P(i)>")
+              %composer.startReplaceableGroup(<>)
+              sourceInformation(%composer, "C(Example)<A()>:Test.kt")
+              %composer.startReplaceableGroup(<>)
+              sourceInformation(%composer, "*<P(i)>")
               val tmp0_iterator = items.iterator()
               while (tmp0_iterator.hasNext()) {
                 val i = tmp0_iterator.next()
@@ -620,7 +660,8 @@
             @NonRestartableComposable
             @Composable
             fun Example(%composer: Composer?, %changed: Int) {
-              %composer.startReplaceableGroup(<>, "C(Example)<L()>:Test.kt")
+              %composer.startReplaceableGroup(<>)
+              sourceInformation(%composer, "C(Example)<L()>:Test.kt")
               val tmp0_iterator = L(%composer, 0).iterator()
               while (tmp0_iterator.hasNext()) {
                 val i = tmp0_iterator.next()
@@ -650,7 +691,8 @@
             @NonRestartableComposable
             @Composable
             fun Example(items: MutableList<Int>, %composer: Composer?, %changed: Int) {
-              %composer.startReplaceableGroup(<>, "C(Example)*<P(item...>:Test.kt")
+              %composer.startReplaceableGroup(<>)
+              sourceInformation(%composer, "C(Example)*<P(item...>:Test.kt")
               while (items.isNotEmpty()) {
                 val item = items.removeAt(items.size - 1)
                 P(item, %composer, 0)
@@ -679,8 +721,10 @@
             @NonRestartableComposable
             @Composable
             fun Example(items: MutableList<Int>, %composer: Composer?, %changed: Int) {
-              %composer.startReplaceableGroup(<>, "C(Example)<A()>:Test.kt")
-              %composer.startReplaceableGroup(<>, "*<P(item...>")
+              %composer.startReplaceableGroup(<>)
+              sourceInformation(%composer, "C(Example)<A()>:Test.kt")
+              %composer.startReplaceableGroup(<>)
+              sourceInformation(%composer, "*<P(item...>")
               while (items.isNotEmpty()) {
                 val item = items.removeAt(items.size - 1)
                 P(item, %composer, 0)
@@ -709,7 +753,8 @@
             @NonRestartableComposable
             @Composable
             fun Example(%composer: Composer?, %changed: Int) {
-              %composer.startReplaceableGroup(<>, "C(Example)*<B()>:Test.kt")
+              %composer.startReplaceableGroup(<>)
+              sourceInformation(%composer, "C(Example)*<B()>:Test.kt")
               while (B(%composer, 0)) {
                 print("hello world")
               }
@@ -735,8 +780,10 @@
             @NonRestartableComposable
             @Composable
             fun Example(%composer: Composer?, %changed: Int) {
-              %composer.startReplaceableGroup(<>, "C(Example)<A()>:Test.kt")
-              %composer.startReplaceableGroup(<>, "*<B()>")
+              %composer.startReplaceableGroup(<>)
+              sourceInformation(%composer, "C(Example)<A()>:Test.kt")
+              %composer.startReplaceableGroup(<>)
+              sourceInformation(%composer, "*<B()>")
               while (B(%composer, 0)) {
                 print("hello world")
               }
@@ -764,7 +811,8 @@
             @NonRestartableComposable
             @Composable
             fun Example(%composer: Composer?, %changed: Int) {
-              %composer.startReplaceableGroup(<>, "C(Example)*<B()>,<A()>:Test.kt")
+              %composer.startReplaceableGroup(<>)
+              sourceInformation(%composer, "C(Example)*<B()>,<A()>:Test.kt")
               while (B(%composer, 0)) {
                 A(%composer, 0)
               }
@@ -791,8 +839,10 @@
             @NonRestartableComposable
             @Composable
             fun Example(%composer: Composer?, %changed: Int) {
-              %composer.startReplaceableGroup(<>, "C(Example)<A(b)>:Test.kt")
-              %composer.startReplaceableGroup(<>, "*<B()>,<A(a)>")
+              %composer.startReplaceableGroup(<>)
+              sourceInformation(%composer, "C(Example)<A(b)>:Test.kt")
+              %composer.startReplaceableGroup(<>)
+              sourceInformation(%composer, "*<B()>,<A(a)>")
               while (B(%composer, 0)) {
                 A(a, %composer, 0)
               }
@@ -820,9 +870,11 @@
             @NonRestartableComposable
             @Composable
             fun Example(x: Int, %composer: Composer?, %changed: Int) {
-              %composer.startReplaceableGroup(<>, "C(Example):Test.kt")
+              %composer.startReplaceableGroup(<>)
+              sourceInformation(%composer, "C(Example):Test.kt")
               if (x > 0) {
-                %composer.startReplaceableGroup(<>, "<A()>")
+                %composer.startReplaceableGroup(<>)
+                sourceInformation(%composer, "<A()>")
                 A(%composer, 0)
                 %composer.endReplaceableGroup()
                 %composer.endReplaceableGroup()
@@ -853,7 +905,8 @@
             @NonRestartableComposable
             @Composable
             fun Example(x: Int, %composer: Composer?, %changed: Int) {
-              %composer.startReplaceableGroup(<>, "C(Example)<A()>:Test.kt")
+              %composer.startReplaceableGroup(<>)
+              sourceInformation(%composer, "C(Example)<A()>:Test.kt")
               if (x > 0) {
                 %composer.endReplaceableGroup()
                 return
@@ -880,9 +933,11 @@
             @NonRestartableComposable
             @Composable
             fun Example(x: Int, %composer: Composer?, %changed: Int): Int {
-              %composer.startReplaceableGroup(<>, "C(Example):Test.kt")
+              %composer.startReplaceableGroup(<>)
+              sourceInformation(%composer, "C(Example):Test.kt")
               if (x > 0) {
-                %composer.startReplaceableGroup(<>, "<A()>")
+                %composer.startReplaceableGroup(<>)
+                sourceInformation(%composer, "<A()>")
                 A(%composer, 0)
                 val tmp1_return = 1
                 %composer.endReplaceableGroup()
@@ -915,7 +970,8 @@
             @NonRestartableComposable
             @Composable
             fun Example(x: Int, %composer: Composer?, %changed: Int): Int {
-              %composer.startReplaceableGroup(<>, "C(Example)<A()>:Test.kt")
+              %composer.startReplaceableGroup(<>)
+              sourceInformation(%composer, "C(Example)<A()>:Test.kt")
               if (x > 0) {
                 val tmp1_return = 1
                 %composer.endReplaceableGroup()
@@ -944,7 +1000,8 @@
             @NonRestartableComposable
             @Composable
             fun Example(%composer: Composer?, %changed: Int): Int {
-              %composer.startReplaceableGroup(<>, "C(Example)<A()>,<R()>:Test.kt")
+              %composer.startReplaceableGroup(<>)
+              sourceInformation(%composer, "C(Example)<A()>,<R()>:Test.kt")
               A(%composer, 0)
               val tmp0 = R(%composer, 0)
               %composer.endReplaceableGroup()
@@ -968,9 +1025,11 @@
             @NonRestartableComposable
             @Composable
             fun Example(x: Int, %composer: Composer?, %changed: Int): Int {
-              %composer.startReplaceableGroup(<>, "C(Example)<R()>:Test.kt")
+              %composer.startReplaceableGroup(<>)
+              sourceInformation(%composer, "C(Example)<R()>:Test.kt")
               if (x > 0) {
-                %composer.startReplaceableGroup(<>, "<R()>")
+                %composer.startReplaceableGroup(<>)
+                sourceInformation(%composer, "<R()>")
                 val tmp1_return = R(%composer, 0)
                 %composer.endReplaceableGroup()
                 %composer.endReplaceableGroup()
@@ -1011,7 +1070,8 @@
             @NonRestartableComposable
             @Composable
             fun Example(items: Iterator<Int>, %composer: Composer?, %changed: Int) {
-              %composer.startReplaceableGroup(<>, "C(Example)*<P(i)>,<P(l)>:Test.kt")
+              %composer.startReplaceableGroup(<>)
+              sourceInformation(%composer, "C(Example)*<P(i)>,<P(l)>:Test.kt")
               while (items.hasNext()) {
                 val i = items.next()
                 val j = i
@@ -1019,13 +1079,15 @@
                 val l = i
                 P(i, %composer, 0)
                 if (i == 0) {
-                  %composer.startReplaceableGroup(<>, "<P(j)>")
+                  %composer.startReplaceableGroup(<>)
+                  sourceInformation(%composer, "<P(j)>")
                   P(j, %composer, 0)
                   %composer.endReplaceableGroup()
                   %composer.endReplaceableGroup()
                   return
                 } else {
-                  %composer.startReplaceableGroup(<>, "<P(k)>")
+                  %composer.startReplaceableGroup(<>)
+                  sourceInformation(%composer, "<P(k)>")
                   P(k, %composer, 0)
                   %composer.endReplaceableGroup()
                 }
@@ -1060,7 +1122,8 @@
         """
             @Composable
             fun Example(items: Iterator<Int>, %composer: Composer?, %changed: Int) {
-              %composer = %composer.startRestartGroup(<>, "C(Example)*<P(i)>,<P(l)>:Test.kt")
+              %composer = %composer.startRestartGroup(<>)
+              sourceInformation(%composer, "C(Example)*<P(i)>,<P(l)>:Test.kt")
               while (items.hasNext()) {
                 val i = items.next()
                 val j = i
@@ -1068,7 +1131,8 @@
                 val l = i
                 P(i, %composer, 0)
                 if (i == 0) {
-                  %composer.startReplaceableGroup(<>, "<P(j)>")
+                  %composer.startReplaceableGroup(<>)
+                  sourceInformation(%composer, "<P(j)>")
                   P(j, %composer, 0)
                   %composer.endReplaceableGroup()
                   %composer.endRestartGroup()?.updateScope { %composer: Composer?, %force: Int ->
@@ -1076,7 +1140,8 @@
                   }
                   return
                 } else {
-                  %composer.startReplaceableGroup(<>, "<P(k)>")
+                  %composer.startReplaceableGroup(<>)
+                  sourceInformation(%composer, "<P(k)>")
                   P(k, %composer, 0)
                   %composer.endReplaceableGroup()
                 }
@@ -1107,7 +1172,8 @@
             @NonRestartableComposable
             @Composable
             fun Example(items: Iterator<Int>, %composer: Composer?, %changed: Int) {
-              %composer.startReplaceableGroup(<>, "C(Example)*<P(i)>:Test.kt")
+              %composer.startReplaceableGroup(<>)
+              sourceInformation(%composer, "C(Example)*<P(i)>:Test.kt")
               while (items.hasNext()) {
                 val i = items.next()
                 if (i == 0) {
@@ -1138,7 +1204,8 @@
             @NonRestartableComposable
             @Composable
             fun Example(items: Iterator<Int>, %composer: Composer?, %changed: Int) {
-              %composer.startReplaceableGroup(<>, "C(Example)*<P(i)>:Test.kt")
+              %composer.startReplaceableGroup(<>)
+              sourceInformation(%composer, "C(Example)*<P(i)>:Test.kt")
               while (items.hasNext()) {
                 val i = items.next()
                 P(i, %composer, 0)
@@ -1172,7 +1239,8 @@
             @NonRestartableComposable
             @Composable
             fun Example(items: Iterator<Int>, %composer: Composer?, %changed: Int) {
-              %composer.startReplaceableGroup(<>, "C(Example)*<P(i)>,<P(j)>:Test.kt")
+              %composer.startReplaceableGroup(<>)
+              sourceInformation(%composer, "C(Example)*<P(i)>,<P(j)>:Test.kt")
               while (items.hasNext()) {
                 val i = items.next()
                 val j = i
@@ -1208,8 +1276,10 @@
             @NonRestartableComposable
             @Composable
             fun Example(items: Iterator<Int>, %composer: Composer?, %changed: Int) {
-              %composer.startReplaceableGroup(<>, "C(Example)<A()>:Test.kt")
-              %composer.startReplaceableGroup(<>, "*<P(i)>,<P(i)>")
+              %composer.startReplaceableGroup(<>)
+              sourceInformation(%composer, "C(Example)<A()>:Test.kt")
+              %composer.startReplaceableGroup(<>)
+              sourceInformation(%composer, "*<P(i)>,<P(i)>")
               while (items.hasNext()) {
                 val i = items.next()
                 P(i, %composer, 0)
@@ -1243,9 +1313,11 @@
             @NonRestartableComposable
             @Composable
             fun Example(items: Iterator<Int>, %composer: Composer?, %changed: Int) {
-              %composer.startReplaceableGroup(<>, "C(Example):Test.kt")
+              %composer.startReplaceableGroup(<>)
+              sourceInformation(%composer, "C(Example):Test.kt")
               while (items.hasNext()) {
-                %composer.startReplaceableGroup(<>, "<P(i)>")
+                %composer.startReplaceableGroup(<>)
+                sourceInformation(%composer, "<P(i)>")
                 val i = items.next()
                 if (i == 0) {
                   %composer.endReplaceableGroup()
@@ -1278,9 +1350,11 @@
             @NonRestartableComposable
             @Composable
             fun Example(items: Iterator<Int>, %composer: Composer?, %changed: Int) {
-              %composer.startReplaceableGroup(<>, "C(Example):Test.kt")
+              %composer.startReplaceableGroup(<>)
+              sourceInformation(%composer, "C(Example):Test.kt")
               while (items.hasNext()) {
-                %composer.startReplaceableGroup(<>, "<P(i)>")
+                %composer.startReplaceableGroup(<>)
+                sourceInformation(%composer, "<P(i)>")
                 val i = items.next()
                 P(i, %composer, 0)
                 if (i == 0) {
@@ -1314,9 +1388,11 @@
             @NonRestartableComposable
             @Composable
             fun Example(items: Iterator<Int>, %composer: Composer?, %changed: Int) {
-              %composer.startReplaceableGroup(<>, "C(Example):Test.kt")
+              %composer.startReplaceableGroup(<>)
+              sourceInformation(%composer, "C(Example):Test.kt")
               while (items.hasNext()) {
-                %composer.startReplaceableGroup(<>, "<P(i)>,<P(i)>")
+                %composer.startReplaceableGroup(<>)
+                sourceInformation(%composer, "<P(i)>,<P(i)>")
                 val i = items.next()
                 P(i, %composer, 0)
                 if (i == 0) {
@@ -1349,7 +1425,8 @@
             @NonRestartableComposable
             @Composable
             fun Example(a: Iterator<Int>, b: Iterator<Int>, %composer: Composer?, %changed: Int) {
-              %composer.startReplaceableGroup(<>, "C(Example)*<A()>:Test.kt")
+              %composer.startReplaceableGroup(<>)
+              sourceInformation(%composer, "C(Example)*<A()>:Test.kt")
               while (a.hasNext()) {
                 val x = a.next()
                 if (x > 100) {
@@ -1385,10 +1462,12 @@
             @NonRestartableComposable
             @Composable
             fun Example(a: Iterator<Int>, b: Iterator<Int>, %composer: Composer?, %changed: Int) {
-              %composer.startReplaceableGroup(<>, "C(Example)*<A()>:Test.kt")
+              %composer.startReplaceableGroup(<>)
+              sourceInformation(%composer, "C(Example)*<A()>:Test.kt")
               a@while (a.hasNext()) {
                 val x = a.next()
-                %composer.startReplaceableGroup(<>, "*<A()>")
+                %composer.startReplaceableGroup(<>)
+                sourceInformation(%composer, "*<A()>")
                 b@while (b.hasNext()) {
                   val y = b.next()
                   if (y == x) {
@@ -1436,13 +1515,15 @@
             @NonRestartableComposable
             @Composable
             fun Example(a: Iterator<Int>, b: Iterator<Int>, %composer: Composer?, %changed: Int) {
-              %composer.startReplaceableGroup(<>, "C(Example)*<A()>:Test.kt")
+              %composer.startReplaceableGroup(<>)
+              sourceInformation(%composer, "C(Example)*<A()>:Test.kt")
               a@while (a.hasNext()) {
                 val x = a.next()
                 if (x == 0) {
                   break
                 }
-                %composer.startReplaceableGroup(<>, "*<A()>")
+                %composer.startReplaceableGroup(<>)
+                sourceInformation(%composer, "*<A()>")
                 b@while (b.hasNext()) {
                   val y = b.next()
                   if (y == 0) {
@@ -1485,10 +1566,13 @@
             @NonRestartableComposable
             @Composable
             fun Example(a: Iterator<Int>, b: Iterator<Int>, %composer: Composer?, %changed: Int) {
-              %composer.startReplaceableGroup(<>, "C(Example)<A()>:Test.kt")
-              %composer.startReplaceableGroup(<>, "*<A()>")
+              %composer.startReplaceableGroup(<>)
+              sourceInformation(%composer, "C(Example)<A()>:Test.kt")
+              %composer.startReplaceableGroup(<>)
+              sourceInformation(%composer, "*<A()>")
               a@while (a.hasNext()) {
-                %composer.startReplaceableGroup(<>, "*<A()>")
+                %composer.startReplaceableGroup(<>)
+                sourceInformation(%composer, "*<A()>")
                 b@while (b.hasNext()) {
                   A(%composer, 0)
                 }
@@ -1519,10 +1603,13 @@
             @NonRestartableComposable
             @Composable
             fun Example(x: Int, %composer: Composer?, %changed: Int) {
-              %composer.startReplaceableGroup(<>, "C(Example):Test.kt")
+              %composer.startReplaceableGroup(<>)
+              sourceInformation(%composer, "C(Example):Test.kt")
               if (x > 0) {
-                %composer.startReplaceableGroup(<>, "<A()>")
-                %composer.startReplaceableGroup(<>, "*<A()>")
+                %composer.startReplaceableGroup(<>)
+                sourceInformation(%composer, "<A()>")
+                %composer.startReplaceableGroup(<>)
+                sourceInformation(%composer, "*<A()>")
                 while (x > 0) {
                   A(%composer, 0)
                 }
@@ -1555,9 +1642,11 @@
             @NonRestartableComposable
             @Composable
             fun Example(x: Int, %composer: Composer?, %changed: Int) {
-              %composer.startReplaceableGroup(<>, "C(Example):Test.kt")
+              %composer.startReplaceableGroup(<>)
+              sourceInformation(%composer, "C(Example):Test.kt")
               if (x > 0) {
-                %composer.startReplaceableGroup(<>, "<A()>,*<A()>")
+                %composer.startReplaceableGroup(<>)
+                sourceInformation(%composer, "<A()>,*<A()>")
                 A(%composer, 0)
                 while (x > 0) {
                   A(%composer, 0)
@@ -1588,9 +1677,11 @@
             @NonRestartableComposable
             @Composable
             fun Example(x: Int, %composer: Composer?, %changed: Int) {
-              %composer.startReplaceableGroup(<>, "C(Example):Test.kt")
+              %composer.startReplaceableGroup(<>)
+              sourceInformation(%composer, "C(Example):Test.kt")
               if (x > 0) {
-                %composer.startReplaceableGroup(<>, "*<A()>")
+                %composer.startReplaceableGroup(<>)
+                sourceInformation(%composer, "*<A()>")
                 while (x > 0) {
                   A(%composer, 0)
                 }
@@ -1620,9 +1711,11 @@
             @NonRestartableComposable
             @Composable
             fun Example(x: Int, %composer: Composer?, %changed: Int) {
-              %composer.startReplaceableGroup(<>, "C(Example):Test.kt")
+              %composer.startReplaceableGroup(<>)
+              sourceInformation(%composer, "C(Example):Test.kt")
               while (x > 0) {
-                %composer.startMovableGroup(<>, x, "<A()>")
+                %composer.startMovableGroup(<>, x)
+                sourceInformation(%composer, "<A()>")
                 A(%composer, 0)
                 %composer.endMovableGroup()
               }
@@ -1650,12 +1743,15 @@
             @NonRestartableComposable
             @Composable
             fun Example(x: Int, %composer: Composer?, %changed: Int) {
-              %composer.startReplaceableGroup(<>, "C(Example):Test.kt")
+              %composer.startReplaceableGroup(<>)
+              sourceInformation(%composer, "C(Example):Test.kt")
               while (x > 0) {
-                %composer.startMovableGroup(<>, x, "<A(a)>")
+                %composer.startMovableGroup(<>, x)
+                sourceInformation(%composer, "<A(a)>")
                 A(a, %composer, 0)
                 %composer.endMovableGroup()
-                %composer.startMovableGroup(<>, x + 1, "<A(b)>")
+                %composer.startMovableGroup(<>, x + 1)
+                sourceInformation(%composer, "<A(b)>")
                 A(b, %composer, 0)
                 %composer.endMovableGroup()
               }
@@ -1681,9 +1777,11 @@
             @NonRestartableComposable
             @Composable
             fun Example(x: Int, %composer: Composer?, %changed: Int) {
-              %composer.startReplaceableGroup(<>, "C(Example)*<A(b)>:Test.kt")
+              %composer.startReplaceableGroup(<>)
+              sourceInformation(%composer, "C(Example)*<A(b)>:Test.kt")
               while (x > 0) {
-                %composer.startMovableGroup(<>, x, "<A(a)>")
+                %composer.startMovableGroup(<>, x)
+                sourceInformation(%composer, "<A(a)>")
                 A(a, %composer, 0)
                 %composer.endMovableGroup()
                 A(b, %composer, 0)
@@ -1710,10 +1808,12 @@
             @NonRestartableComposable
             @Composable
             fun Example(x: Int, %composer: Composer?, %changed: Int) {
-              %composer.startReplaceableGroup(<>, "C(Example)*<A(a)>:Test.kt")
+              %composer.startReplaceableGroup(<>)
+              sourceInformation(%composer, "C(Example)*<A(a)>:Test.kt")
               while (x > 0) {
                 A(a, %composer, 0)
-                %composer.startMovableGroup(<>, x, "<A(b)>")
+                %composer.startMovableGroup(<>, x)
+                sourceInformation(%composer, "<A(b)>")
                 A(b, %composer, 0)
                 %composer.endMovableGroup()
               }
@@ -1740,10 +1840,12 @@
             @NonRestartableComposable
             @Composable
             fun Example(x: Int, %composer: Composer?, %changed: Int) {
-              %composer.startReplaceableGroup(<>, "C(Example)*<A(a)>,<A(c)>:Test.kt")
+              %composer.startReplaceableGroup(<>)
+              sourceInformation(%composer, "C(Example)*<A(a)>,<A(c)>:Test.kt")
               while (x > 0) {
                 A(a, %composer, 0)
-                %composer.startMovableGroup(<>, x, "<A(b)>")
+                %composer.startMovableGroup(<>, x)
+                sourceInformation(%composer, "<A(b)>")
                 A(b, %composer, 0)
                 %composer.endMovableGroup()
                 A(c, %composer, 0)
@@ -1767,8 +1869,10 @@
             @NonRestartableComposable
             @Composable
             fun Example(x: Int, %composer: Composer?, %changed: Int) {
-              %composer.startReplaceableGroup(<>, "C(Example):Test.kt")
-              %composer.startMovableGroup(<>, x, "<A()>")
+              %composer.startReplaceableGroup(<>)
+              sourceInformation(%composer, "C(Example):Test.kt")
+              %composer.startMovableGroup(<>, x)
+              sourceInformation(%composer, "<A()>")
               A(%composer, 0)
               %composer.endMovableGroup()
               %composer.endReplaceableGroup()
@@ -1791,8 +1895,10 @@
             @NonRestartableComposable
             @Composable
             fun Example(x: Int, %composer: Composer?, %changed: Int) {
-              %composer.startReplaceableGroup(<>, "C(Example)<A(b)>:Test.kt")
-              %composer.startMovableGroup(<>, x, "<A(a)>")
+              %composer.startReplaceableGroup(<>)
+              sourceInformation(%composer, "C(Example)<A(b)>:Test.kt")
+              %composer.startMovableGroup(<>, x)
+              sourceInformation(%composer, "<A(a)>")
               A(a, %composer, 0)
               %composer.endMovableGroup()
               A(b, %composer, 0)
@@ -1816,9 +1922,11 @@
             @NonRestartableComposable
             @Composable
             fun Example(x: Int, %composer: Composer?, %changed: Int) {
-              %composer.startReplaceableGroup(<>, "C(Example)<A(a)>:Test.kt")
+              %composer.startReplaceableGroup(<>)
+              sourceInformation(%composer, "C(Example)<A(a)>:Test.kt")
               A(a, %composer, 0)
-              %composer.startMovableGroup(<>, x, "<A(b)>")
+              %composer.startMovableGroup(<>, x)
+              sourceInformation(%composer, "<A(b)>")
               A(b, %composer, 0)
               %composer.endMovableGroup()
               %composer.endReplaceableGroup()
@@ -1842,10 +1950,13 @@
             @NonRestartableComposable
             @Composable
             fun Example(x: Int, %composer: Composer?, %changed: Int) {
-              %composer.startReplaceableGroup(<>, "C(Example):Test.kt")
+              %composer.startReplaceableGroup(<>)
+              sourceInformation(%composer, "C(Example):Test.kt")
               if (x > 0) {
-                %composer.startReplaceableGroup(<>, "")
-                %composer.startMovableGroup(<>, x, "<A()>")
+                %composer.startReplaceableGroup(<>)
+                sourceInformation(%composer, "")
+                %composer.startMovableGroup(<>, x)
+                sourceInformation(%composer, "<A()>")
                 A(%composer, 0)
                 %composer.endMovableGroup()
                 %composer.endReplaceableGroup()
@@ -1875,10 +1986,13 @@
             @NonRestartableComposable
             @Composable
             fun Example(x: Int, %composer: Composer?, %changed: Int) {
-              %composer.startReplaceableGroup(<>, "C(Example):Test.kt")
+              %composer.startReplaceableGroup(<>)
+              sourceInformation(%composer, "C(Example):Test.kt")
               if (x > 0) {
-                %composer.startReplaceableGroup(<>, "<A(b)>")
-                %composer.startMovableGroup(<>, x, "<A(a)>")
+                %composer.startReplaceableGroup(<>)
+                sourceInformation(%composer, "<A(b)>")
+                %composer.startMovableGroup(<>, x)
+                sourceInformation(%composer, "<A(a)>")
                 A(a, %composer, 0)
                 %composer.endMovableGroup()
                 A(b, %composer, 0)
@@ -1909,11 +2023,14 @@
             @NonRestartableComposable
             @Composable
             fun Example(x: Int, %composer: Composer?, %changed: Int) {
-              %composer.startReplaceableGroup(<>, "C(Example):Test.kt")
+              %composer.startReplaceableGroup(<>)
+              sourceInformation(%composer, "C(Example):Test.kt")
               if (x > 0) {
-                %composer.startReplaceableGroup(<>, "<A(a)>")
+                %composer.startReplaceableGroup(<>)
+                sourceInformation(%composer, "<A(a)>")
                 A(a, %composer, 0)
-                %composer.startMovableGroup(<>, x, "<A(b)>")
+                %composer.startMovableGroup(<>, x)
+                sourceInformation(%composer, "<A(b)>")
                 A(b, %composer, 0)
                 %composer.endMovableGroup()
                 %composer.endReplaceableGroup()
@@ -1940,8 +2057,10 @@
             @NonRestartableComposable
             @Composable
             fun Example(a: Int, b: Int, c: Int, d: Int, %composer: Composer?, %changed: Int) {
-              %composer.startReplaceableGroup(<>, "C(Example):Test.kt")
-              %composer.startMovableGroup(<>, %composer.joinKey(%composer.joinKey(%composer.joinKey(a, b), c), d), "<A()>")
+              %composer.startReplaceableGroup(<>)
+              sourceInformation(%composer, "C(Example):Test.kt")
+              %composer.startMovableGroup(<>, %composer.joinKey(%composer.joinKey(%composer.joinKey(a, b), c), d))
+              sourceInformation(%composer, "<A()>")
               A(%composer, 0)
               %composer.endMovableGroup()
               %composer.endReplaceableGroup()
@@ -1965,9 +2084,11 @@
             @NonRestartableComposable
             @Composable
             fun Example(x: Int, %composer: Composer?, %changed: Int) {
-              %composer.startReplaceableGroup(<>, "C(Example)*<R()>:Test.kt")
+              %composer.startReplaceableGroup(<>)
+              sourceInformation(%composer, "C(Example)*<R()>:Test.kt")
               while (x > 0) {
-                %composer.startMovableGroup(<>, R(%composer, 0), "<A()>")
+                %composer.startMovableGroup(<>, R(%composer, 0))
+                sourceInformation(%composer, "<A()>")
                 A(%composer, 0)
                 %composer.endMovableGroup()
               }
@@ -1989,9 +2110,11 @@
             @NonRestartableComposable
             @Composable
             fun Example(x: Int, %composer: Composer?, %changed: Int) {
-              %composer.startReplaceableGroup(<>, "C(Example)<P(y)>:Test.kt")
+              %composer.startReplaceableGroup(<>)
+              sourceInformation(%composer, "C(Example)<P(y)>:Test.kt")
               val y =
-              %composer.startMovableGroup(<>, x, "<R()>")
+              %composer.startMovableGroup(<>, x)
+              sourceInformation(%composer, "<R()>")
               val tmp0 = R(%composer, 0)
               %composer.endMovableGroup()
               tmp0
@@ -2017,14 +2140,18 @@
             @NonRestartableComposable
             @Composable
             fun Example(x: Int, %composer: Composer?, %changed: Int): Int {
-              %composer.startReplaceableGroup(<>, "C(Example):Test.kt")
+              %composer.startReplaceableGroup(<>)
+              sourceInformation(%composer, "C(Example):Test.kt")
               val tmp0 = if (x > 0) {
-                %composer.startReplaceableGroup(<>, "")
+                %composer.startReplaceableGroup(<>)
+                sourceInformation(%composer, "")
                 val tmp4_group =
-                val tmp3_group = if (%composer.startReplaceableGroup(<>, "<B()>")
+                val tmp3_group = if (%composer.startReplaceableGroup(<>)
+                sourceInformation(%composer, "<B()>")
                 val tmp1_group = B(%composer, 0)
                 %composer.endReplaceableGroup()
-                tmp1_group) 1 else if (%composer.startReplaceableGroup(<>, "<B()>")
+                tmp1_group) 1 else if (%composer.startReplaceableGroup(<>)
+                sourceInformation(%composer, "<B()>")
                 val tmp2_group = B(%composer, 0)
                 %composer.endReplaceableGroup()
                 tmp2_group) 2 else 3
@@ -2091,8 +2218,10 @@
             @NonRestartableComposable
             @Composable
             fun Simple(%composer: Composer?, %changed: Int) {
-              %composer.startReplaceableGroup(<>, "C(Simple)<A()>:Test.kt")
-              %composer.startReplaceableGroup(<>, "*<A()>")
+              %composer.startReplaceableGroup(<>)
+              sourceInformation(%composer, "C(Simple)<A()>:Test.kt")
+              %composer.startReplaceableGroup(<>)
+              sourceInformation(%composer, "*<A()>")
               run {
                 A(%composer, 0)
               }
@@ -2103,8 +2232,10 @@
             @NonRestartableComposable
             @Composable
             fun WithReturn(%composer: Composer?, %changed: Int) {
-              %composer.startReplaceableGroup(<>, "C(WithReturn)<A()>:Test.kt")
-              %composer.startReplaceableGroup(<>, "*<A()>")
+              %composer.startReplaceableGroup(<>)
+              sourceInformation(%composer, "C(WithReturn)<A()>:Test.kt")
+              %composer.startReplaceableGroup(<>)
+              sourceInformation(%composer, "*<A()>")
               run {
                 A(%composer, 0)
                 %composer.endReplaceableGroup()
@@ -2118,7 +2249,8 @@
             @NonRestartableComposable
             @Composable
             fun NoCalls(%composer: Composer?, %changed: Int) {
-              %composer.startReplaceableGroup(<>, "C(NoCalls)<A()>:Test.kt")
+              %composer.startReplaceableGroup(<>)
+              sourceInformation(%composer, "C(NoCalls)<A()>:Test.kt")
               run {
                 println("hello world")
               }
@@ -2128,7 +2260,8 @@
             @NonRestartableComposable
             @Composable
             fun NoCallsAfter(%composer: Composer?, %changed: Int) {
-              %composer.startReplaceableGroup(<>, "C(NoCallsAfter)*<A()>:Test.kt")
+              %composer.startReplaceableGroup(<>)
+              sourceInformation(%composer, "C(NoCallsAfter)*<A()>:Test.kt")
               run {
                 A(%composer, 0)
               }
@@ -2154,7 +2287,8 @@
         """
             @Composable
             fun Example(x: Int?, %composer: Composer?, %changed: Int) {
-              %composer = %composer.startRestartGroup(<>, "C(Example)<A(c)>:Test.kt")
+              %composer = %composer.startRestartGroup(<>)
+              sourceInformation(%composer, "C(Example)<A(c)>:Test.kt")
               val %dirty = %changed
               if (%changed and 0b1110 === 0) {
                 %dirty = %dirty or if (%composer.changed(x)) 0b0100 else 0b0010
@@ -2168,10 +2302,12 @@
                     null
                   }
                   else -> {
-                    %composer.startReplaceableGroup(<>, "*<A(b)>")
+                    %composer.startReplaceableGroup(<>)
+                    sourceInformation(%composer, "*<A(b)>")
                     tmp0_safe_receiver.let { it: Int ->
                       if (it > 0) {
-                        %composer.startReplaceableGroup(<>, "<A(a)>")
+                        %composer.startReplaceableGroup(<>)
+                        sourceInformation(%composer, "<A(a)>")
                         A(a, %composer, 0)
                         %composer.endReplaceableGroup()
                       } else {
@@ -2211,7 +2347,8 @@
         """
             @Composable
             fun Example(x: Int?, %composer: Composer?, %changed: Int) {
-              %composer = %composer.startRestartGroup(<>, "C(Example)<A()>:Test.kt")
+              %composer = %composer.startRestartGroup(<>)
+              sourceInformation(%composer, "C(Example)<A()>:Test.kt")
               val %dirty = %changed
               if (%changed and 0b1110 === 0) {
                 %dirty = %dirty or if (%composer.changed(x)) 0b0100 else 0b0010
@@ -2249,7 +2386,8 @@
         """
             @Composable
             fun <T> provided(value: T, %composer: Composer?, %changed: Int): State<T> {
-              %composer.startReplaceableGroup(<>, "C(provided)*<rememb...>:Test.kt")
+              %composer.startReplaceableGroup(<>)
+              sourceInformation(%composer, "C(provided)*<rememb...>:Test.kt")
               val tmp0 = remember({
                 val tmp0_return = mutableStateOf(
                   value = value
@@ -2282,7 +2420,8 @@
         """
             @Composable
             fun Test(x: Int, %composer: Composer?, %changed: Int): Int {
-              %composer.startReplaceableGroup(<>, "C(Test)*<A()>:Test.kt")
+              %composer.startReplaceableGroup(<>)
+              sourceInformation(%composer, "C(Test)*<A()>:Test.kt")
               val tmp0 =
               val tmp1_group = x.let { it: Int ->
                 A(%composer, 0)
@@ -2309,7 +2448,8 @@
         """
             @Composable
             fun Test(%composer: Composer?, %changed: Int) {
-              %composer = %composer.startRestartGroup(<>, "C(Test)<W>:Test.kt")
+              %composer = %composer.startRestartGroup(<>)
+              sourceInformation(%composer, "C(Test)<W>:Test.kt")
               if (%changed !== 0 || !%composer.skipping) {
                 W(ComposableSingletons%TestKt.lambda-1, %composer, 0)
               } else {
@@ -2320,7 +2460,8 @@
               }
             }
             internal object ComposableSingletons%TestKt {
-              val lambda-1: Function2<Composer, Int, Unit> = composableLambdaInstance(<>, false, "C<A()>:Test.kt") { %composer: Composer?, %changed: Int ->
+              val lambda-1: Function2<Composer, Int, Unit> = composableLambdaInstance(<>, false) { %composer: Composer?, %changed: Int ->
+                sourceInformation(%composer, "C<A()>:Test.kt")
                 if (%changed and 0b1011 xor 0b0010 !== 0 || !%composer.skipping) {
                   A(%composer, 0)
                 } else {
@@ -2344,10 +2485,12 @@
         """
             @Composable
             fun Test(%composer: Composer?, %changed: Int) {
-              %composer = %composer.startRestartGroup(<>, "C(Test)<IW>:Test.kt")
+              %composer = %composer.startRestartGroup(<>)
+              sourceInformation(%composer, "C(Test)<IW>:Test.kt")
               if (%changed !== 0 || !%composer.skipping) {
                 IW({ %composer: Composer?, %changed: Int ->
-                  %composer.startReplaceableGroup(<>, "C<A()>:Test.kt")
+                  %composer.startReplaceableGroup(<>)
+                  sourceInformation(%composer, "C<A()>:Test.kt")
                   if (%changed and 0b1011 xor 0b0010 !== 0 || !%composer.skipping) {
                     A(%composer, 0)
                   } else {
@@ -2383,7 +2526,8 @@
         """
             @Composable
             fun Test(%composer: Composer?, %changed: Int) {
-              %composer = %composer.startRestartGroup(<>, "C(Test)<Wrap>:Test.kt")
+              %composer = %composer.startRestartGroup(<>)
+              sourceInformation(%composer, "C(Test)<Wrap>:Test.kt")
               if (%changed !== 0 || !%composer.skipping) {
                 Wrap(ComposableSingletons%TestKt.lambda-1, %composer, 0)
               } else {
@@ -2394,9 +2538,11 @@
               }
             }
             internal object ComposableSingletons%TestKt {
-              val lambda-1: Function2<Composer, Int, Unit> = composableLambdaInstance(<>, false, "C<effect>:Test.kt") { %composer: Composer?, %changed: Int ->
+              val lambda-1: Function2<Composer, Int, Unit> = composableLambdaInstance(<>, false) { %composer: Composer?, %changed: Int ->
+                sourceInformation(%composer, "C<effect>:Test.kt")
                 if (%changed and 0b1011 xor 0b0010 !== 0 || !%composer.skipping) {
-                  %composer.startReplaceableGroup(<>, "*<effect>")
+                  %composer.startReplaceableGroup(<>)
+                  sourceInformation(%composer, "*<effect>")
                   repeat(number) { it: Int ->
                     effects[it] = effect({
                       0
@@ -2437,7 +2583,8 @@
         """
             @Composable
             fun Test(value: InlineClass, %composer: Composer?, %changed: Int) {
-              %composer = %composer.startRestartGroup(<>, "C(Test)P(0:InlineClass)<A()>:Test.kt")
+              %composer = %composer.startRestartGroup(<>)
+              sourceInformation(%composer, "C(Test)P(0:InlineClass)<A()>:Test.kt")
               val %dirty = %changed
               if (%changed and 0b1110 === 0) {
                 %dirty = %dirty or if (%composer.changed(value.value)) 0b0100 else 0b0010
@@ -2606,7 +2753,8 @@
         """
             @Composable
             fun Test01(p0: Int, p1: Int, p2: Int, p3: Int, %composer: Composer?, %changed: Int) {
-              %composer = %composer.startRestartGroup(<>, "C(Test01):Test.kt")
+              %composer = %composer.startRestartGroup(<>)
+              sourceInformation(%composer, "C(Test01):Test.kt")
               val %dirty = %changed
               if (%changed and 0b1110 === 0) {
                 %dirty = %dirty or if (%composer.changed(p0)) 0b0100 else 0b0010
@@ -2634,7 +2782,8 @@
             }
             @Composable
             fun Test02(p0: Int, p1: Int, p3: Int, p2: Int, %composer: Composer?, %changed: Int) {
-              %composer = %composer.startRestartGroup(<>, "C(Test02)P(!2,3):Test.kt")
+              %composer = %composer.startRestartGroup(<>)
+              sourceInformation(%composer, "C(Test02)P(!2,3):Test.kt")
               val %dirty = %changed
               if (%changed and 0b1110 === 0) {
                 %dirty = %dirty or if (%composer.changed(p0)) 0b0100 else 0b0010
@@ -2662,7 +2811,8 @@
             }
             @Composable
             fun Test03(p0: Int, p2: Int, p1: Int, p3: Int, %composer: Composer?, %changed: Int) {
-              %composer = %composer.startRestartGroup(<>, "C(Test03)P(!1,2):Test.kt")
+              %composer = %composer.startRestartGroup(<>)
+              sourceInformation(%composer, "C(Test03)P(!1,2):Test.kt")
               val %dirty = %changed
               if (%changed and 0b1110 === 0) {
                 %dirty = %dirty or if (%composer.changed(p0)) 0b0100 else 0b0010
@@ -2690,7 +2840,8 @@
             }
             @Composable
             fun Test04(p0: Int, p2: Int, p3: Int, p1: Int, %composer: Composer?, %changed: Int) {
-              %composer = %composer.startRestartGroup(<>, "C(Test04)P(!1,2,3):Test.kt")
+              %composer = %composer.startRestartGroup(<>)
+              sourceInformation(%composer, "C(Test04)P(!1,2,3):Test.kt")
               val %dirty = %changed
               if (%changed and 0b1110 === 0) {
                 %dirty = %dirty or if (%composer.changed(p0)) 0b0100 else 0b0010
@@ -2718,7 +2869,8 @@
             }
             @Composable
             fun Test05(p0: Int, p3: Int, p1: Int, p2: Int, %composer: Composer?, %changed: Int) {
-              %composer = %composer.startRestartGroup(<>, "C(Test05)P(!1,3):Test.kt")
+              %composer = %composer.startRestartGroup(<>)
+              sourceInformation(%composer, "C(Test05)P(!1,3):Test.kt")
               val %dirty = %changed
               if (%changed and 0b1110 === 0) {
                 %dirty = %dirty or if (%composer.changed(p0)) 0b0100 else 0b0010
@@ -2746,7 +2898,8 @@
             }
             @Composable
             fun Test06(p0: Int, p3: Int, p2: Int, p1: Int, %composer: Composer?, %changed: Int) {
-              %composer = %composer.startRestartGroup(<>, "C(Test06)P(!1,3,2):Test.kt")
+              %composer = %composer.startRestartGroup(<>)
+              sourceInformation(%composer, "C(Test06)P(!1,3,2):Test.kt")
               val %dirty = %changed
               if (%changed and 0b1110 === 0) {
                 %dirty = %dirty or if (%composer.changed(p0)) 0b0100 else 0b0010
@@ -2774,7 +2927,8 @@
             }
             @Composable
             fun Test07(p1: Int, p0: Int, p2: Int, p3: Int, %composer: Composer?, %changed: Int) {
-              %composer = %composer.startRestartGroup(<>, "C(Test07)P(1):Test.kt")
+              %composer = %composer.startRestartGroup(<>)
+              sourceInformation(%composer, "C(Test07)P(1):Test.kt")
               val %dirty = %changed
               if (%changed and 0b1110 === 0) {
                 %dirty = %dirty or if (%composer.changed(p1)) 0b0100 else 0b0010
@@ -2802,7 +2956,8 @@
             }
             @Composable
             fun Test08(p1: Int, p0: Int, p3: Int, p2: Int, %composer: Composer?, %changed: Int) {
-              %composer = %composer.startRestartGroup(<>, "C(Test08)P(1!1,3):Test.kt")
+              %composer = %composer.startRestartGroup(<>)
+              sourceInformation(%composer, "C(Test08)P(1!1,3):Test.kt")
               val %dirty = %changed
               if (%changed and 0b1110 === 0) {
                 %dirty = %dirty or if (%composer.changed(p1)) 0b0100 else 0b0010
@@ -2830,7 +2985,8 @@
             }
             @Composable
             fun Test09(p1: Int, p2: Int, p0: Int, p3: Int, %composer: Composer?, %changed: Int) {
-              %composer = %composer.startRestartGroup(<>, "C(Test09)P(1,2):Test.kt")
+              %composer = %composer.startRestartGroup(<>)
+              sourceInformation(%composer, "C(Test09)P(1,2):Test.kt")
               val %dirty = %changed
               if (%changed and 0b1110 === 0) {
                 %dirty = %dirty or if (%composer.changed(p1)) 0b0100 else 0b0010
@@ -2858,7 +3014,8 @@
             }
             @Composable
             fun Test00(p1: Int, p2: Int, p3: Int, p0: Int, %composer: Composer?, %changed: Int) {
-              %composer = %composer.startRestartGroup(<>, "C(Test00)P(1,2,3):Test.kt")
+              %composer = %composer.startRestartGroup(<>)
+              sourceInformation(%composer, "C(Test00)P(1,2,3):Test.kt")
               val %dirty = %changed
               if (%changed and 0b1110 === 0) {
                 %dirty = %dirty or if (%composer.changed(p1)) 0b0100 else 0b0010
@@ -2886,7 +3043,8 @@
             }
             @Composable
             fun Test11(p1: Int, p3: Int, p0: Int, p2: Int, %composer: Composer?, %changed: Int) {
-              %composer = %composer.startRestartGroup(<>, "C(Test11)P(1,3):Test.kt")
+              %composer = %composer.startRestartGroup(<>)
+              sourceInformation(%composer, "C(Test11)P(1,3):Test.kt")
               val %dirty = %changed
               if (%changed and 0b1110 === 0) {
                 %dirty = %dirty or if (%composer.changed(p1)) 0b0100 else 0b0010
@@ -2914,7 +3072,8 @@
             }
             @Composable
             fun Test12(p1: Int, p3: Int, p2: Int, p0: Int, %composer: Composer?, %changed: Int) {
-              %composer = %composer.startRestartGroup(<>, "C(Test12)P(1,3,2):Test.kt")
+              %composer = %composer.startRestartGroup(<>)
+              sourceInformation(%composer, "C(Test12)P(1,3,2):Test.kt")
               val %dirty = %changed
               if (%changed and 0b1110 === 0) {
                 %dirty = %dirty or if (%composer.changed(p1)) 0b0100 else 0b0010
@@ -2942,7 +3101,8 @@
             }
             @Composable
             fun Test13(p2: Int, p0: Int, p1: Int, p3: Int, %composer: Composer?, %changed: Int) {
-              %composer = %composer.startRestartGroup(<>, "C(Test13)P(2):Test.kt")
+              %composer = %composer.startRestartGroup(<>)
+              sourceInformation(%composer, "C(Test13)P(2):Test.kt")
               val %dirty = %changed
               if (%changed and 0b1110 === 0) {
                 %dirty = %dirty or if (%composer.changed(p2)) 0b0100 else 0b0010
@@ -2970,7 +3130,8 @@
             }
             @Composable
             fun Test14(p2: Int, p0: Int, p3: Int, p1: Int, %composer: Composer?, %changed: Int) {
-              %composer = %composer.startRestartGroup(<>, "C(Test14)P(2!1,3):Test.kt")
+              %composer = %composer.startRestartGroup(<>)
+              sourceInformation(%composer, "C(Test14)P(2!1,3):Test.kt")
               val %dirty = %changed
               if (%changed and 0b1110 === 0) {
                 %dirty = %dirty or if (%composer.changed(p2)) 0b0100 else 0b0010
@@ -2998,7 +3159,8 @@
             }
             @Composable
             fun Test15(p2: Int, p1: Int, p0: Int, p3: Int, %composer: Composer?, %changed: Int) {
-              %composer = %composer.startRestartGroup(<>, "C(Test15)P(2,1):Test.kt")
+              %composer = %composer.startRestartGroup(<>)
+              sourceInformation(%composer, "C(Test15)P(2,1):Test.kt")
               val %dirty = %changed
               if (%changed and 0b1110 === 0) {
                 %dirty = %dirty or if (%composer.changed(p2)) 0b0100 else 0b0010
@@ -3026,7 +3188,8 @@
             }
             @Composable
             fun Test16(p2: Int, p1: Int, p3: Int, p0: Int, %composer: Composer?, %changed: Int) {
-              %composer = %composer.startRestartGroup(<>, "C(Test16)P(2,1,3):Test.kt")
+              %composer = %composer.startRestartGroup(<>)
+              sourceInformation(%composer, "C(Test16)P(2,1,3):Test.kt")
               val %dirty = %changed
               if (%changed and 0b1110 === 0) {
                 %dirty = %dirty or if (%composer.changed(p2)) 0b0100 else 0b0010
@@ -3054,7 +3217,8 @@
             }
             @Composable
             fun Test17(p2: Int, p3: Int, p0: Int, p1: Int, %composer: Composer?, %changed: Int) {
-              %composer = %composer.startRestartGroup(<>, "C(Test17)P(2,3):Test.kt")
+              %composer = %composer.startRestartGroup(<>)
+              sourceInformation(%composer, "C(Test17)P(2,3):Test.kt")
               val %dirty = %changed
               if (%changed and 0b1110 === 0) {
                 %dirty = %dirty or if (%composer.changed(p2)) 0b0100 else 0b0010
@@ -3082,7 +3246,8 @@
             }
             @Composable
             fun Test18(p2: Int, p3: Int, p1: Int, p0: Int, %composer: Composer?, %changed: Int) {
-              %composer = %composer.startRestartGroup(<>, "C(Test18)P(2,3,1):Test.kt")
+              %composer = %composer.startRestartGroup(<>)
+              sourceInformation(%composer, "C(Test18)P(2,3,1):Test.kt")
               val %dirty = %changed
               if (%changed and 0b1110 === 0) {
                 %dirty = %dirty or if (%composer.changed(p2)) 0b0100 else 0b0010
@@ -3110,7 +3275,8 @@
             }
             @Composable
             fun Test19(p3: Int, p0: Int, p1: Int, p2: Int, %composer: Composer?, %changed: Int) {
-              %composer = %composer.startRestartGroup(<>, "C(Test19)P(3):Test.kt")
+              %composer = %composer.startRestartGroup(<>)
+              sourceInformation(%composer, "C(Test19)P(3):Test.kt")
               val %dirty = %changed
               if (%changed and 0b1110 === 0) {
                 %dirty = %dirty or if (%composer.changed(p3)) 0b0100 else 0b0010
@@ -3138,7 +3304,8 @@
             }
             @Composable
             fun Test20(p3: Int, p0: Int, p2: Int, p1: Int, %composer: Composer?, %changed: Int) {
-              %composer = %composer.startRestartGroup(<>, "C(Test20)P(3!1,2):Test.kt")
+              %composer = %composer.startRestartGroup(<>)
+              sourceInformation(%composer, "C(Test20)P(3!1,2):Test.kt")
               val %dirty = %changed
               if (%changed and 0b1110 === 0) {
                 %dirty = %dirty or if (%composer.changed(p3)) 0b0100 else 0b0010
@@ -3166,7 +3333,8 @@
             }
             @Composable
             fun Test21(p3: Int, p1: Int, p0: Int, p2: Int, %composer: Composer?, %changed: Int) {
-              %composer = %composer.startRestartGroup(<>, "C(Test21)P(3,1):Test.kt")
+              %composer = %composer.startRestartGroup(<>)
+              sourceInformation(%composer, "C(Test21)P(3,1):Test.kt")
               val %dirty = %changed
               if (%changed and 0b1110 === 0) {
                 %dirty = %dirty or if (%composer.changed(p3)) 0b0100 else 0b0010
@@ -3194,7 +3362,8 @@
             }
             @Composable
             fun Test22(p3: Int, p1: Int, p2: Int, p0: Int, %composer: Composer?, %changed: Int) {
-              %composer = %composer.startRestartGroup(<>, "C(Test22)P(3,1,2):Test.kt")
+              %composer = %composer.startRestartGroup(<>)
+              sourceInformation(%composer, "C(Test22)P(3,1,2):Test.kt")
               val %dirty = %changed
               if (%changed and 0b1110 === 0) {
                 %dirty = %dirty or if (%composer.changed(p3)) 0b0100 else 0b0010
@@ -3222,7 +3391,8 @@
             }
             @Composable
             fun Test23(p3: Int, p2: Int, p0: Int, p1: Int, %composer: Composer?, %changed: Int) {
-              %composer = %composer.startRestartGroup(<>, "C(Test23)P(3,2):Test.kt")
+              %composer = %composer.startRestartGroup(<>)
+              sourceInformation(%composer, "C(Test23)P(3,2):Test.kt")
               val %dirty = %changed
               if (%changed and 0b1110 === 0) {
                 %dirty = %dirty or if (%composer.changed(p3)) 0b0100 else 0b0010
@@ -3250,7 +3420,8 @@
             }
             @Composable
             fun Test24(p3: Int, p2: Int, p1: Int, p0: Int, %composer: Composer?, %changed: Int) {
-              %composer = %composer.startRestartGroup(<>, "C(Test24)P(3,2,1):Test.kt")
+              %composer = %composer.startRestartGroup(<>)
+              sourceInformation(%composer, "C(Test24)P(3,2,1):Test.kt")
               val %dirty = %changed
               if (%changed and 0b1110 === 0) {
                 %dirty = %dirty or if (%composer.changed(p3)) 0b0100 else 0b0010
@@ -3300,7 +3471,8 @@
         expectedTransformed = """
             @Composable
             fun Test(value: LocalInlineClass, %composer: Composer?, %changed: Int) {
-              %composer = %composer.startRestartGroup(<>, "C(Test)P(0:c#runtime.tests.LocalInlineClass):Test.kt#992ot2")
+              %composer = %composer.startRestartGroup(<>)
+              sourceInformation(%composer, "C(Test)P(0:c#runtime.tests.LocalInlineClass):Test.kt#992ot2")
               val %dirty = %changed
               if (%changed and 0b1110 === 0) {
                 %dirty = %dirty or if (%composer.changed(value.value)) 0b0100 else 0b0010
@@ -3340,7 +3512,8 @@
         expectedTransformed = """
             @Composable
             fun Test(%composer: Composer?, %changed: Int) {
-              %composer = %composer.startRestartGroup(<>, "C(Test)<b()>,<c()>,<d()>,<A(b(),>,<B()>:Test.kt")
+              %composer = %composer.startRestartGroup(<>)
+              sourceInformation(%composer, "C(Test)<b()>,<c()>,<d()>,<A(b(),>,<B()>:Test.kt")
               if (%changed !== 0 || !%composer.skipping) {
                 A(b(%composer, 0), c(%composer, 0), d(%composer, 0), %composer, 0)
                 B(%composer, 0)
@@ -3388,7 +3561,8 @@
             class SomeClass {
               var a: String = "Test"
               fun onCreate() {
-                setContent(composableLambdaInstance(<>, true, "C<B(a)>,<B(a)>:Test.kt") { %composer: Composer?, %changed: Int ->
+                setContent(composableLambdaInstance(<>, true) { %composer: Composer?, %changed: Int ->
+                  sourceInformation(%composer, "C<B(a)>,<B(a)>:Test.kt")
                   if (%changed and 0b1011 xor 0b0010 !== 0 || !%composer.skipping) {
                     B(a, %composer, 0)
                     B(a, %composer, 0)
@@ -3402,7 +3576,8 @@
             }
             fun Test() {
               var a = "Test"
-              setContent(composableLambdaInstance(<>, true, "C<B(a)>,<B(a)>:Test.kt") { %composer: Composer?, %changed: Int ->
+              setContent(composableLambdaInstance(<>, true) { %composer: Composer?, %changed: Int ->
+                sourceInformation(%composer, "C<B(a)>,<B(a)>:Test.kt")
                 if (%changed and 0b1011 xor 0b0010 !== 0 || !%composer.skipping) {
                   B(a, %composer, 0)
                   B(a, %composer, 0)
@@ -3443,7 +3618,8 @@
         expectedTransformed = """
             @Composable
             fun Test(%composer: Composer?, %changed: Int) {
-              %composer = %composer.startRestartGroup(<>, "C(Test)<W>:Test.kt")
+              %composer = %composer.startRestartGroup(<>)
+              sourceInformation(%composer, "C(Test)<W>:Test.kt")
               if (%changed !== 0 || !%composer.skipping) {
                 W(ComposableSingletons%TestKt.lambda-1, %composer, 0)
               } else {
@@ -3454,13 +3630,16 @@
               }
             }
             internal object ComposableSingletons%TestKt {
-              val lambda-1: Function2<Composer, Int, Unit> = composableLambdaInstance(<>, false, "C<IW>:Test.kt") { %composer: Composer?, %changed: Int ->
+              val lambda-1: Function2<Composer, Int, Unit> = composableLambdaInstance(<>, false) { %composer: Composer?, %changed: Int ->
+                sourceInformation(%composer, "C<IW>:Test.kt")
                 if (%changed and 0b1011 xor 0b0010 !== 0 || !%composer.skipping) {
                   IW({ %composer: Composer?, %changed: Int ->
-                    %composer.startReplaceableGroup(<>, "C<T(2)>,<T(4)>:Test.kt")
+                    %composer.startReplaceableGroup(<>)
+                    sourceInformation(%composer, "C<T(2)>,<T(4)>:Test.kt")
                     if (%changed and 0b1011 xor 0b0010 !== 0 || !%composer.skipping) {
                       T(2, %composer, 0b0110)
-                      %composer.startReplaceableGroup(<>, "*<T(3)>")
+                      %composer.startReplaceableGroup(<>)
+                      sourceInformation(%composer, "*<T(3)>")
                       repeat(3) { it: Int ->
                         T(3, %composer, 0b0110)
                       }
@@ -3509,27 +3688,138 @@
             val current: Int
               @Composable @ReadOnlyComposable @JvmName(name = "getCurrent")
               get() {
-                %composer.startReplaceableGroup(<>, "C:Test.kt")
+                sourceInformationMarkerStart(%composer, <>, "C:Test.kt")
                 val tmp0 = 0
-                %composer.endReplaceableGroup()
+                sourceInformationMarkerEnd(%composer)
                 return tmp0
               }
             @Composable
             @ReadOnlyComposable
             fun calculateSometing(%composer: Composer?, %changed: Int): Int {
-              %composer.startReplaceableGroup(<>, "C(calculateSometing):Test.kt")
+              sourceInformationMarkerStart(%composer, <>, "C(calculateSometing):Test.kt")
               val tmp0 = 0
-              %composer.endReplaceableGroup()
+              sourceInformationMarkerEnd(%composer)
               return tmp0
             }
             @Composable
             fun Test(%composer: Composer?, %changed: Int) {
-              %composer = %composer.startRestartGroup(<>, "C(Test)<curren...>,<calcul...>,<Layout>:Test.kt")
+              %composer = %composer.startRestartGroup(<>)
+              sourceInformation(%composer, "C(Test)<curren...>,<calcul...>,<Layout>:Test.kt")
               if (%changed !== 0 || !%composer.skipping) {
                 val c = current
                 val cl = calculateSometing(%composer, 0)
                 Layout({ %composer: Composer?, %changed: Int ->
-                  %composer.startReplaceableGroup(<>, "C<Text("...>:Test.kt")
+                  %composer.startReplaceableGroup(<>)
+                  sourceInformation(%composer, "C<Text("...>:Test.kt")
+                  if (%changed and 0b1011 xor 0b0010 !== 0 || !%composer.skipping) {
+                    Text("%c %cl", %composer, 0)
+                  } else {
+                    %composer.skipToGroupEnd()
+                  }
+                  %composer.endReplaceableGroup()
+                }, %composer, 0)
+              } else {
+                %composer.skipToGroupEnd()
+              }
+              %composer.endRestartGroup()?.updateScope { %composer: Composer?, %force: Int ->
+                Test(%composer, %changed or 0b0001)
+              }
+            }
+        """,
+        """
+            import androidx.compose.runtime.Composable
+
+            @Composable
+            inline fun Layout(content: @Composable () -> Unit) { content() }
+
+            @Composable
+            fun Text(text: String) { }
+        """
+    )
+
+    @Test
+    fun testReadOnlyInlineValSourceLocations() = verifyComposeIrTransform(
+        """
+            import androidx.compose.runtime.Composable
+            import androidx.compose.runtime.ReadOnlyComposable
+
+            class CurrentHolder {
+                inline val current: Int
+                    @ReadOnlyComposable
+                    @Composable
+                    get() = 0
+            }
+
+            class HolderHolder {
+                private val _currentHolder = CurrentHolder()
+                val current: Int
+                    @ReadOnlyComposable
+                    @Composable
+                    get() = _currentHolder.current
+            }
+
+            val holderHolder = HolderHolder()
+
+            @Composable
+            @ReadOnlyComposable
+            fun calculateSometing(): Int {
+                return 0;
+            }
+
+            @Composable
+            fun Test() {
+                val c = holderHolder.current
+                val cl = calculateSometing()
+                Layout {
+                    Text("${'$'}c ${'$'}cl")
+                }
+            }
+        """,
+        """
+            @StabilityInferred(parameters = 0)
+            class CurrentHolder {
+              val current: Int
+                @ReadOnlyComposable @Composable @JvmName(name = "getCurrent")
+                get() {
+                  sourceInformationMarkerStart(%composer, <>, "C:Test.kt")
+                  val tmp0 = 0
+                  sourceInformationMarkerEnd(%composer)
+                  return tmp0
+                }
+              static val %stable: Int = 0
+            }
+            @StabilityInferred(parameters = 0)
+            class HolderHolder {
+              val _currentHolder: CurrentHolder = CurrentHolder()
+              val current: Int
+                @ReadOnlyComposable @Composable @JvmName(name = "getCurrent")
+                get() {
+                  sourceInformationMarkerStart(%composer, <>, "C<curren...>:Test.kt")
+                  val tmp0 = _currentHolder.current
+                  sourceInformationMarkerEnd(%composer)
+                  return tmp0
+                }
+              static val %stable: Int = 0
+            }
+            val holderHolder: HolderHolder = HolderHolder()
+            @Composable
+            @ReadOnlyComposable
+            fun calculateSometing(%composer: Composer?, %changed: Int): Int {
+              sourceInformationMarkerStart(%composer, <>, "C(calculateSometing):Test.kt")
+              val tmp0 = 0
+              sourceInformationMarkerEnd(%composer)
+              return tmp0
+            }
+            @Composable
+            fun Test(%composer: Composer?, %changed: Int) {
+              %composer = %composer.startRestartGroup(<>)
+              sourceInformation(%composer, "C(Test)<curren...>,<calcul...>,<Layout>:Test.kt")
+              if (%changed !== 0 || !%composer.skipping) {
+                val c = holderHolder.current
+                val cl = calculateSometing(%composer, 0)
+                Layout({ %composer: Composer?, %changed: Int ->
+                  %composer.startReplaceableGroup(<>)
+                  sourceInformation(%composer, "C<Text("...>:Test.kt")
                   if (%changed and 0b1011 xor 0b0010 !== 0 || !%composer.skipping) {
                     Text("%c %cl", %composer, 0)
                   } else {
diff --git a/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/ControlFlowTransformTestsNoSource.kt b/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/ControlFlowTransformTestsNoSource.kt
index 9b4fca9..8ccb434 100644
--- a/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/ControlFlowTransformTestsNoSource.kt
+++ b/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/ControlFlowTransformTestsNoSource.kt
@@ -33,7 +33,8 @@
         """
             @Composable
             fun Test(%composer: Composer?, %changed: Int) {
-              %composer = %composer.startRestartGroup(<>, "C(Test)")
+              %composer = %composer.startRestartGroup(<>)
+              sourceInformation(%composer, "C(Test)")
               if (%changed !== 0 || !%composer.skipping) {
                 A(a, %composer, 0)
                 A(b, %composer, 0)
@@ -86,7 +87,8 @@
         """
             @Composable
             fun Test(%composer: Composer?, %changed: Int) {
-              %composer = %composer.startRestartGroup(<>, "C(Test)")
+              %composer = %composer.startRestartGroup(<>)
+              sourceInformation(%composer, "C(Test)")
               if (%changed !== 0 || !%composer.skipping) {
                 W(ComposableSingletons%TestKt.lambda-1, %composer, 0)
               } else {
@@ -97,7 +99,7 @@
               }
             }
             internal object ComposableSingletons%TestKt {
-              val lambda-1: Function2<Composer, Int, Unit> = composableLambdaInstance(<>, false, "") { %composer: Composer?, %changed: Int ->
+              val lambda-1: Function2<Composer, Int, Unit> = composableLambdaInstance(<>, false) { %composer: Composer?, %changed: Int ->
                 if (%changed and 0b1011 xor 0b0010 !== 0 || !%composer.skipping) {
                   A(%composer, 0)
                 } else {
@@ -121,7 +123,8 @@
         """
             @Composable
             fun Test(%composer: Composer?, %changed: Int) {
-              %composer = %composer.startRestartGroup(<>, "C(Test)")
+              %composer = %composer.startRestartGroup(<>)
+              sourceInformation(%composer, "C(Test)")
               if (%changed !== 0 || !%composer.skipping) {
                 IW({ %composer: Composer?, %changed: Int ->
                   if (%changed and 0b1011 xor 0b0010 !== 0 || !%composer.skipping) {
diff --git a/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/DefaultParamTransformTests.kt b/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/DefaultParamTransformTests.kt
index 1c8d410..0f07375 100644
--- a/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/DefaultParamTransformTests.kt
+++ b/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/DefaultParamTransformTests.kt
@@ -63,7 +63,8 @@
         """
             @Composable
             fun Test(%composer: Composer?, %changed: Int) {
-              %composer = %composer.startRestartGroup(<>, "C(Test)<A(1)>,<B()>,<B(2)>:Test.kt")
+              %composer = %composer.startRestartGroup(<>)
+              sourceInformation(%composer, "C(Test)<A(1)>,<B()>,<B(2)>:Test.kt")
               if (%changed !== 0 || !%composer.skipping) {
                 A(1, %composer, 0b0110)
                 B(0, %composer, 0, 0b0001)
@@ -96,7 +97,8 @@
         """
             @Composable
             fun Example(foo: Foo, %composer: Composer?, %changed: Int, %default: Int) {
-              %composer = %composer.startRestartGroup(<>, "C(Example)P(0:Foo):Test.kt")
+              %composer = %composer.startRestartGroup(<>)
+              sourceInformation(%composer, "C(Example)P(0:Foo):Test.kt")
               val %dirty = %changed
               if (%default and 0b0001 !== 0) {
                 %dirty = %dirty or 0b0110
@@ -117,7 +119,8 @@
             }
             @Composable
             fun Test(%composer: Composer?, %changed: Int) {
-              %composer = %composer.startRestartGroup(<>, "C(Test)<Exampl...>:Test.kt")
+              %composer = %composer.startRestartGroup(<>)
+              sourceInformation(%composer, "C(Test)<Exampl...>:Test.kt")
               if (%changed !== 0 || !%composer.skipping) {
                 Example(Foo(0), %composer, 0, 0b0001)
               } else {
@@ -145,7 +148,8 @@
         """
             @Composable
             fun Test(%composer: Composer?, %changed: Int) {
-              %composer = %composer.startRestartGroup(<>, "C(Test)<A(0,>,<A(a>:Test.kt")
+              %composer = %composer.startRestartGroup(<>)
+              sourceInformation(%composer, "C(Test)<A(0,>,<A(a>:Test.kt")
               if (%changed !== 0 || !%composer.skipping) {
                 A(0, 1, 2, 0, 0, %composer, 0b000110110110, 0b00011000)
                 A(0, 0, 2, 0, 0, %composer, 0b000110000110, 0b00011010)
@@ -173,7 +177,8 @@
         """
             @Composable
             fun Test(x: Int, %composer: Composer?, %changed: Int, %default: Int) {
-              %composer = %composer.startRestartGroup(<>, "C(Test):Test.kt")
+              %composer = %composer.startRestartGroup(<>)
+              sourceInformation(%composer, "C(Test):Test.kt")
               val %dirty = %changed
               if (%changed and 0b1110 === 0) {
                 %dirty = %dirty or if (%default and 0b0001 === 0 && %composer.changed(x)) 0b0100 else 0b0010
@@ -217,7 +222,8 @@
         """
             @Composable
             fun A(a: Int, b: Int, %composer: Composer?, %changed: Int, %default: Int) {
-              %composer = %composer.startRestartGroup(<>, "C(A):Test.kt")
+              %composer = %composer.startRestartGroup(<>)
+              sourceInformation(%composer, "C(A):Test.kt")
               val %dirty = %changed
               if (%default and 0b0001 !== 0) {
                 %dirty = %dirty or 0b0110
@@ -331,7 +337,8 @@
         """
             @Composable
             fun Example(a00: Int, a01: Int, a02: Int, a03: Int, a04: Int, a05: Int, a06: Int, a07: Int, a08: Int, a09: Int, a10: Int, a11: Int, a12: Int, a13: Int, a14: Int, a15: Int, a16: Int, a17: Int, a18: Int, a19: Int, a20: Int, a21: Int, a22: Int, a23: Int, a24: Int, a25: Int, a26: Int, a27: Int, a28: Int, a29: Int, a30: Int, %composer: Composer?, %changed: Int, %changed1: Int, %changed2: Int, %changed3: Int, %default: Int) {
-              %composer = %composer.startRestartGroup(<>, "C(Example):Test.kt")
+              %composer = %composer.startRestartGroup(<>)
+              sourceInformation(%composer, "C(Example):Test.kt")
               val %dirty = %changed
               val %dirty1 = %changed1
               val %dirty2 = %changed2
@@ -703,7 +710,8 @@
         """
             @Composable
             fun Example(a00: Int, a01: Int, a02: Int, a03: Int, a04: Int, a05: Int, a06: Int, a07: Int, a08: Int, a09: Int, a10: Int, a11: Int, a12: Int, a13: Int, a14: Int, a15: Int, a16: Int, a17: Int, a18: Int, a19: Int, a20: Int, a21: Int, a22: Int, a23: Int, a24: Int, a25: Int, a26: Int, a27: Int, a28: Int, a29: Int, a30: Int, a31: Int, %composer: Composer?, %changed: Int, %changed1: Int, %changed2: Int, %changed3: Int, %default: Int, %default1: Int) {
-              %composer = %composer.startRestartGroup(<>, "C(Example):Test.kt")
+              %composer = %composer.startRestartGroup(<>)
+              sourceInformation(%composer, "C(Example):Test.kt")
               val %dirty = %changed
               val %dirty1 = %changed1
               val %dirty2 = %changed2
@@ -1085,7 +1093,8 @@
         """
             @Composable
             fun Example(a00: Int, a01: Int, a02: Int, a03: Int, a04: Int, a05: Int, a06: Int, a07: Int, a08: Int, a09: Foo?, a10: Int, a11: Int, a12: Int, a13: Int, a14: Int, a15: Int, a16: Int, a17: Int, a18: Int, a19: Int, a20: Int, a21: Int, a22: Int, a23: Int, a24: Int, a25: Int, a26: Int, a27: Int, a28: Int, a29: Int, a30: Int, a31: Foo?, %composer: Composer?, %changed: Int, %changed1: Int, %changed2: Int, %changed3: Int, %default: Int, %default1: Int) {
-              %composer = %composer.startRestartGroup(<>, "C(Example):Test.kt")
+              %composer = %composer.startRestartGroup(<>)
+              sourceInformation(%composer, "C(Example):Test.kt")
               val %dirty = %changed
               val %dirty1 = %changed1
               val %dirty2 = %changed2
@@ -1419,7 +1428,8 @@
               @NonRestartableComposable
               @Composable
               fun foo(x: Int, %composer: Composer?, %changed: Int, %default: Int) {
-                %composer.startReplaceableGroup(<>, "C(foo):Test.kt")
+                %composer.startReplaceableGroup(<>)
+                sourceInformation(%composer, "C(foo):Test.kt")
                 if (%default and 0b0001 !== 0) {
                   x = 0
                 }
@@ -1432,7 +1442,8 @@
               @NonRestartableComposable
               @Composable
               fun Example(%composer: Composer?, %changed: Int) {
-                %composer.startReplaceableGroup(<>, "C(Example)<foo()>:Test.kt")
+                %composer.startReplaceableGroup(<>)
+                sourceInformation(%composer, "C(Example)<foo()>:Test.kt")
                 foo(0, %composer, 0b01110000 and %changed shl 0b0011, 0b0001)
                 %composer.endReplaceableGroup()
               }
diff --git a/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/FunctionBodySkippingTransformTests.kt b/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/FunctionBodySkippingTransformTests.kt
index 44c68ac..de0dc0e 100644
--- a/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/FunctionBodySkippingTransformTests.kt
+++ b/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/FunctionBodySkippingTransformTests.kt
@@ -72,7 +72,8 @@
         """
             @Composable
             fun Test(x: Int, y: Int, %composer: Composer?, %changed: Int, %default: Int) {
-              %composer = %composer.startRestartGroup(<>, "C(Test)<Wrap>:Test.kt")
+              %composer = %composer.startRestartGroup(<>)
+              sourceInformation(%composer, "C(Test)<Wrap>:Test.kt")
               val %dirty = %changed
               if (%default and 0b0001 !== 0) {
                 %dirty = %dirty or 0b0110
@@ -92,14 +93,17 @@
                   y = 0
                 }
                 used(y)
-                Wrap(composableLambda(%composer, <>, true, "C:Test.kt") { %composer: Composer?, %changed: Int ->
+                Wrap(composableLambda(%composer, <>, true) { %composer: Composer?, %changed: Int ->
+                  sourceInformation(%composer, "C:Test.kt")
                   if (%changed and 0b1011 xor 0b0010 !== 0 || !%composer.skipping) {
                     if (x > 0) {
-                      %composer.startReplaceableGroup(<>, "<A(x)>")
+                      %composer.startReplaceableGroup(<>)
+                      sourceInformation(%composer, "<A(x)>")
                       A(x, 0, %composer, 0b1110 and %dirty, 0b0010)
                       %composer.endReplaceableGroup()
                     } else {
-                      %composer.startReplaceableGroup(<>, "<A(x)>")
+                      %composer.startReplaceableGroup(<>)
+                      sourceInformation(%composer, "<A(x)>")
                       A(x, 0, %composer, 0b1110 and %dirty, 0b0010)
                       %composer.endReplaceableGroup()
                     }
@@ -136,7 +140,8 @@
               Example(class <no name provided> : A {
                 @Composable
                 override fun compute(it: Int, %composer: Composer?, %changed: Int) {
-                  %composer = %composer.startRestartGroup(<>, "C(compute)<comput...>:Test.kt")
+                  %composer = %composer.startRestartGroup(<>)
+                  sourceInformation(%composer, "C(compute)<comput...>:Test.kt")
                   val %dirty = %changed
                   if (%changed and 0b1110 === 0) {
                     %dirty = %dirty or if (%composer.changed(it)) 0b0100 else 0b0010
@@ -186,7 +191,8 @@
         """
             @Composable
             fun Button(colors: ButtonColors, %composer: Composer?, %changed: Int) {
-              %composer = %composer.startRestartGroup(<>, "C(Button)<getCol...>,<Text("...>:Test.kt")
+              %composer = %composer.startRestartGroup(<>)
+              sourceInformation(%composer, "C(Button)<getCol...>,<Text("...>:Test.kt")
               val %dirty = %changed
               if (%changed and 0b1110 === 0) {
                 %dirty = %dirty or if (%composer.changed(colors)) 0b0100 else 0b0010
@@ -202,12 +208,14 @@
             }
             @Composable
             fun Test(%composer: Composer?, %changed: Int) {
-              %composer = %composer.startRestartGroup(<>, "C(Test)<Button>:Test.kt")
+              %composer = %composer.startRestartGroup(<>)
+              sourceInformation(%composer, "C(Test)<Button>:Test.kt")
               if (%changed !== 0 || !%composer.skipping) {
                 Button(class <no name provided> : ButtonColors {
                   @Composable
                   override fun getColor(%composer: Composer?, %changed: Int): Color {
-                    %composer.startReplaceableGroup(<>, "C(getColor)<condit...>:Test.kt")
+                    %composer.startReplaceableGroup(<>)
+                    sourceInformation(%composer, "C(getColor)<condit...>:Test.kt")
                     val tmp0 = if (condition(%composer, 0)) {
                       Companion.Red
                     } else {
@@ -308,7 +316,8 @@
         """
             @Composable
             fun RowColumnImpl(orientation: LayoutOrientation, modifier: Modifier?, arrangement: Vertical?, crossAxisAlignment: Horizontal?, crossAxisSize: SizeMode?, content: Function2<Composer, Int, Unit>, %composer: Composer?, %changed: Int, %default: Int) {
-              %composer = %composer.startRestartGroup(<>, "C(RowColumnImpl)P(5,4!1,2,3)<conten...>:Test.kt")
+              %composer = %composer.startRestartGroup(<>)
+              sourceInformation(%composer, "C(RowColumnImpl)P(5,4!1,2,3)<conten...>:Test.kt")
               val %dirty = %changed
               if (%default and 0b0001 !== 0) {
                 %dirty = %dirty or 0b0110
@@ -376,7 +385,8 @@
             }
             @Composable
             fun Column(modifier: Modifier?, verticalArrangement: Vertical?, horizontalGravity: Horizontal?, content: Function2<Composer, Int, Unit>, %composer: Composer?, %changed: Int, %default: Int) {
-              %composer = %composer.startRestartGroup(<>, "C(Column)P(2,3,1)<RowCol...>:Test.kt")
+              %composer = %composer.startRestartGroup(<>)
+              sourceInformation(%composer, "C(Column)P(2,3,1)<RowCol...>:Test.kt")
               val %dirty = %changed
               if (%default and 0b0001 !== 0) {
                 %dirty = %dirty or 0b0110
@@ -448,7 +458,8 @@
         """
             @Composable
             fun SimpleBox(modifier: Modifier?, %composer: Composer?, %changed: Int, %default: Int) {
-              %composer = %composer.startRestartGroup(<>, "C(SimpleBox):Test.kt")
+              %composer = %composer.startRestartGroup(<>)
+              sourceInformation(%composer, "C(SimpleBox):Test.kt")
               val %dirty = %changed
               if (%default and 0b0001 !== 0) {
                 %dirty = %dirty or 0b0110
@@ -484,7 +495,8 @@
         """
             @Composable
             fun Example(a: Int, %composer: Composer?, %changed: Int, %default: Int) {
-              %composer = %composer.startRestartGroup(<>, "C(Example):Test.kt")
+              %composer = %composer.startRestartGroup(<>)
+              sourceInformation(%composer, "C(Example):Test.kt")
               val %dirty = %changed
               if (%changed and 0b1110 === 0) {
                 %dirty = %dirty or if (%default and 0b0001 === 0 && %composer.changed(a)) 0b0100 else 0b0010
@@ -531,7 +543,8 @@
         """
             @Composable
             fun Example(a: Int, %composer: Composer?, %changed: Int) {
-              %composer = %composer.startRestartGroup(<>, "C(Example)<Inner(...>:Test.kt")
+              %composer = %composer.startRestartGroup(<>)
+              sourceInformation(%composer, "C(Example)<Inner(...>:Test.kt")
               val %dirty = %changed
               if (%changed and 0b1110 === 0) {
                 %dirty = %dirty or if (%composer.changed(a)) 0b0100 else 0b0010
@@ -539,7 +552,8 @@
               if (%dirty and 0b1011 xor 0b0010 !== 0 || !%composer.skipping) {
                 @Composable
                 fun Inner(%composer: Composer?, %changed: Int) {
-                  %composer.startReplaceableGroup(<>, "C(Inner)<A(a)>:Test.kt")
+                  %composer.startReplaceableGroup(<>)
+                  sourceInformation(%composer, "C(Inner)<A(a)>:Test.kt")
                   A(a, %composer, 0b1110 and %dirty)
                   %composer.endReplaceableGroup()
                 }
@@ -577,11 +591,13 @@
             @Composable
             @NonRestartableComposable
             fun Example(%composer: Composer?, %changed: Int) {
-              %composer.startReplaceableGroup(<>, "C(Example)<Call()>:Test.kt")
+              %composer.startReplaceableGroup(<>)
+              sourceInformation(%composer, "C(Example)<Call()>:Test.kt")
               Call(%composer, 0)
               val tmp0_iterator = 0 .. 1.iterator()
               while (tmp0_iterator.hasNext()) {
-                %composer.startReplaceableGroup(<>, "<Call()>,<Call()>")
+                %composer.startReplaceableGroup(<>)
+                sourceInformation(%composer, "<Call()>,<Call()>")
                 val index = tmp0_iterator.next()
                 Call(%composer, 0)
                 if (condition()) {
@@ -621,7 +637,8 @@
         """
             @Composable
             fun SimpleBox(modifier: Modifier?, shape: Shape?, %composer: Composer?, %changed: Int, %default: Int) {
-              %composer = %composer.startRestartGroup(<>, "C(SimpleBox):Test.kt")
+              %composer = %composer.startRestartGroup(<>)
+              sourceInformation(%composer, "C(SimpleBox):Test.kt")
               val %dirty = %changed
               if (%default and 0b0001 !== 0) {
                 %dirty = %dirty or 0b0110
@@ -680,7 +697,8 @@
         """
             @Composable
             fun SimpleBox(modifier: Modifier?, content: Function2<Composer, Int, Unit>?, %composer: Composer?, %changed: Int, %default: Int) {
-              %composer = %composer.startRestartGroup(<>, "C(SimpleBox)P(1)<conten...>:Test.kt")
+              %composer = %composer.startRestartGroup(<>)
+              sourceInformation(%composer, "C(SimpleBox)P(1)<conten...>:Test.kt")
               val %dirty = %changed
               if (%default and 0b0001 !== 0) {
                 %dirty = %dirty or 0b0110
@@ -717,7 +735,8 @@
               }
             }
             internal object ComposableSingletons%TestKt {
-              val lambda-1: Function2<Composer, Int, Unit> = composableLambdaInstance(<>, false, "C:Test.kt") { %composer: Composer?, %changed: Int ->
+              val lambda-1: Function2<Composer, Int, Unit> = composableLambdaInstance(<>, false) { %composer: Composer?, %changed: Int ->
+                sourceInformation(%composer, "C:Test.kt")
                 if (%changed and 0b1011 xor 0b0010 !== 0 || !%composer.skipping) {
                   Unit
                 } else {
@@ -746,7 +765,8 @@
         """
             val foo: Function4<Int, Foo, Composer, Int, Unit> = ComposableSingletons%TestKt.lambda-1
             internal object ComposableSingletons%TestKt {
-              val lambda-1: Function4<Int, Foo, Composer, Int, Unit> = composableLambdaInstance(<>, false, "C<A(x)>,<B(y)>:") { x: Int, y: Foo, %composer: Composer?, %changed: Int ->
+              val lambda-1: Function4<Int, Foo, Composer, Int, Unit> = composableLambdaInstance(<>, false) { x: Int, y: Foo, %composer: Composer?, %changed: Int ->
+                sourceInformation(%composer, "C<A(x)>,<B(y)>:Test.kt")
                 val %dirty = %changed
                 if (%changed and 0b1110 === 0) {
                   %dirty = %dirty or if (%composer.changed(x)) 0b0100 else 0b0010
@@ -781,7 +801,8 @@
         """
             val foo: Function4<Int, Foo, Composer, Int, Unit> = ComposableSingletons%TestKt.lambda-1
             internal object ComposableSingletons%TestKt {
-              val lambda-1: Function4<Int, Foo, Composer, Int, Unit> = composableLambdaInstance(<>, false, "C<A(x)>,<B(y)>:") { x: Int, y: Foo, %composer: Composer?, %changed: Int ->
+              val lambda-1: Function4<Int, Foo, Composer, Int, Unit> = composableLambdaInstance(<>, false) { x: Int, y: Foo, %composer: Composer?, %changed: Int ->
+                sourceInformation(%composer, "C<A(x)>,<B(y)>:Test.kt")
                 A(x, %composer, 0b1110 and %changed)
                 B(y, %composer, 0b1000)
               }
@@ -806,7 +827,8 @@
         """
             @Composable
             fun SomeThing(content: Function2<Composer, Int, Unit>, %composer: Composer?, %changed: Int) {
-              %composer = %composer.startRestartGroup(<>, "C(SomeThing)<conten...>:Test.kt")
+              %composer = %composer.startRestartGroup(<>)
+              sourceInformation(%composer, "C(SomeThing)<conten...>:Test.kt")
               val %dirty = %changed
               if (%changed and 0b1110 === 0) {
                 %dirty = %dirty or if (%composer.changed(content)) 0b0100 else 0b0010
@@ -822,7 +844,8 @@
             }
             @Composable
             fun Example(%composer: Composer?, %changed: Int) {
-              %composer = %composer.startRestartGroup(<>, "C(Example)<SomeTh...>:Test.kt")
+              %composer = %composer.startRestartGroup(<>)
+              sourceInformation(%composer, "C(Example)<SomeTh...>:Test.kt")
               if (%changed !== 0 || !%composer.skipping) {
                 SomeThing(ComposableSingletons%TestKt.lambda-1, %composer, 0)
               } else {
@@ -833,7 +856,8 @@
               }
             }
             internal object ComposableSingletons%TestKt {
-              val lambda-1: Function2<Composer, Int, Unit> = composableLambdaInstance(<>, false, "C:Test.kt") { %composer: Composer?, %changed: Int ->
+              val lambda-1: Function2<Composer, Int, Unit> = composableLambdaInstance(<>, false) { %composer: Composer?, %changed: Int ->
+                sourceInformation(%composer, "C:Test.kt")
                 if (%changed and 0b1011 xor 0b0010 !== 0 || !%composer.skipping) {
                   val id = object
                 } else {
@@ -857,7 +881,8 @@
         """
             @Composable
             fun B(values: IntArray, %composer: Composer?, %changed: Int) {
-              %composer = %composer.startRestartGroup(<>, "C(B):Test.kt")
+              %composer = %composer.startRestartGroup(<>)
+              sourceInformation(%composer, "C(B):Test.kt")
               val %dirty = %changed
               %composer.startReplaceableGroup(values.size)
               val tmp0_iterator = values.iterator()
@@ -896,7 +921,8 @@
         """
             @Composable
             fun B(values: Array<out Foo>, %composer: Composer?, %changed: Int) {
-              %composer = %composer.startRestartGroup(<>, "C(B):Test.kt")
+              %composer = %composer.startRestartGroup(<>)
+              sourceInformation(%composer, "C(B):Test.kt")
               val %dirty = %changed
               %composer.startReplaceableGroup(values.size)
               val tmp0_iterator = values.iterator()
@@ -935,7 +961,8 @@
         """
             @Composable
             fun B(values: Array<out Foo>, %composer: Composer?, %changed: Int) {
-              %composer = %composer.startRestartGroup(<>, "C(B):Test.kt")
+              %composer = %composer.startRestartGroup(<>)
+              sourceInformation(%composer, "C(B):Test.kt")
               print(values)
               %composer.endRestartGroup()?.updateScope { %composer: Composer?, %force: Int ->
                 B(*values, %composer, %changed or 0b0001)
@@ -965,7 +992,8 @@
               var counter: Int = 0
               @Composable
               fun A(%composer: Composer?, %changed: Int) {
-                %composer = %composer.startRestartGroup(<>, "C(A):Test.kt")
+                %composer = %composer.startRestartGroup(<>)
+                sourceInformation(%composer, "C(A):Test.kt")
                 if (%changed and 0b0001 !== 0 || !%composer.skipping) {
                   print("hello world")
                 } else {
@@ -978,7 +1006,8 @@
               }
               @Composable
               fun B(%composer: Composer?, %changed: Int) {
-                %composer = %composer.startRestartGroup(<>, "C(B):Test.kt")
+                %composer = %composer.startRestartGroup(<>)
+                sourceInformation(%composer, "C(B):Test.kt")
                 print(counter)
                 val tmp0_rcvr = <this>
                 %composer.endRestartGroup()?.updateScope { %composer: Composer?, %force: Int ->
@@ -1006,7 +1035,8 @@
         """
             @Composable
             fun Example(a: Int, b: Int, c: Int, %composer: Composer?, %changed: Int, %default: Int) {
-              %composer = %composer.startRestartGroup(<>, "C(Example)<makeIn...>:Test.kt")
+              %composer = %composer.startRestartGroup(<>)
+              sourceInformation(%composer, "C(Example)<makeIn...>:Test.kt")
               val %dirty = %changed
               if (%default and 0b0001 !== 0) {
                 %dirty = %dirty or 0b0110
@@ -1075,7 +1105,8 @@
         """
             @Composable
             fun Wrap(y: Int, content: Function3<@[ParameterName(name = 'x')] Int, Composer, Int, Unit>, %composer: Composer?, %changed: Int) {
-              %composer = %composer.startRestartGroup(<>, "C(Wrap)P(1)<conten...>:Test.kt")
+              %composer = %composer.startRestartGroup(<>)
+              sourceInformation(%composer, "C(Wrap)P(1)<conten...>:Test.kt")
               val %dirty = %changed
               if (%changed and 0b1110 === 0) {
                 %dirty = %dirty or if (%composer.changed(y)) 0b0100 else 0b0010
@@ -1094,7 +1125,8 @@
             }
             @Composable
             fun Test(x: Int, y: Int, %composer: Composer?, %changed: Int, %default: Int) {
-              %composer = %composer.startRestartGroup(<>, "C(Test)<Wrap(1...>:Test.kt")
+              %composer = %composer.startRestartGroup(<>)
+              sourceInformation(%composer, "C(Test)<Wrap(1...>:Test.kt")
               val %dirty = %changed
               if (%default and 0b0001 !== 0) {
                 %dirty = %dirty or 0b0110
@@ -1114,7 +1146,8 @@
                   y = 0
                 }
                 used(y)
-                Wrap(10, composableLambda(%composer, <>, true, "C<A(x)>:Test.kt") { it: Int, %composer: Composer?, %changed: Int ->
+                Wrap(10, composableLambda(%composer, <>, true) { it: Int, %composer: Composer?, %changed: Int ->
+                  sourceInformation(%composer, "C<A(x)>:Test.kt")
                   val %dirty = %changed
                   if (%changed and 0b1110 === 0) {
                     %dirty = %dirty or if (%composer.changed(it)) 0b0100 else 0b0010
@@ -1151,7 +1184,8 @@
         """
             @Composable
             fun Test(x: Int, y: Int, %composer: Composer?, %changed: Int, %default: Int): Int {
-              %composer.startReplaceableGroup(<>, "C(Test)<A(x,>:Test.kt")
+              %composer.startReplaceableGroup(<>)
+              sourceInformation(%composer, "C(Test)<A(x,>:Test.kt")
               if (%default and 0b0001 !== 0) {
                 x = 0
               }
@@ -1179,7 +1213,8 @@
         """
             val test: Function3<Int, Composer, Int, Unit> = ComposableSingletons%TestKt.lambda-1
             internal object ComposableSingletons%TestKt {
-              val lambda-1: Function3<Int, Composer, Int, Unit> = composableLambdaInstance(<>, false, "C<A(x)>:") { x: Int, %composer: Composer?, %changed: Int ->
+              val lambda-1: Function3<Int, Composer, Int, Unit> = composableLambdaInstance(<>, false) { x: Int, %composer: Composer?, %changed: Int ->
+                sourceInformation(%composer, "C<A(x)>:Test.kt")
                 val %dirty = %changed
                 if (%changed and 0b1110 === 0) {
                   %dirty = %dirty or if (%composer.changed(x)) 0b0100 else 0b0010
@@ -1205,7 +1240,8 @@
         """
             @Composable
             fun Test(x: Int, %composer: Composer?, %changed: Int): Int {
-              %composer.startReplaceableGroup(<>, "C(Test)<A()>:Test.kt")
+              %composer.startReplaceableGroup(<>)
+              sourceInformation(%composer, "C(Test)<A()>:Test.kt")
               val tmp0 = A(0, 0, %composer, 0, 0b0011)
               %composer.endReplaceableGroup()
               return tmp0
@@ -1226,7 +1262,8 @@
         """
             @Composable
             fun Test(x: Int, y: Int, %composer: Composer?, %changed: Int) {
-              %composer = %composer.startRestartGroup(<>, "C(Test)<A(y>:Test.kt")
+              %composer = %composer.startRestartGroup(<>)
+              sourceInformation(%composer, "C(Test)<A(y>:Test.kt")
               val %dirty = %changed
               if (%changed and 0b1110 === 0) {
                 %dirty = %dirty or if (%composer.changed(x)) 0b0100 else 0b0010
@@ -1269,7 +1306,8 @@
         """
             @Composable
             fun CanSkip(a: Int, b: Foo?, %composer: Composer?, %changed: Int, %default: Int) {
-              %composer = %composer.startRestartGroup(<>, "C(CanSkip):Test.kt")
+              %composer = %composer.startRestartGroup(<>)
+              sourceInformation(%composer, "C(CanSkip):Test.kt")
               val %dirty = %changed
               if (%default and 0b0001 !== 0) {
                 %dirty = %dirty or 0b0110
@@ -1308,7 +1346,8 @@
             }
             @Composable
             fun CannotSkip(a: Int, b: Foo, %composer: Composer?, %changed: Int) {
-              %composer = %composer.startRestartGroup(<>, "C(CannotSkip):Test.kt")
+              %composer = %composer.startRestartGroup(<>)
+              sourceInformation(%composer, "C(CannotSkip):Test.kt")
               used(a)
               used(b)
               print("Hello World")
@@ -1318,7 +1357,8 @@
             }
             @Composable
             fun NoParams(%composer: Composer?, %changed: Int) {
-              %composer = %composer.startRestartGroup(<>, "C(NoParams):Test.kt")
+              %composer = %composer.startRestartGroup(<>)
+              sourceInformation(%composer, "C(NoParams):Test.kt")
               if (%changed !== 0 || !%composer.skipping) {
                 print("Hello World")
               } else {
@@ -1345,7 +1385,8 @@
         """
             @Composable
             fun Bar.CanSkip(b: Foo?, %composer: Composer?, %changed: Int, %default: Int) {
-              %composer = %composer.startRestartGroup(<>, "C(CanSkip):Test.kt")
+              %composer = %composer.startRestartGroup(<>)
+              sourceInformation(%composer, "C(CanSkip):Test.kt")
               val %dirty = %changed
               if (%default.inv() and 0b0001 !== 0 || %dirty and 0b0001 !== 0 || !%composer.skipping) {
                 if (%changed and 0b0001 === 0 || %composer.defaultsInvalid) {
@@ -1387,7 +1428,8 @@
         """
             @Composable
             fun Test(%composer: Composer?, %changed: Int) {
-              %composer = %composer.startRestartGroup(<>, "C(Test)<A()>:Test.kt")
+              %composer = %composer.startRestartGroup(<>)
+              sourceInformation(%composer, "C(Test)<A()>:Test.kt")
               if (%changed !== 0 || !%composer.skipping) {
                 A(%composer, 0)
               } else {
@@ -1414,7 +1456,8 @@
         """
             @Composable
             fun Test(x: Int, %composer: Composer?, %changed: Int) {
-              %composer = %composer.startRestartGroup(<>, "C(Test)<A(x)>:Test.kt")
+              %composer = %composer.startRestartGroup(<>)
+              sourceInformation(%composer, "C(Test)<A(x)>:Test.kt")
               val %dirty = %changed
               if (%changed and 0b1110 === 0) {
                 %dirty = %dirty or if (%composer.changed(x)) 0b0100 else 0b0010
@@ -1455,7 +1498,8 @@
         """
             @Composable
             fun A(text: String, %composer: Composer?, %changed: Int) {
-              %composer = %composer.startRestartGroup(<>, "C(A)<B(text...>:Test.kt")
+              %composer = %composer.startRestartGroup(<>)
+              sourceInformation(%composer, "C(A)<B(text...>:Test.kt")
               val %dirty = %changed
               if (%changed and 0b1110 === 0) {
                 %dirty = %dirty or if (%composer.changed(text)) 0b0100 else 0b0010
@@ -1471,7 +1515,8 @@
             }
             @Composable
             fun B(text: String, color: Color, %composer: Composer?, %changed: Int, %default: Int) {
-              %composer = %composer.startRestartGroup(<>, "C(B)P(1,0:Color):Test.kt")
+              %composer = %composer.startRestartGroup(<>)
+              sourceInformation(%composer, "C(B)P(1,0:Color):Test.kt")
               val %dirty = %changed
               if (%default and 0b0001 !== 0) {
                 %dirty = %dirty or 0b0110
@@ -1563,7 +1608,8 @@
         """
             @Composable
             fun A(%composer: Composer?, %changed: Int) {
-              %composer = %composer.startRestartGroup(<>, "C(A)<D>,<C({})>,<C(stab...>,<C(16.d...>,<C(Dp(1...>,<C(16.d...>,<C(norm...>,<C(Int....>,<C(stab...>,<C(Modi...>,<C(Foo....>,<C(cons...>,<C(123)>,<C(123>,<C(x)>,<C(x>:Test.kt")
+              %composer = %composer.startRestartGroup(<>)
+              sourceInformation(%composer, "C(A)<D>,<C({})>,<C(stab...>,<C(16.d...>,<C(Dp(1...>,<C(16.d...>,<C(norm...>,<C(Int....>,<C(stab...>,<C(Modi...>,<C(Foo....>,<C(cons...>,<C(123)>,<C(123>,<C(x)>,<C(x>:Test.kt")
               if (%changed !== 0 || !%composer.skipping) {
                 val x = 123
                 D(ComposableSingletons%TestKt.lambda-1, %composer, 0)
@@ -1592,7 +1638,8 @@
             }
             @Composable
             fun B(%composer: Composer?, %changed: Int) {
-              %composer = %composer.startRestartGroup(<>, "C(B)<C(Math...>,<C(Math...>:Test.kt")
+              %composer = %composer.startRestartGroup(<>)
+              sourceInformation(%composer, "C(B)<C(Math...>,<C(Math...>:Test.kt")
               if (%changed !== 0 || !%composer.skipping) {
                 C(random(), %composer, 0)
                 C(random() / 100.0f, %composer, 0)
@@ -1604,7 +1651,8 @@
               }
             }
             internal object ComposableSingletons%TestKt {
-              val lambda-1: Function2<Composer, Int, Unit> = composableLambdaInstance(<>, false, "C:Test.kt") { %composer: Composer?, %changed: Int ->
+              val lambda-1: Function2<Composer, Int, Unit> = composableLambdaInstance(<>, false) { %composer: Composer?, %changed: Int ->
+                sourceInformation(%composer, "C:Test.kt")
                 if (%changed and 0b1011 xor 0b0010 !== 0 || !%composer.skipping) {
                   Unit
                 } else {
@@ -1628,7 +1676,8 @@
         """
             @Composable
             fun Example(%composer: Composer?, %changed: Int) {
-              %composer = %composer.startRestartGroup(<>, "C(Example)<D>:Test.kt")
+              %composer = %composer.startRestartGroup(<>)
+              sourceInformation(%composer, "C(Example)<D>:Test.kt")
               if (%changed !== 0 || !%composer.skipping) {
                 D(ComposableSingletons%TestKt.lambda-1, %composer, 0)
               } else {
@@ -1639,7 +1688,8 @@
               }
             }
             internal object ComposableSingletons%TestKt {
-              val lambda-1: Function2<Composer, Int, Unit> = composableLambdaInstance(<>, false, "C:Test.kt") { %composer: Composer?, %changed: Int ->
+              val lambda-1: Function2<Composer, Int, Unit> = composableLambdaInstance(<>, false) { %composer: Composer?, %changed: Int ->
+                sourceInformation(%composer, "C:Test.kt")
                 if (%changed and 0b1011 xor 0b0010 !== 0 || !%composer.skipping) {
                   Unit
                 } else {
@@ -1664,7 +1714,8 @@
         """
             @Composable
             fun Test(x: Int, %composer: Composer?, %changed: Int, %default: Int) {
-              %composer = %composer.startRestartGroup(<>, "C(Test)<A(x)>:Test.kt")
+              %composer = %composer.startRestartGroup(<>)
+              sourceInformation(%composer, "C(Test)<A(x)>:Test.kt")
               val %dirty = %changed
               if (%default and 0b0001 !== 0) {
                 %dirty = %dirty or 0b0110
@@ -1701,7 +1752,8 @@
         """
             @Composable
             fun Test(x: Int, %composer: Composer?, %changed: Int, %default: Int) {
-              %composer = %composer.startRestartGroup(<>, "C(Test)<I()>,<A(x)>:Test.kt")
+              %composer = %composer.startRestartGroup(<>)
+              sourceInformation(%composer, "C(Test)<I()>,<A(x)>:Test.kt")
               val %dirty = %changed
               if (%changed and 0b1110 === 0) {
                 %dirty = %dirty or if (%default and 0b0001 === 0 && %composer.changed(x)) 0b0100 else 0b0010
@@ -1746,7 +1798,8 @@
         """
             @Composable
             fun Test(x: Foo, %composer: Composer?, %changed: Int) {
-              %composer = %composer.startRestartGroup(<>, "C(Test)<A(x)>:Test.kt")
+              %composer = %composer.startRestartGroup(<>)
+              sourceInformation(%composer, "C(Test)<A(x)>:Test.kt")
               A(x, %composer, 0b1000)
               %composer.endRestartGroup()?.updateScope { %composer: Composer?, %force: Int ->
                 Test(x, %composer, %changed or 0b0001)
@@ -1770,7 +1823,8 @@
         """
             @Composable
             fun Test(x: Foo?, %composer: Composer?, %changed: Int, %default: Int) {
-              %composer = %composer.startRestartGroup(<>, "C(Test)<A(x)>:Test.kt")
+              %composer = %composer.startRestartGroup(<>)
+              sourceInformation(%composer, "C(Test)<A(x)>:Test.kt")
               val %dirty = %changed
               if (%changed and 0b1110 === 0) {
                 %dirty = %dirty or if (%default and 0b0001 === 0 && %composer.changed(x)) 0b0100 else 0b0010
@@ -1815,7 +1869,8 @@
         """
             @Composable
             fun Test(a: Int, b: Boolean, c: Int, d: Foo?, e: List<Int>?, %composer: Composer?, %changed: Int, %default: Int) {
-              %composer = %composer.startRestartGroup(<>, "C(Test)<A(a,>:Test.kt")
+              %composer = %composer.startRestartGroup(<>)
+              sourceInformation(%composer, "C(Test)<A(a,>:Test.kt")
               val %dirty = %changed
               if (%default and 0b0001 !== 0) {
                 %dirty = %dirty or 0b0110
@@ -1887,7 +1942,8 @@
         """
             @Composable
             fun X(x: Int, %composer: Composer?, %changed: Int) {
-              %composer = %composer.startRestartGroup(<>, "C(X)<X(x>,<X(x)>:Test.kt")
+              %composer = %composer.startRestartGroup(<>)
+              sourceInformation(%composer, "C(X)<X(x>,<X(x)>:Test.kt")
               val %dirty = %changed
               if (%changed and 0b1110 === 0) {
                 %dirty = %dirty or if (%composer.changed(x)) 0b0100 else 0b0010
@@ -1948,7 +2004,8 @@
               itemsIndexed(items, ComposableSingletons%TestKt.lambda-1)
             }
             internal object ComposableSingletons%TestKt {
-              val lambda-1: @[ExtensionFunctionType] Function5<LazyItemScope, Int, User?, Composer, Int, Unit> = composableLambdaInstance(<>, false, "C:Test.kt") { index: Int, user: User?, %composer: Composer?, %changed: Int ->
+              val lambda-1: @[ExtensionFunctionType] Function5<LazyItemScope, Int, User?, Composer, Int, Unit> = composableLambdaInstance(<>, false) { index: Int, user: User?, %composer: Composer?, %changed: Int ->
+                sourceInformation(%composer, "C:Test.kt")
                 if (%changed and 0b0001010000000001 xor 0b010000000000 !== 0 || !%composer.skipping) {
                   print("Hello World")
                 } else {
@@ -1974,7 +2031,8 @@
         """
             @Composable
             fun Unstable.Test(%composer: Composer?, %changed: Int) {
-              %composer = %composer.startRestartGroup(<>, "C(Test)<doSome...>:Test.kt")
+              %composer = %composer.startRestartGroup(<>)
+              sourceInformation(%composer, "C(Test)<doSome...>:Test.kt")
               val %dirty = %changed
               if (%changed and 0b1110 === 0) {
                 %dirty = %dirty or if (%composer.changed(<this>)) 0b0100 else 0b0010
@@ -1990,7 +2048,8 @@
             }
             @Composable
             fun doSomething(x: Unstable, %composer: Composer?, %changed: Int) {
-              %composer = %composer.startRestartGroup(<>, "C(doSomething):Test.kt")
+              %composer = %composer.startRestartGroup(<>)
+              sourceInformation(%composer, "C(doSomething):Test.kt")
               if (%changed and 0b0001 !== 0 || !%composer.skipping) {
               } else {
                 %composer.skipToGroupEnd()
@@ -2074,7 +2133,8 @@
         """
             @Composable
             fun A(x: Int, %composer: Composer?, %changed: Int) {
-              %composer = %composer.startRestartGroup(<>, "C(A)<B(>:Test.kt")
+              %composer = %composer.startRestartGroup(<>)
+              sourceInformation(%composer, "C(A)<B(>:Test.kt")
               val %dirty = %changed
               if (%changed and 0b1110 === 0) {
                 %dirty = %dirty or if (%composer.changed(x)) 0b0100 else 0b0010
@@ -2118,14 +2178,16 @@
             val stableUnused: @[ExtensionFunctionType] Function3<StableFoo, Composer, Int, Unit> = ComposableSingletons%TestKt.lambda-3
             val stableUsed: @[ExtensionFunctionType] Function3<StableFoo, Composer, Int, Unit> = ComposableSingletons%TestKt.lambda-4
             internal object ComposableSingletons%TestKt {
-              val lambda-1: @[ExtensionFunctionType] Function3<Foo, Composer, Int, Unit> = composableLambdaInstance(<>, false, "C:") { %composer: Composer?, %changed: Int ->
+              val lambda-1: @[ExtensionFunctionType] Function3<Foo, Composer, Int, Unit> = composableLambdaInstance(<>, false) { %composer: Composer?, %changed: Int ->
+                sourceInformation(%composer, "C:Test.kt")
                 if (%changed and 0b01010001 xor 0b00010000 !== 0 || !%composer.skipping) {
                   Unit
                 } else {
                   %composer.skipToGroupEnd()
                 }
               }
-              val lambda-2: @[ExtensionFunctionType] Function3<Foo, Composer, Int, Unit> = composableLambdaInstance(<>, false, "C:") { %composer: Composer?, %changed: Int ->
+              val lambda-2: @[ExtensionFunctionType] Function3<Foo, Composer, Int, Unit> = composableLambdaInstance(<>, false) { %composer: Composer?, %changed: Int ->
+                sourceInformation(%composer, "C:Test.kt")
                 val %dirty = %changed
                 if (%changed and 0b1110 === 0) {
                   %dirty = %dirty or if (%composer.changed(%this%null)) 0b0100 else 0b0010
@@ -2136,14 +2198,16 @@
                   %composer.skipToGroupEnd()
                 }
               }
-              val lambda-3: @[ExtensionFunctionType] Function3<StableFoo, Composer, Int, Unit> = composableLambdaInstance(<>, false, "C:") { %composer: Composer?, %changed: Int ->
+              val lambda-3: @[ExtensionFunctionType] Function3<StableFoo, Composer, Int, Unit> = composableLambdaInstance(<>, false) { %composer: Composer?, %changed: Int ->
+                sourceInformation(%composer, "C:Test.kt")
                 if (%changed and 0b01010001 xor 0b00010000 !== 0 || !%composer.skipping) {
                   Unit
                 } else {
                   %composer.skipToGroupEnd()
                 }
               }
-              val lambda-4: @[ExtensionFunctionType] Function3<StableFoo, Composer, Int, Unit> = composableLambdaInstance(<>, false, "C:") { %composer: Composer?, %changed: Int ->
+              val lambda-4: @[ExtensionFunctionType] Function3<StableFoo, Composer, Int, Unit> = composableLambdaInstance(<>, false) { %composer: Composer?, %changed: Int ->
+                sourceInformation(%composer, "C:Test.kt")
                 val %dirty = %changed
                 if (%changed and 0b1110 === 0) {
                   %dirty = %dirty or if (%composer.changed(%this%null)) 0b0100 else 0b0010
@@ -2179,19 +2243,22 @@
         """
             @Composable
             fun A(x: Int, %composer: Composer?, %changed: Int) {
-              %composer = %composer.startRestartGroup(<>, "C(A)<Provid...>,<B(x)>:Test.kt")
+              %composer = %composer.startRestartGroup(<>)
+              sourceInformation(%composer, "C(A)<Provid...>,<B(x)>:Test.kt")
               val %dirty = %changed
               if (%changed and 0b1110 === 0) {
                 %dirty = %dirty or if (%composer.changed(x)) 0b0100 else 0b0010
               }
               if (%dirty and 0b1011 xor 0b0010 !== 0 || !%composer.skipping) {
-                Provide(composableLambda(%composer, <>, true, "C<Provid...>,<B(x,>:Test.kt") { y: Int, %composer: Composer?, %changed: Int ->
+                Provide(composableLambda(%composer, <>, true) { y: Int, %composer: Composer?, %changed: Int ->
+                  sourceInformation(%composer, "C<Provid...>,<B(x,>:Test.kt")
                   val %dirty = %changed
                   if (%changed and 0b1110 === 0) {
                     %dirty = %dirty or if (%composer.changed(y)) 0b0100 else 0b0010
                   }
                   if (%dirty and 0b01011011 xor 0b00010010 !== 0 || !%composer.skipping) {
-                    Provide(composableLambda(%composer, <>, true, "C<B(x,>:Test.kt") { z: Int, %composer: Composer?, %changed: Int ->
+                    Provide(composableLambda(%composer, <>, true) { z: Int, %composer: Composer?, %changed: Int ->
+                      sourceInformation(%composer, "C<B(x,>:Test.kt")
                       val %dirty = %changed
                       if (%changed and 0b1110 === 0) {
                         %dirty = %dirty or if (%composer.changed(z)) 0b0100 else 0b0010
@@ -2235,7 +2302,8 @@
         """
             @Composable
             fun A(x: Int, %composer: Composer?, %changed: Int) {
-              %composer = %composer.startRestartGroup(<>, "C(A)<foo(x)>:Test.kt")
+              %composer = %composer.startRestartGroup(<>)
+              sourceInformation(%composer, "C(A)<foo(x)>:Test.kt")
               val %dirty = %changed
               if (%changed and 0b1110 === 0) {
                 %dirty = %dirty or if (%composer.changed(x)) 0b0100 else 0b0010
@@ -2243,7 +2311,8 @@
               if (%dirty and 0b1011 xor 0b0010 !== 0 || !%composer.skipping) {
                 @Composable
                 fun foo(y: Int, %composer: Composer?, %changed: Int) {
-                  %composer.startReplaceableGroup(<>, "C(foo)<B(x,>:Test.kt")
+                  %composer.startReplaceableGroup(<>)
+                  sourceInformation(%composer, "C(foo)<B(x,>:Test.kt")
                   B(x, y, %composer, 0b1110 and %dirty or 0b01110000 and %changed shl 0b0011)
                   %composer.endReplaceableGroup()
                 }
@@ -2322,7 +2391,8 @@
         """
             @Composable
             fun Example(a00: Int, a01: Int, a02: Int, a03: Int, a04: Int, a05: Int, a06: Int, a07: Int, a08: Int, a09: Int, a10: Int, a11: Int, a12: Int, a13: Int, a14: Int, %composer: Composer?, %changed: Int, %changed1: Int, %default: Int) {
-              %composer = %composer.startRestartGroup(<>, "C(Example)<Exampl...>,<Exampl...>:Test.kt")
+              %composer = %composer.startRestartGroup(<>)
+              sourceInformation(%composer, "C(Example)<Exampl...>,<Exampl...>:Test.kt")
               val %dirty = %changed
               val %dirty1 = %changed1
               if (%default and 0b0001 !== 0) {
@@ -2525,7 +2595,8 @@
         """
             @Composable
             fun Example(a00: Int, a01: Int, a02: Int, a03: Int, a04: Int, a05: Int, a06: Int, a07: Int, a08: Int, a09: Int, a10: Int, a11: Int, a12: Int, a13: Int, a14: Int, a15: Int, %composer: Composer?, %changed: Int, %changed1: Int, %default: Int) {
-              %composer = %composer.startRestartGroup(<>, "C(Example)<Exampl...>,<Exampl...>:Test.kt")
+              %composer = %composer.startRestartGroup(<>)
+              sourceInformation(%composer, "C(Example)<Exampl...>,<Exampl...>:Test.kt")
               val %dirty = %changed
               val %dirty1 = %changed1
               if (%default and 0b0001 !== 0) {
@@ -2696,17 +2767,17 @@
               val current: Int
                 @Composable @ReadOnlyComposable @JvmName(name = "getCurrent")
                 get() {
-                  %composer.startReplaceableGroup(<>, "C:Test.kt")
+                  sourceInformationMarkerStart(%composer, <>, "C:Test.kt")
                   val tmp0 = %composer.hashCode()
-                  %composer.endReplaceableGroup()
+                  sourceInformationMarkerEnd(%composer)
                   return tmp0
                 }
               @ReadOnlyComposable
               @Composable
               fun getHashCode(%composer: Composer?, %changed: Int): Int {
-                %composer.startReplaceableGroup(<>, "C(getHashCode):Test.kt")
+                sourceInformationMarkerStart(%composer, <>, "C(getHashCode):Test.kt")
                 val tmp0 = %composer.hashCode()
-                %composer.endReplaceableGroup()
+                sourceInformationMarkerEnd(%composer)
                 return tmp0
               }
               static val %stable: Int = 0
@@ -2714,9 +2785,9 @@
             @ReadOnlyComposable
             @Composable
             fun getHashCode(%composer: Composer?, %changed: Int): Int {
-              %composer.startReplaceableGroup(<>, "C(getHashCode):Test.kt")
+              sourceInformationMarkerStart(%composer, <>, "C(getHashCode):Test.kt")
               val tmp0 = %composer.hashCode()
-              %composer.endReplaceableGroup()
+              sourceInformationMarkerEnd(%composer)
               return tmp0
             }
         """
@@ -2743,7 +2814,8 @@
         """
             @Composable
             fun Example(wontChange: Int, mightChange: Int, %composer: Composer?, %changed: Int, %default: Int) {
-              %composer = %composer.startRestartGroup(<>, "C(Example)P(1)<curren...>,<A(wont...>,<A(migh...>:Test.kt")
+              %composer = %composer.startRestartGroup(<>)
+              sourceInformation(%composer, "C(Example)P(1)<curren...>,<A(wont...>,<A(migh...>:Test.kt")
               val %dirty = %changed
               if (%default and 0b0001 !== 0) {
                 %dirty = %dirty or 0b0110
@@ -2794,7 +2866,8 @@
         """
             @Composable
             fun Example(content: Function2<Composer, Int, Unit>, %composer: Composer?, %changed: Int) {
-              %composer = %composer.startRestartGroup(<>, "C(Example)<invoke...>:Test.kt")
+              %composer = %composer.startRestartGroup(<>)
+              sourceInformation(%composer, "C(Example)<invoke...>:Test.kt")
               val %dirty = %changed
               if (%changed and 0b1110 === 0) {
                 %dirty = %dirty or if (%composer.changed(content)) 0b0100 else 0b0010
@@ -2854,7 +2927,8 @@
         """
             @Composable
             fun Box2(modifier: Modifier?, paddingStart: Dp, content: Function2<Composer, Int, Unit>?, %composer: Composer?, %changed: Int, %default: Int) {
-              %composer = %composer.startRestartGroup(<>, "C(Box2)P(1,2:c#ui.unit.Dp)<conten...>:Test.kt")
+              %composer = %composer.startRestartGroup(<>)
+              sourceInformation(%composer, "C(Box2)P(1,2:c#ui.unit.Dp)<conten...>:Test.kt")
               val %dirty = %changed
               if (%default and 0b0001 !== 0) {
                 %dirty = %dirty or 0b0110
@@ -2902,7 +2976,8 @@
               }
             }
             internal object ComposableSingletons%TestKt {
-              val lambda-1: Function2<Composer, Int, Unit> = composableLambdaInstance(<>, false, "C:Test.kt") { %composer: Composer?, %changed: Int ->
+              val lambda-1: Function2<Composer, Int, Unit> = composableLambdaInstance(<>, false) { %composer: Composer?, %changed: Int ->
+                sourceInformation(%composer, "C:Test.kt")
                 if (%changed and 0b1011 xor 0b0010 !== 0 || !%composer.skipping) {
                   Unit
                 } else {
@@ -2933,14 +3008,16 @@
         """
             @Composable
             fun Test(cond: Boolean, %composer: Composer?, %changed: Int) {
-              %composer = %composer.startRestartGroup(<>, "C(Test):Test.kt")
+              %composer = %composer.startRestartGroup(<>)
+              sourceInformation(%composer, "C(Test):Test.kt")
               val %dirty = %changed
               if (%changed and 0b1110 === 0) {
                 %dirty = %dirty or if (%composer.changed(cond)) 0b0100 else 0b0010
               }
               if (%dirty and 0b1011 xor 0b0010 !== 0 || !%composer.skipping) {
                 if (cond) {
-                  %composer.startReplaceableGroup(<>, "<A()>")
+                  %composer.startReplaceableGroup(<>)
+                  sourceInformation(%composer, "<A()>")
                   A(%composer, 0)
                   %composer.endReplaceableGroup()
                 } else {
@@ -2948,7 +3025,8 @@
                   %composer.endReplaceableGroup()
                 }
                 if (cond) {
-                  %composer.startReplaceableGroup(<>, "<B()>")
+                  %composer.startReplaceableGroup(<>)
+                  sourceInformation(%composer, "<B()>")
                   B(%composer, 0)
                   %composer.endReplaceableGroup()
                 } else {
@@ -2991,7 +3069,8 @@
         """
             @Composable
             fun Unskippable(a: Unstable, b: Stable, c: MaybeStable, %composer: Composer?, %changed: Int) {
-              %composer = %composer.startRestartGroup(<>, "C(Unskippable):Test.kt")
+              %composer = %composer.startRestartGroup(<>)
+              sourceInformation(%composer, "C(Unskippable):Test.kt")
               used(a)
               %composer.endRestartGroup()?.updateScope { %composer: Composer?, %force: Int ->
                 Unskippable(a, b, c, %composer, %changed or 0b0001)
@@ -2999,7 +3078,8 @@
             }
             @Composable
             fun Skippable1(a: Unstable, b: Stable, c: MaybeStable, %composer: Composer?, %changed: Int) {
-              %composer = %composer.startRestartGroup(<>, "C(Skippable1):Test.kt")
+              %composer = %composer.startRestartGroup(<>)
+              sourceInformation(%composer, "C(Skippable1):Test.kt")
               val %dirty = %changed
               if (%changed and 0b01110000 === 0) {
                 %dirty = %dirty or if (%composer.changed(b)) 0b00100000 else 0b00010000
@@ -3015,7 +3095,8 @@
             }
             @Composable
             fun Skippable2(a: Unstable, b: Stable, c: MaybeStable, %composer: Composer?, %changed: Int) {
-              %composer = %composer.startRestartGroup(<>, "C(Skippable2):Test.kt")
+              %composer = %composer.startRestartGroup(<>)
+              sourceInformation(%composer, "C(Skippable2):Test.kt")
               val %dirty = %changed
               if (%changed and 0b001110000000 === 0) {
                 %dirty = %dirty or if (%composer.changed(c)) 0b000100000000 else 0b10000000
@@ -3031,7 +3112,8 @@
             }
             @Composable
             fun Skippable3(a: Unstable, b: Stable, c: MaybeStable, %composer: Composer?, %changed: Int) {
-              %composer = %composer.startRestartGroup(<>, "C(Skippable3):Test.kt")
+              %composer = %composer.startRestartGroup(<>)
+              sourceInformation(%composer, "C(Skippable3):Test.kt")
               if (%changed and 0b0001 !== 0 || !%composer.skipping) {
               } else {
                 %composer.skipToGroupEnd()
@@ -3061,7 +3143,8 @@
         """
             @Composable
             fun MaybeStable.example(x: Int, %composer: Composer?, %changed: Int) {
-              %composer = %composer.startRestartGroup(<>, "C(example):Test.kt")
+              %composer = %composer.startRestartGroup(<>)
+              sourceInformation(%composer, "C(example):Test.kt")
               val %dirty = %changed
               if (%changed and 0b1110 === 0) {
                 %dirty = %dirty or if (%composer.changed(<this>)) 0b0100 else 0b0010
@@ -3081,7 +3164,8 @@
             }
             val example: @[ExtensionFunctionType] Function4<MaybeStable, Int, Composer, Int, Unit> = ComposableSingletons%TestKt.lambda-1
             internal object ComposableSingletons%TestKt {
-              val lambda-1: @[ExtensionFunctionType] Function4<MaybeStable, Int, Composer, Int, Unit> = composableLambdaInstance(<>, false, "C:") { it: Int, %composer: Composer?, %changed: Int ->
+              val lambda-1: @[ExtensionFunctionType] Function4<MaybeStable, Int, Composer, Int, Unit> = composableLambdaInstance(<>, false) { it: Int, %composer: Composer?, %changed: Int ->
+                sourceInformation(%composer, "C:Test.kt")
                 val %dirty = %changed
                 if (%changed and 0b1110 === 0) {
                   %dirty = %dirty or if (%composer.changed(%this%null)) 0b0100 else 0b0010
diff --git a/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/LambdaMemoizationTransformTests.kt b/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/LambdaMemoizationTransformTests.kt
index 838f9bf..1946801 100644
--- a/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/LambdaMemoizationTransformTests.kt
+++ b/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/LambdaMemoizationTransformTests.kt
@@ -36,7 +36,8 @@
             @StabilityInferred(parameters = 0)
             class A {
               val b: String = ""
-              val c: Function2<Composer, Int, Unit> = composableLambdaInstance(<>, true, "C:") { %composer: Composer?, %changed: Int ->
+              val c: Function2<Composer, Int, Unit> = composableLambdaInstance(<>, true) { %composer: Composer?, %changed: Int ->
+                sourceInformation(%composer, "C:Test.kt")
                 if (%changed and 0b1011 xor 0b0010 !== 0 || !%composer.skipping) {
                   print(b)
                 } else {
@@ -66,23 +67,28 @@
         """
             @Composable
             fun Example(%composer: Composer?, %changed: Int) {
-              %composer = %composer.startRestartGroup(<>, "C(Example):Test.kt")
+              %composer = %composer.startRestartGroup(<>)
+              sourceInformation(%composer, "C(Example):Test.kt")
               if (%changed !== 0 || !%composer.skipping) {
                 @Composable
                 fun A(%composer: Composer?, %changed: Int) {
-                  %composer.startReplaceableGroup(<>, "C(A):Test.kt")
+                  %composer.startReplaceableGroup(<>)
+                  sourceInformation(%composer, "C(A):Test.kt")
                   %composer.endReplaceableGroup()
                 }
                 @Composable
                 fun B(content: Function2<Composer, Int, Unit>, %composer: Composer?, %changed: Int) {
-                  %composer.startReplaceableGroup(<>, "C(B)<conten...>:Test.kt")
+                  %composer.startReplaceableGroup(<>)
+                  sourceInformation(%composer, "C(B)<conten...>:Test.kt")
                   content(%composer, 0b1110 and %changed)
                   %composer.endReplaceableGroup()
                 }
                 @Composable
                 fun C(%composer: Composer?, %changed: Int) {
-                  %composer.startReplaceableGroup(<>, "C(C)<B>:Test.kt")
-                  B(composableLambda(%composer, <>, false, "C<A()>:Test.kt") { %composer: Composer?, %changed: Int ->
+                  %composer.startReplaceableGroup(<>)
+                  sourceInformation(%composer, "C(C)<B>:Test.kt")
+                  B(composableLambda(%composer, <>, false) { %composer: Composer?, %changed: Int ->
+                    sourceInformation(%composer, "C<A()>:Test.kt")
                     if (%changed and 0b1011 xor 0b0010 !== 0 || !%composer.skipping) {
                       A(%composer, 0)
                     } else {
@@ -120,10 +126,12 @@
         """
             @Composable
             fun A(%composer: Composer?, %changed: Int) {
-              %composer = %composer.startRestartGroup(<>, "C(A)<B>:Test.kt")
+              %composer = %composer.startRestartGroup(<>)
+              sourceInformation(%composer, "C(A)<B>:Test.kt")
               if (%changed !== 0 || !%composer.skipping) {
                 <<LOCALDELPROP>>
-                B(composableLambda(%composer, <>, true, "C:Test.kt") { %composer: Composer?, %changed: Int ->
+                B(composableLambda(%composer, <>, true) { %composer: Composer?, %changed: Int ->
+                  sourceInformation(%composer, "C:Test.kt")
                   if (%changed and 0b1011 xor 0b0010 !== 0 || !%composer.skipping) {
                     print(<get-x>())
                   } else {
@@ -157,14 +165,16 @@
             val foo: Function2<Composer, Int, Unit> = ComposableSingletons%TestKt.lambda-1
             val bar: Function2<Composer, Int, Unit> = ComposableSingletons%TestKt.lambda-2
             internal object ComposableSingletons%TestKt {
-              val lambda-1: Function2<Composer, Int, Unit> = composableLambdaInstance(<>, false, "C:") { %composer: Composer?, %changed: Int ->
+              val lambda-1: Function2<Composer, Int, Unit> = composableLambdaInstance(<>, false) { %composer: Composer?, %changed: Int ->
+                sourceInformation(%composer, "C:Test.kt")
                 if (%changed and 0b1011 xor 0b0010 !== 0 || !%composer.skipping) {
                   Unit
                 } else {
                   %composer.skipToGroupEnd()
                 }
               }
-              val lambda-2: Function2<Composer, Int, Unit> = composableLambdaInstance(<>, false, "C:") { %composer: Composer?, %changed: Int ->
+              val lambda-2: Function2<Composer, Int, Unit> = composableLambdaInstance(<>, false) { %composer: Composer?, %changed: Int ->
+                sourceInformation(%composer, "C:Test.kt")
                 if (%changed and 0b1011 xor 0b0010 !== 0 || !%composer.skipping) {
                   Unit
                 } else {
@@ -192,7 +202,8 @@
         """
             @Composable
             fun A(%composer: Composer?, %changed: Int) {
-              %composer = %composer.startRestartGroup(<>, "C(A)<B(foo)>,<B(bar)>:Test.kt")
+              %composer = %composer.startRestartGroup(<>)
+              sourceInformation(%composer, "C(A)<B(foo)>,<B(bar)>:Test.kt")
               if (%changed !== 0 || !%composer.skipping) {
                 val foo = ComposableSingletons%TestKt.lambda-1
                 val bar = ComposableSingletons%TestKt.lambda-2
@@ -206,14 +217,16 @@
               }
             }
             internal object ComposableSingletons%TestKt {
-              val lambda-1: Function2<Composer, Int, Unit> = composableLambdaInstance(<>, false, "C:Test.kt") { %composer: Composer?, %changed: Int ->
+              val lambda-1: Function2<Composer, Int, Unit> = composableLambdaInstance(<>, false) { %composer: Composer?, %changed: Int ->
+                sourceInformation(%composer, "C:Test.kt")
                 if (%changed and 0b1011 xor 0b0010 !== 0 || !%composer.skipping) {
                   Unit
                 } else {
                   %composer.skipToGroupEnd()
                 }
               }
-              val lambda-2: Function2<Composer, Int, Unit> = composableLambdaInstance(<>, false, "C:Test.kt") { %composer: Composer?, %changed: Int ->
+              val lambda-2: Function2<Composer, Int, Unit> = composableLambdaInstance(<>, false) { %composer: Composer?, %changed: Int ->
+                sourceInformation(%composer, "C:Test.kt")
                 if (%changed and 0b1011 xor 0b0010 !== 0 || !%composer.skipping) {
                   Unit
                 } else {
@@ -240,7 +253,8 @@
         """
             @Composable
             fun A(%composer: Composer?, %changed: Int) {
-              %composer = %composer.startRestartGroup(<>, "C(A)<B>:Test.kt")
+              %composer = %composer.startRestartGroup(<>)
+              sourceInformation(%composer, "C(A)<B>:Test.kt")
               if (%changed !== 0 || !%composer.skipping) {
                 B(ComposableSingletons%TestKt.lambda-1, %composer, 0)
               } else {
@@ -251,7 +265,8 @@
               }
             }
             internal object ComposableSingletons%TestKt {
-              val lambda-1: Function2<Composer, Int, Unit> = composableLambdaInstance(<>, false, "C:Test.kt") { %composer: Composer?, %changed: Int ->
+              val lambda-1: Function2<Composer, Int, Unit> = composableLambdaInstance(<>, false) { %composer: Composer?, %changed: Int ->
+                sourceInformation(%composer, "C:Test.kt")
                 if (%changed and 0b1011 xor 0b0010 !== 0 || !%composer.skipping) {
                   Unit
                 } else {
@@ -282,7 +297,8 @@
         """
             @Composable
             fun Test(enabled: Boolean, content: Function2<Composer, Int, Unit>?, %composer: Composer?, %changed: Int, %default: Int) {
-              %composer = %composer.startRestartGroup(<>, "C(Test)P(1)<Wrap(c...>:Test.kt")
+              %composer = %composer.startRestartGroup(<>)
+              sourceInformation(%composer, "C(Test)P(1)<Wrap(c...>:Test.kt")
               val %dirty = %changed
               if (%default and 0b0001 !== 0) {
                 %dirty = %dirty or 0b0110
@@ -296,7 +312,8 @@
               }
               if (%dirty and 0b01011011 xor 0b00010010 !== 0 || !%composer.skipping) {
                 if (%default and 0b0010 !== 0) {
-                  content = composableLambda(%composer, <>, true, "C<Displa...>:Test.kt") { %composer: Composer?, %changed: Int ->
+                  content = composableLambda(%composer, <>, true) { %composer: Composer?, %changed: Int ->
+                    sourceInformation(%composer, "C<Displa...>:Test.kt")
                     if (%changed and 0b1011 xor 0b0010 !== 0 || !%composer.skipping) {
                       Display("%enabled", %composer, 0)
                     } else {
@@ -337,13 +354,15 @@
         """
             @Composable
             fun Test(enabled: Boolean, %composer: Composer?, %changed: Int) {
-              %composer = %composer.startRestartGroup(<>, "C(Test)<Wrap(c...>:Test.kt")
+              %composer = %composer.startRestartGroup(<>)
+              sourceInformation(%composer, "C(Test)<Wrap(c...>:Test.kt")
               val %dirty = %changed
               if (%changed and 0b1110 === 0) {
                 %dirty = %dirty or if (%composer.changed(enabled)) 0b0100 else 0b0010
               }
               if (%dirty and 0b1011 xor 0b0010 !== 0 || !%composer.skipping) {
-                val content = composableLambda(%composer, <>, true, "C<Displa...>:Test.kt") { %composer: Composer?, %changed: Int ->
+                val content = composableLambda(%composer, <>, true) { %composer: Composer?, %changed: Int ->
+                  sourceInformation(%composer, "C<Displa...>:Test.kt")
                   if (%changed and 0b1011 xor 0b0010 !== 0 || !%composer.skipping) {
                     Display("%enabled", %composer, 0)
                   } else {
@@ -388,7 +407,8 @@
         """
         @Composable
         fun TestLambda(content: Function0<Unit>, %composer: Composer?, %changed: Int) {
-          %composer = %composer.startRestartGroup(<>, "C(TestLambda):Test.kt")
+          %composer = %composer.startRestartGroup(<>)
+          sourceInformation(%composer, "C(TestLambda):Test.kt")
           val %dirty = %changed
           if (%changed and 0b1110 === 0) {
             %dirty = %dirty or if (%composer.changed(content)) 0b0100 else 0b0010
@@ -404,7 +424,8 @@
         }
         @Composable
         fun Test(%composer: Composer?, %changed: Int) {
-          %composer = %composer.startRestartGroup(<>, "C(Test)<TestLa...>:Test.kt")
+          %composer = %composer.startRestartGroup(<>)
+          sourceInformation(%composer, "C(Test)<TestLa...>:Test.kt")
           if (%changed !== 0 || !%composer.skipping) {
             TestLambda({
               println("Doesn't capture")
@@ -440,7 +461,8 @@
         """
         @Composable
         fun TestLambda(content: Function0<Unit>, %composer: Composer?, %changed: Int) {
-          %composer = %composer.startRestartGroup(<>, "C(TestLambda):Test.kt")
+          %composer = %composer.startRestartGroup(<>)
+          sourceInformation(%composer, "C(TestLambda):Test.kt")
           val %dirty = %changed
           if (%changed and 0b1110 === 0) {
             %dirty = %dirty or if (%composer.changed(content)) 0b0100 else 0b0010
@@ -456,7 +478,8 @@
         }
         @Composable
         fun Test(a: String, %composer: Composer?, %changed: Int) {
-          %composer = %composer.startRestartGroup(<>, "C(Test)<{>,<TestLa...>:Test.kt")
+          %composer = %composer.startRestartGroup(<>)
+          sourceInformation(%composer, "C(Test)<{>,<TestLa...>:Test.kt")
           val %dirty = %changed
           if (%changed and 0b1110 === 0) {
             %dirty = %dirty or if (%composer.changed(a)) 0b0100 else 0b0010
diff --git a/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/RememberIntrinsicTransformTests.kt b/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/RememberIntrinsicTransformTests.kt
index 51532ad..e0863c1 100644
--- a/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/RememberIntrinsicTransformTests.kt
+++ b/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/RememberIntrinsicTransformTests.kt
@@ -62,7 +62,8 @@
             @Composable
             @NonRestartableComposable
             fun app(x: Boolean, %composer: Composer?, %changed: Int) {
-              %composer.startReplaceableGroup(<>, "C(app)<rememb...>:Test.kt")
+              %composer.startReplaceableGroup(<>)
+              sourceInformation(%composer, "C(app)<rememb...>:Test.kt")
               val a = if (x) {
                 %composer.startReplaceableGroup(<>)
                 val tmp0_group = %composer.cache(false) {
@@ -105,7 +106,8 @@
         """
             @Composable
             fun <T> loadResourceInternal(key: String, pendingResource: T?, failedResource: T?, %composer: Composer?, %changed: Int, %default: Int): Boolean {
-              %composer.startReplaceableGroup(<>, "C(loadResourceInternal)P(1,2):Test.kt")
+              %composer.startReplaceableGroup(<>)
+              sourceInformation(%composer, "C(loadResourceInternal)P(1,2):Test.kt")
               if (%default and 0b0010 !== 0) {
                 pendingResource = null
               }
@@ -147,7 +149,8 @@
         """
             @Composable
             fun test1(x: KnownStable, %composer: Composer?, %changed: Int) {
-              %composer = %composer.startRestartGroup(<>, "C(test1):Test.kt")
+              %composer = %composer.startRestartGroup(<>)
+              sourceInformation(%composer, "C(test1):Test.kt")
               val %dirty = %changed
               if (%changed and 0b1110 === 0) {
                 %dirty = %dirty or if (%composer.changed(x)) 0b0100 else 0b0010
@@ -166,7 +169,8 @@
             }
             @Composable
             fun test2(x: KnownUnstable, %composer: Composer?, %changed: Int) {
-              %composer = %composer.startRestartGroup(<>, "C(test2):Test.kt")
+              %composer = %composer.startRestartGroup(<>)
+              sourceInformation(%composer, "C(test2):Test.kt")
               %composer.cache(%composer.changed(x)) {
                 val tmp0_return = 1
                 tmp0_return
@@ -177,7 +181,8 @@
             }
             @Composable
             fun test3(x: Uncertain, %composer: Composer?, %changed: Int) {
-              %composer = %composer.startRestartGroup(<>, "C(test3):Test.kt")
+              %composer = %composer.startRestartGroup(<>)
+              sourceInformation(%composer, "C(test3):Test.kt")
               val %dirty = %changed
               if (%changed and 0b1110 === 0) {
                 %dirty = %dirty or if (%composer.changed(x)) 0b0100 else 0b0010
@@ -227,7 +232,8 @@
             @Composable
             @NonRestartableComposable
             fun test1(x: KnownStable, %composer: Composer?, %changed: Int) {
-              %composer.startReplaceableGroup(<>, "C(test1):Test.kt")
+              %composer.startReplaceableGroup(<>)
+              sourceInformation(%composer, "C(test1):Test.kt")
               %composer.cache(%changed and 0b1110 xor 0b0110 > 4 && %composer.changed(x) || %changed and 0b0110 === 0b0100) {
                 val tmp0_return = 1
                 tmp0_return
@@ -237,7 +243,8 @@
             @Composable
             @NonRestartableComposable
             fun test2(x: KnownUnstable, %composer: Composer?, %changed: Int) {
-              %composer.startReplaceableGroup(<>, "C(test2):Test.kt")
+              %composer.startReplaceableGroup(<>)
+              sourceInformation(%composer, "C(test2):Test.kt")
               %composer.cache(%composer.changed(x)) {
                 val tmp0_return = 1
                 tmp0_return
@@ -247,7 +254,8 @@
             @Composable
             @NonRestartableComposable
             fun test3(x: Uncertain, %composer: Composer?, %changed: Int) {
-              %composer.startReplaceableGroup(<>, "C(test3):Test.kt")
+              %composer.startReplaceableGroup(<>)
+              sourceInformation(%composer, "C(test3):Test.kt")
               %composer.cache(%changed and 0b1110 xor 0b0110 > 4 && %composer.changed(x) || %changed and 0b0110 === 0b0100) {
                 val tmp0_return = 1
                 tmp0_return
@@ -269,7 +277,8 @@
         """
             @Composable
             fun rememberFoo(a: Int, b: Int, %composer: Composer?, %changed: Int): Foo {
-              %composer.startReplaceableGroup(<>, "C(rememberFoo):Test.kt")
+              %composer.startReplaceableGroup(<>)
+              sourceInformation(%composer, "C(rememberFoo):Test.kt")
               val tmp0 = %composer.cache(%changed and 0b1110 xor 0b0110 > 4 && %composer.changed(a) || %changed and 0b0110 === 0b0100 or %changed and 0b01110000 xor 0b00110000 > 32 && %composer.changed(b) || %changed and 0b00110000 === 0b00100000) {
                 val tmp0_return = Foo(a, b)
                 tmp0_return
@@ -298,7 +307,8 @@
         """
             @Composable
             fun Test(%composer: Composer?, %changed: Int) {
-              %composer = %composer.startRestartGroup(<>, "C(Test)<A()>,<rememb...>:Test.kt")
+              %composer = %composer.startRestartGroup(<>)
+              sourceInformation(%composer, "C(Test)<A()>,<rememb...>:Test.kt")
               if (%changed !== 0 || !%composer.skipping) {
                 val foo = %composer.cache(false) {
                   val tmp0_return = Foo()
@@ -340,7 +350,8 @@
         """
             @Composable
             fun Test(%composer: Composer?, %changed: Int) {
-              %composer = %composer.startRestartGroup(<>, "C(Test):Test.kt")
+              %composer = %composer.startRestartGroup(<>)
+              sourceInformation(%composer, "C(Test):Test.kt")
               if (%changed !== 0 || !%composer.skipping) {
                 val a = someInt()
                 val b = someInt()
@@ -373,7 +384,8 @@
         """
             @Composable
             fun Test(%composer: Composer?, %changed: Int) {
-              %composer = %composer.startRestartGroup(<>, "C(Test)<CInt()...>,<rememb...>:Test.kt")
+              %composer = %composer.startRestartGroup(<>)
+              sourceInformation(%composer, "C(Test)<CInt()...>,<rememb...>:Test.kt")
               if (%changed !== 0 || !%composer.skipping) {
                 val foo = remember(CInt(%composer, 0), {
                   val tmp0_return = Foo()
@@ -408,7 +420,8 @@
         """
             @Composable
             fun Test(%composer: Composer?, %changed: Int) {
-              %composer = %composer.startRestartGroup(<>, "C(Test)<curren...>:Test.kt")
+              %composer = %composer.startRestartGroup(<>)
+              sourceInformation(%composer, "C(Test)<curren...>:Test.kt")
               if (%changed !== 0 || !%composer.skipping) {
                 val bar = compositionLocalBar.current
                 val foo = %composer.cache(%composer.changed(bar)) {
@@ -443,7 +456,8 @@
         """
             @Composable
             fun Test(%composer: Composer?, %changed: Int) {
-              %composer = %composer.startRestartGroup(<>, "C(Test)<curren...>:Test.kt")
+              %composer = %composer.startRestartGroup(<>)
+              sourceInformation(%composer, "C(Test)<curren...>:Test.kt")
               if (%changed !== 0 || !%composer.skipping) {
                 val foo = %composer.cache(%composer.changed(compositionLocalBar.current)) {
                   val tmp0_return = Foo()
@@ -475,7 +489,8 @@
         """
             @Composable
             fun Test(%composer: Composer?, %changed: Int) {
-              %composer = %composer.startRestartGroup(<>, "C(Test)<A()>,<rememb...>:Test.kt")
+              %composer = %composer.startRestartGroup(<>)
+              sourceInformation(%composer, "C(Test)<A()>,<rememb...>:Test.kt")
               if (%changed !== 0 || !%composer.skipping) {
                 A(%composer, 0)
                 val foo = remember({
@@ -510,7 +525,8 @@
         """
             @Composable
             fun Test(condition: Boolean, %composer: Composer?, %changed: Int) {
-              %composer = %composer.startRestartGroup(<>, "C(Test)<A()>:Test.kt")
+              %composer = %composer.startRestartGroup(<>)
+              sourceInformation(%composer, "C(Test)<A()>:Test.kt")
               val %dirty = %changed
               if (%changed and 0b1110 === 0) {
                 %dirty = %dirty or if (%composer.changed(condition)) 0b0100 else 0b0010
@@ -556,14 +572,16 @@
         """
             @Composable
             fun Test(condition: Boolean, %composer: Composer?, %changed: Int) {
-              %composer = %composer.startRestartGroup(<>, "C(Test):Test.kt")
+              %composer = %composer.startRestartGroup(<>)
+              sourceInformation(%composer, "C(Test):Test.kt")
               val %dirty = %changed
               if (%changed and 0b1110 === 0) {
                 %dirty = %dirty or if (%composer.changed(condition)) 0b0100 else 0b0010
               }
               if (%dirty and 0b1011 xor 0b0010 !== 0 || !%composer.skipping) {
                 if (condition) {
-                  %composer.startReplaceableGroup(<>, "<A()>,<rememb...>")
+                  %composer.startReplaceableGroup(<>)
+                  sourceInformation(%composer, "<A()>,<rememb...>")
                   A(%composer, 0)
                   val foo = remember({
                     val tmp0_return = Foo()
@@ -602,7 +620,8 @@
         """
             @Composable
             fun Test(items: List<Int>, %composer: Composer?, %changed: Int) {
-              %composer = %composer.startRestartGroup(<>, "C(Test)*<rememb...>:Test.kt")
+              %composer = %composer.startRestartGroup(<>)
+              sourceInformation(%composer, "C(Test)*<rememb...>:Test.kt")
               val tmp0_iterator = items.iterator()
               while (tmp0_iterator.hasNext()) {
                 val item = tmp0_iterator.next()
@@ -640,7 +659,8 @@
         """
             @Composable
             fun Test(items: List<Int>, %composer: Composer?, %changed: Int) {
-              %composer = %composer.startRestartGroup(<>, "C(Test)*<rememb...>,<A()>:Test.kt")
+              %composer = %composer.startRestartGroup(<>)
+              sourceInformation(%composer, "C(Test)*<rememb...>,<A()>:Test.kt")
               val tmp0_iterator = items.iterator()
               while (tmp0_iterator.hasNext()) {
                 val item = tmp0_iterator.next()
@@ -674,7 +694,8 @@
         """
             @Composable
             fun Test(items: List<Int>, %composer: Composer?, %changed: Int) {
-              %composer = %composer.startRestartGroup(<>, "C(Test):Test.kt")
+              %composer = %composer.startRestartGroup(<>)
+              sourceInformation(%composer, "C(Test):Test.kt")
               val foo = %composer.cache(false) {
                 val tmp0_return = Foo()
                 tmp0_return
@@ -702,7 +723,8 @@
         """
             @Composable
             fun Test(a: Int, b: Int, c: Bar, d: Boolean, %composer: Composer?, %changed: Int) {
-              %composer = %composer.startRestartGroup(<>, "C(Test):Test.kt")
+              %composer = %composer.startRestartGroup(<>)
+              sourceInformation(%composer, "C(Test):Test.kt")
               val %dirty = %changed
               if (%changed and 0b1110 === 0) {
                 %dirty = %dirty or if (%composer.changed(a)) 0b0100 else 0b0010
@@ -746,7 +768,8 @@
         """
             @Composable
             fun Test(items: Array<Bar>, %composer: Composer?, %changed: Int) {
-              %composer = %composer.startRestartGroup(<>, "C(Test)<rememb...>:Test.kt")
+              %composer = %composer.startRestartGroup(<>)
+              sourceInformation(%composer, "C(Test)<rememb...>:Test.kt")
               val foo = remember(*items, {
                 val tmp0_return = Foo()
                 tmp0_return
@@ -774,7 +797,8 @@
         """
             @Composable
             fun Test(inlineInt: InlineInt, %composer: Composer?, %changed: Int) {
-              %composer = %composer.startRestartGroup(<>, "C(Test)P(0:InlineInt):Test.kt")
+              %composer = %composer.startRestartGroup(<>)
+              sourceInformation(%composer, "C(Test)P(0:InlineInt):Test.kt")
               val %dirty = %changed
               if (%changed and 0b1110 === 0) {
                 %dirty = %dirty or if (%composer.changed(inlineInt.value)) 0b0100 else 0b0010
@@ -815,7 +839,8 @@
         """
             @Composable
             fun Test(%composer: Composer?, %changed: Int) {
-              %composer = %composer.startRestartGroup(<>, "C(Test):Test.kt")
+              %composer = %composer.startRestartGroup(<>)
+              sourceInformation(%composer, "C(Test):Test.kt")
               if (%changed !== 0 || !%composer.skipping) {
                 val a = someInt()
                 val b = someInt()
@@ -855,7 +880,8 @@
         """
             @Composable
             fun Test(a: Int, %composer: Composer?, %changed: Int) {
-              %composer = %composer.startRestartGroup(<>, "C(Test):Test.kt")
+              %composer = %composer.startRestartGroup(<>)
+              sourceInformation(%composer, "C(Test):Test.kt")
               val %dirty = %changed
               if (%changed and 0b1110 === 0) {
                 %dirty = %dirty or if (%composer.changed(a)) 0b0100 else 0b0010
@@ -892,7 +918,8 @@
         """
             @Composable
             fun Test(a: Int, %composer: Composer?, %changed: Int): Foo {
-              %composer.startReplaceableGroup(<>, "C(Test):Test.kt")
+              %composer.startReplaceableGroup(<>)
+              sourceInformation(%composer, "C(Test):Test.kt")
               val b = someInt()
               val tmp0 = %composer.cache(%changed and 0b1110 xor 0b0110 > 4 && %composer.changed(a) || %changed and 0b0110 === 0b0100 or %composer.changed(b)) {
                 val tmp0_return = Foo(a, b)
@@ -921,7 +948,8 @@
         """
             @Composable
             fun Test(a: Int, %composer: Composer?, %changed: Int, %default: Int) {
-              %composer = %composer.startRestartGroup(<>, "C(Test)<rememb...>:Test.kt")
+              %composer = %composer.startRestartGroup(<>)
+              sourceInformation(%composer, "C(Test)<rememb...>:Test.kt")
               val %dirty = %changed
               if (%changed and 0b1110 === 0) {
                 %dirty = %dirty or if (%default and 0b0001 === 0 && %composer.changed(a)) 0b0100 else 0b0010
diff --git a/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/StabilityPropagationTransformTests.kt b/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/StabilityPropagationTransformTests.kt
index b434332..a056dcd 100644
--- a/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/StabilityPropagationTransformTests.kt
+++ b/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/StabilityPropagationTransformTests.kt
@@ -61,7 +61,8 @@
         """
         @Composable
         fun Test(x: Foo, %composer: Composer?, %changed: Int) {
-          %composer = %composer.startRestartGroup(<>, "C(Test)<A(x)>,<A(Foo(...>,<rememb...>,<A(reme...>:Test.kt")
+          %composer = %composer.startRestartGroup(<>)
+          sourceInformation(%composer, "C(Test)<A(x)>,<A(Foo(...>,<rememb...>,<A(reme...>:Test.kt")
           val %dirty = %changed
           if (%changed and 0b1110 === 0) {
             %dirty = %dirty or if (%composer.changed(x)) 0b0100 else 0b0010
@@ -102,7 +103,8 @@
         """
             @Composable
             fun Test(x: Foo, %composer: Composer?, %changed: Int) {
-              %composer = %composer.startRestartGroup(<>, "C(Test)<A(x)>,<A(Foo(...>,<rememb...>,<A(reme...>:Test.kt")
+              %composer = %composer.startRestartGroup(<>)
+              sourceInformation(%composer, "C(Test)<A(x)>,<A(Foo(...>,<rememb...>,<A(reme...>:Test.kt")
               A(x, %composer, 0b1000)
               A(Foo(0), %composer, 0b1000)
               A(remember({
@@ -130,7 +132,8 @@
         """
             @Composable
             fun Example(%composer: Composer?, %changed: Int) {
-              %composer = %composer.startRestartGroup(<>, "C(Example)<A(list...>:Test.kt")
+              %composer = %composer.startRestartGroup(<>)
+              sourceInformation(%composer, "C(Example)<A(list...>:Test.kt")
               if (%changed !== 0 || !%composer.skipping) {
                 A(listOf("a"), %composer, 0)
               } else {
diff --git a/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/KtxNameConventions.kt b/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/KtxNameConventions.kt
index 4b9c724..4db291b 100644
--- a/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/KtxNameConventions.kt
+++ b/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/KtxNameConventions.kt
@@ -12,4 +12,7 @@
     val STARTRESTARTGROUP = Name.identifier("startRestartGroup")
     val ENDRESTARTGROUP = Name.identifier("endRestartGroup")
     val UPDATE_SCOPE = Name.identifier("updateScope")
+    val SOURCEINFORMATION = "sourceInformation"
+    val SOURCEINFORMATIONMARKERSTART = "sourceInformationMarkerStart"
+    val SOURCEINFORMATIONMARKEREND = "sourceInformationMarkerEnd"
 }
\ No newline at end of file
diff --git a/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/ComposableFunctionBodyTransformer.kt b/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/ComposableFunctionBodyTransformer.kt
index 56ec08a..c448eee 100644
--- a/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/ComposableFunctionBodyTransformer.kt
+++ b/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/ComposableFunctionBodyTransformer.kt
@@ -515,13 +515,6 @@
             }
     }
 
-    private val startReplaceableSourceFunction by guardedLazy {
-        composerIrClass.functions
-            .first {
-                it.name.identifier == "startReplaceableGroup" && it.valueParameters.size == 2
-            }
-    }
-
     private val endReplaceableFunction by guardedLazy {
         composerIrClass.functions
             .first {
@@ -550,13 +543,6 @@
             }
     }
 
-    private val startMovableSourceFunction by guardedLazy {
-        composerIrClass.functions
-            .first {
-                it.name.identifier == "startMovableGroup" && it.valueParameters.size == 3
-            }
-    }
-
     private val endMovableFunction by guardedLazy {
         composerIrClass.functions
             .first {
@@ -572,13 +558,6 @@
             }
     }
 
-    private val startRestartGroupSourceFunction by guardedLazy {
-        composerIrClass.functions
-            .first {
-                it.name == KtxNameConventions.STARTRESTARTGROUP && it.valueParameters.size == 2
-            }
-    }
-
     private val endRestartGroupFunction by guardedLazy {
         composerIrClass
             .functions
@@ -587,6 +566,24 @@
             }
     }
 
+    private val sourceInformationFunction by guardedLazy {
+        getTopLevelFunctions(
+            ComposeFqNames.fqNameFor(KtxNameConventions.SOURCEINFORMATION)
+        ).map { it.owner }.first()
+    }
+
+    private val sourceInformationMarkerStartFunction by guardedLazy {
+        getTopLevelFunctions(
+            ComposeFqNames.fqNameFor(KtxNameConventions.SOURCEINFORMATIONMARKERSTART)
+        ).map { it.owner }.first()
+    }
+
+    private val sourceInformationMarkerEndFunction by guardedLazy {
+        getTopLevelFunctions(
+            ComposeFqNames.fqNameFor(KtxNameConventions.SOURCEINFORMATIONMARKEREND)
+        ).map { it.owner }.first()
+    }
+
     private val IrType.arguments: List<IrTypeArgument>
         get() = (this as? IrSimpleType)?.arguments.orEmpty()
 
@@ -768,7 +765,7 @@
 
     @OptIn(ObsoleteDescriptorBasedAPI::class)
     private fun IrFunction.shouldElideGroups(): Boolean {
-        return (!collectSourceInformation && descriptor.hasReadonlyComposableAnnotation()) ||
+        return descriptor.hasReadonlyComposableAnnotation() ||
             descriptor.hasExplicitGroupsAnnotation()
     }
 
@@ -822,17 +819,31 @@
             body.startOffset,
             body.endOffset,
             listOfNotNull(
-                if (!elideGroups)
-                    irStartReplaceableGroup(
-                        body,
-                        scope,
-                        declaration.irSourceKey()
-                    )
-                else
-                    null,
+                when {
+                    !elideGroups ->
+                        irStartReplaceableGroup(
+                            body,
+                            scope,
+                            declaration.irSourceKey()
+                        )
+                    collectSourceInformation &&
+                        !declaration.descriptor.hasExplicitGroupsAnnotation() ->
+                        irSourceInformationMarkerStart(
+                            body,
+                            scope,
+                            declaration.irSourceKey()
+                        )
+                    else -> null
+                },
                 *bodyPreamble.statements.toTypedArray(),
                 *transformed.statements.toTypedArray(),
-                if (!elideGroups) irEndReplaceableGroup() else null,
+                when {
+                    !elideGroups -> irEndReplaceableGroup()
+                    collectSourceInformation &&
+                        !declaration.descriptor.hasExplicitGroupsAnnotation() ->
+                        irSourceInformationMarkerEnd(body)
+                    else -> null
+                },
                 returnVar?.let { irReturn(declaration.symbol, irGet(it)) }
             )
         )
@@ -855,9 +866,15 @@
         // no group, since composableLambda should already create one
         // no default logic
         val body = declaration.body!!
+        val sourceInformationPreamble = mutableStatementContainer()
         val skipPreamble = mutableStatementContainer()
         val bodyPreamble = mutableStatementContainer()
 
+        // First generate the source information call
+        if (collectSourceInformation && !scope.isInlinedLambda) {
+            sourceInformationPreamble.statements.add(irSourceInformation(scope))
+        }
+
         // we start off assuming that we *can* skip execution of the function
         var canSkipExecution = declaration.returnType.isUnit() &&
             scope.allTrackedParams.none { stabilityOf(it.type).knownUnstable() }
@@ -927,6 +944,7 @@
                     if (collectSourceInformation && scope.isInlinedLambda)
                         irStartReplaceableGroup(body, scope)
                     else null,
+                    *sourceInformationPreamble.statements.toTypedArray(),
                     *skipPreamble.statements.toTypedArray(),
                     *bodyPreamble.statements.toTypedArray(),
                     transformedBody,
@@ -941,6 +959,7 @@
                 body.startOffset,
                 body.endOffset,
                 listOfNotNull(
+                    *sourceInformationPreamble.statements.toTypedArray(),
                     *skipPreamble.statements.toTypedArray(),
                     *bodyPreamble.statements.toTypedArray(),
                     transformed,
@@ -1740,16 +1759,61 @@
         scope: Scope.BlockScope,
         key: IrExpression = element.irSourceKey()
     ): IrExpression {
-        val startDescriptor = if (scope.hasSourceInformation)
-            startReplaceableSourceFunction else startReplaceableFunction
-        return irMethodCall(
-            irCurrentComposer(),
-            startDescriptor
+        return irWithSourceInformation(
+            irMethodCall(
+                irCurrentComposer(),
+                startReplaceableFunction
+            ).also {
+                it.putValueArgument(0, key)
+            },
+            scope
+        )
+    }
+
+    private fun irWithSourceInformation(
+        startGroup: IrExpression,
+        scope: Scope.BlockScope
+    ): IrExpression {
+        return if (scope.hasSourceInformation) {
+            irBlock(statements = listOf(startGroup, irSourceInformation(scope)))
+        } else startGroup
+    }
+
+    private fun irSourceInformation(scope: Scope.BlockScope): IrExpression {
+        val sourceInformation = irCall(
+            sourceInformationFunction
         ).also {
-            it.putValueArgument(0, key)
-            if (scope.hasSourceInformation) {
-                recordSourceParameter(it, 1, scope)
-            }
+            it.putValueArgument(0, irCurrentComposer())
+        }
+        recordSourceParameter(sourceInformation, 1, scope)
+        return sourceInformation
+    }
+
+    private fun irSourceInformationMarkerStart(
+        element: IrElement,
+        scope: Scope.BlockScope,
+        key: IrExpression = element.irSourceKey(),
+    ): IrExpression {
+        return irCall(
+            sourceInformationMarkerStartFunction,
+            element.startOffset,
+            element.endOffset
+        ).also {
+            it.putValueArgument(0, irCurrentComposer())
+            it.putValueArgument(1, key)
+            recordSourceParameter(it, 2, scope)
+        }
+    }
+
+    private fun irSourceInformationMarkerEnd(
+        element: IrElement,
+    ): IrExpression {
+        return irCall(
+            sourceInformationMarkerEndFunction,
+            element.startOffset,
+            element.endOffset
+        ).also {
+            it.putValueArgument(0, irCurrentComposer())
         }
     }
 
@@ -1767,21 +1831,19 @@
         scope: Scope.BlockScope,
         key: IrExpression = element.irSourceKey()
     ): IrExpression {
-        val startDescriptor = if (scope.hasSourceInformation)
-            startRestartGroupSourceFunction else startRestartGroupFunction
-        return irSet(
-            nearestComposer(),
-            irMethodCall(
-                irCurrentComposer(),
-                startDescriptor,
-                element.startOffset,
-                element.endOffset
-            ).also {
-                it.putValueArgument(0, key)
-                if (scope.hasSourceInformation) {
-                    recordSourceParameter(it, 1, scope)
+        return irWithSourceInformation(
+            irSet(
+                nearestComposer(),
+                irMethodCall(
+                    irCurrentComposer(),
+                    startRestartGroupFunction,
+                    element.startOffset,
+                    element.endOffset
+                ).also {
+                    it.putValueArgument(0, key)
                 }
-            }
+            ),
+            scope
         )
     }
 
@@ -1854,23 +1916,18 @@
         joinedData: IrExpression,
         scope: Scope.BlockScope
     ): IrExpression {
-        val startFunction = if (scope.hasSourceInformation) {
-            startMovableSourceFunction
-        } else {
-            startMovableFunction
-        }
-        return irMethodCall(
-            irCurrentComposer(),
-            startFunction,
-            element.startOffset,
-            element.endOffset
-        ).also {
-            it.putValueArgument(0, element.irSourceKey())
-            it.putValueArgument(1, joinedData)
-            if (scope.hasSourceInformation) {
-                recordSourceParameter(it, 2, scope)
-            }
-        }
+        return irWithSourceInformation(
+            irMethodCall(
+                irCurrentComposer(),
+                startMovableFunction,
+                element.startOffset,
+                element.endOffset
+            ).also {
+                it.putValueArgument(0, element.irSourceKey())
+                it.putValueArgument(1, joinedData)
+            },
+            scope
+        )
     }
 
     private fun irEndMovableGroup(): IrExpression {
@@ -2372,17 +2429,6 @@
                     expression
                 }
             }
-            collectSourceInformation &&
-                expression.symbol.descriptor.fqNameSafe == ComposeFqNames.composableLambda -> {
-                // For calls to `composableLambda` we introduce a scope to collect the source
-                // locations on the top level of the lambda as the startRestartGroup is in the
-                // composable lambda wrapper.
-                val composableLambdaScope = withScope(Scope.ComposableLambdaScope()) {
-                    expression.transformChildrenVoid()
-                }
-                recordSourceParameter(expression, 3, composableLambdaScope)
-                return expression
-            }
             expression.isComposableSingletonGetter() -> {
                 // This looks like `ComposableSingletonClass.lambda-123`, which is a static/saved
                 // call of composableLambdaInstance. We want to transform the property here now
@@ -2393,18 +2439,6 @@
                 property?.transformChildrenVoid()
                 return super.visitCall(expression)
             }
-            collectSourceInformation &&
-                expression.symbol.descriptor.fqNameSafe ==
-                ComposeFqNames.composableLambdaInstance -> {
-                // For calls to `composableLambdaInstance` that are not singletons we introduce a
-                // scope to collect the source locations on the top level of the lambda as the
-                // startRestartGroup is in the composable lambda wrapper.
-                val composableLambdaScope = withScope(Scope.ComposableLambdaScope()) {
-                    expression.transformChildrenVoid()
-                }
-                recordSourceParameter(expression, 2, composableLambdaScope)
-                return expression
-            }
             else -> return super.visitCall(expression)
         }
     }
@@ -3329,13 +3363,9 @@
 
             override fun calculateSourceInfo(sourceInformationEnabled: Boolean): String? =
                 if (sourceInformationEnabled) {
-                    if (function.isLambda() && !isInlinedLambda) {
-                        super.calculateSourceInfo(sourceInformationEnabled)
-                    } else {
-                        "${callInformation()}${parameterInformation()}${
-                        super.calculateSourceInfo(sourceInformationEnabled) ?: ""
-                        }:${sourceFileInformation()}"
-                    }
+                    "${callInformation()}${parameterInformation()}${
+                    super.calculateSourceInfo(sourceInformationEnabled) ?: ""
+                    }:${sourceFileInformation()}"
                 } else {
                     if (function.visibility.isPublicAPI) {
                         "${callInformation()}${parameterInformation()}"
diff --git a/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/ComposerLambdaMemoization.kt b/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/ComposerLambdaMemoization.kt
index 0bc3dcc..131e734 100644
--- a/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/ComposerLambdaMemoization.kt
+++ b/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/ComposerLambdaMemoization.kt
@@ -48,7 +48,6 @@
 import org.jetbrains.kotlin.ir.builders.irGet
 import org.jetbrains.kotlin.ir.builders.irGetField
 import org.jetbrains.kotlin.ir.builders.irInt
-import org.jetbrains.kotlin.ir.builders.irNull
 import org.jetbrains.kotlin.ir.builders.irReturn
 import org.jetbrains.kotlin.ir.builders.irTemporary
 import org.jetbrains.kotlin.ir.declarations.IrAttributeContainer
@@ -687,9 +686,6 @@
             val shouldBeTracked = collector.captures.isNotEmpty()
             putValueArgument(index++, irBuilder.irBoolean(shouldBeTracked))
 
-            // sourceInformation parameter
-            putValueArgument(index++, irBuilder.irNull())
-
             // ComposableLambdaN requires the arity
             if (useComposableLambdaN) {
                 // arity parameter
diff --git a/compose/foundation/foundation-layout/src/androidAndroidTest/kotlin/androidx/compose/foundation/layout/RowColumnTest.kt b/compose/foundation/foundation-layout/src/androidAndroidTest/kotlin/androidx/compose/foundation/layout/RowColumnTest.kt
index 66665ef..26d96f2 100644
--- a/compose/foundation/foundation-layout/src/androidAndroidTest/kotlin/androidx/compose/foundation/layout/RowColumnTest.kt
+++ b/compose/foundation/foundation-layout/src/androidAndroidTest/kotlin/androidx/compose/foundation/layout/RowColumnTest.kt
@@ -1762,6 +1762,35 @@
         assertTrue(latch.await(1, TimeUnit.SECONDS))
         assertEquals(size, rowWidth)
     }
+
+    @Test
+    fun testRow_includesSpacing_withWeightChildren() = with(density) {
+        val rowWidth = 40
+        val space = 8
+        val latch = CountDownLatch(2)
+        show {
+            Row(
+                modifier = Modifier.widthIn(max = rowWidth.toDp()),
+                horizontalArrangement = Arrangement.spacedBy(space.toDp())
+            ) {
+                Box(
+                    Modifier.weight(1f).onGloballyPositioned {
+                        assertEquals((rowWidth - space) / 2, it.size.width)
+                        assertEquals(0, it.positionInRoot().x.toInt())
+                        latch.countDown()
+                    }
+                )
+                Box(
+                    Modifier.weight(1f).onGloballyPositioned {
+                        assertEquals((rowWidth - space) / 2, it.size.width)
+                        assertEquals((rowWidth - space) / 2 + space, it.positionInRoot().x.toInt())
+                        latch.countDown()
+                    }
+                )
+            }
+        }
+        assertTrue(latch.await(1, TimeUnit.SECONDS))
+    }
     // endregion
 
     // region Size tests in Column
@@ -2271,6 +2300,38 @@
         assertTrue(latch.await(1, TimeUnit.SECONDS))
         assertEquals(size, columnHeight)
     }
+
+    @Test
+    fun testColumn_includesSpacing_withWeightChildren() = with(density) {
+        val columnHeight = 40
+        val space = 8
+        val latch = CountDownLatch(2)
+        show {
+            Column(
+                modifier = Modifier.height(columnHeight.toDp()),
+                verticalArrangement = Arrangement.spacedBy(space.toDp())
+            ) {
+                Box(
+                    Modifier.weight(1f).onGloballyPositioned {
+                        assertEquals((columnHeight - space) / 2, it.size.height)
+                        assertEquals(0, it.positionInRoot().y.toInt())
+                        latch.countDown()
+                    }
+                )
+                Box(
+                    Modifier.weight(1f).onGloballyPositioned {
+                        assertEquals((columnHeight - space) / 2, it.size.height)
+                        assertEquals(
+                            (columnHeight - space) / 2 + space,
+                            it.positionInRoot().y.toInt()
+                        )
+                        latch.countDown()
+                    }
+                )
+            }
+        }
+        assertTrue(latch.await(1, TimeUnit.SECONDS))
+    }
     // endregion
 
     // region Main axis alignment tests in Row
diff --git a/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/Arrangement.kt b/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/Arrangement.kt
index 9728409..0a5320d 100644
--- a/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/Arrangement.kt
+++ b/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/Arrangement.kt
@@ -563,12 +563,12 @@
             outPositions: IntArray
         ) {
             if (sizes.isEmpty()) return
-            val spacePx = with(density) { space.roundToPx() }
+            val spacePx = space.roundToPx()
 
             var occupied = 0
             var lastSpace = 0
-            if (layoutDirection == LayoutDirection.Rtl && rtlMirror) sizes.reverse()
-            sizes.forEachIndexed { index, it ->
+            val reversed = rtlMirror && layoutDirection == LayoutDirection.Rtl
+            sizes.forEachIndexed(reversed) { index, it ->
                 outPositions[index] = min(occupied, totalSize - it)
                 lastSpace = min(spacePx, totalSize - outPositions[index] - it)
                 occupied = outPositions[index] + it + lastSpace
@@ -581,8 +581,6 @@
                     outPositions[index] += groupPosition
                 }
             }
-
-            if (layoutDirection == LayoutDirection.Rtl && rtlMirror) outPositions.reverse()
         }
 
         override fun Density.arrange(
diff --git a/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/RowColumnImpl.kt b/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/RowColumnImpl.kt
index e20038e..83a2f34 100644
--- a/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/RowColumnImpl.kt
+++ b/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/RowColumnImpl.kt
@@ -162,6 +162,8 @@
                         placeables[i] = placeable
                     }
                 }
+                weightedSpace = (weightedSpace + arrangementSpacingPx * (weightChildrenCount - 1))
+                    .coerceAtMost(constraints.mainAxisMax - fixedSpace)
             }
 
             var beforeCrossAxisAlignmentLine = 0
diff --git a/compose/foundation/foundation/api/1.0.0-beta07.txt b/compose/foundation/foundation/api/1.0.0-beta07.txt
index 03f29a3..76355d9 100644
--- a/compose/foundation/foundation/api/1.0.0-beta07.txt
+++ b/compose/foundation/foundation/api/1.0.0-beta07.txt
@@ -381,6 +381,9 @@
   public final class LazyListMeasureKt {
   }
 
+  public final class LazyListPrefetcher_androidKt {
+  }
+
   @androidx.compose.foundation.lazy.LazyScopeMarker public interface LazyListScope {
     method public void item(optional Object? key, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.lazy.LazyItemScope,kotlin.Unit> content);
     method public void items(int count, optional kotlin.jvm.functions.Function1<? super java.lang.Integer,?>? key, kotlin.jvm.functions.Function2<? super androidx.compose.foundation.lazy.LazyItemScope,? super java.lang.Integer,kotlin.Unit> itemContent);
diff --git a/compose/foundation/foundation/api/current.txt b/compose/foundation/foundation/api/current.txt
index 03f29a3..76355d9 100644
--- a/compose/foundation/foundation/api/current.txt
+++ b/compose/foundation/foundation/api/current.txt
@@ -381,6 +381,9 @@
   public final class LazyListMeasureKt {
   }
 
+  public final class LazyListPrefetcher_androidKt {
+  }
+
   @androidx.compose.foundation.lazy.LazyScopeMarker public interface LazyListScope {
     method public void item(optional Object? key, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.lazy.LazyItemScope,kotlin.Unit> content);
     method public void items(int count, optional kotlin.jvm.functions.Function1<? super java.lang.Integer,?>? key, kotlin.jvm.functions.Function2<? super androidx.compose.foundation.lazy.LazyItemScope,? super java.lang.Integer,kotlin.Unit> itemContent);
diff --git a/compose/foundation/foundation/api/public_plus_experimental_1.0.0-beta07.txt b/compose/foundation/foundation/api/public_plus_experimental_1.0.0-beta07.txt
index 4085bab..4bd7f753 100644
--- a/compose/foundation/foundation/api/public_plus_experimental_1.0.0-beta07.txt
+++ b/compose/foundation/foundation/api/public_plus_experimental_1.0.0-beta07.txt
@@ -413,6 +413,9 @@
   public final class LazyListMeasureKt {
   }
 
+  public final class LazyListPrefetcher_androidKt {
+  }
+
   @androidx.compose.foundation.lazy.LazyScopeMarker public interface LazyListScope {
     method public void item(optional Object? key, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.lazy.LazyItemScope,kotlin.Unit> content);
     method public void items(int count, optional kotlin.jvm.functions.Function1<? super java.lang.Integer,?>? key, kotlin.jvm.functions.Function2<? super androidx.compose.foundation.lazy.LazyItemScope,? super java.lang.Integer,kotlin.Unit> itemContent);
diff --git a/compose/foundation/foundation/api/public_plus_experimental_current.txt b/compose/foundation/foundation/api/public_plus_experimental_current.txt
index 4085bab..4bd7f753 100644
--- a/compose/foundation/foundation/api/public_plus_experimental_current.txt
+++ b/compose/foundation/foundation/api/public_plus_experimental_current.txt
@@ -413,6 +413,9 @@
   public final class LazyListMeasureKt {
   }
 
+  public final class LazyListPrefetcher_androidKt {
+  }
+
   @androidx.compose.foundation.lazy.LazyScopeMarker public interface LazyListScope {
     method public void item(optional Object? key, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.lazy.LazyItemScope,kotlin.Unit> content);
     method public void items(int count, optional kotlin.jvm.functions.Function1<? super java.lang.Integer,?>? key, kotlin.jvm.functions.Function2<? super androidx.compose.foundation.lazy.LazyItemScope,? super java.lang.Integer,kotlin.Unit> itemContent);
diff --git a/compose/foundation/foundation/api/restricted_1.0.0-beta07.txt b/compose/foundation/foundation/api/restricted_1.0.0-beta07.txt
index 03f29a3..76355d9 100644
--- a/compose/foundation/foundation/api/restricted_1.0.0-beta07.txt
+++ b/compose/foundation/foundation/api/restricted_1.0.0-beta07.txt
@@ -381,6 +381,9 @@
   public final class LazyListMeasureKt {
   }
 
+  public final class LazyListPrefetcher_androidKt {
+  }
+
   @androidx.compose.foundation.lazy.LazyScopeMarker public interface LazyListScope {
     method public void item(optional Object? key, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.lazy.LazyItemScope,kotlin.Unit> content);
     method public void items(int count, optional kotlin.jvm.functions.Function1<? super java.lang.Integer,?>? key, kotlin.jvm.functions.Function2<? super androidx.compose.foundation.lazy.LazyItemScope,? super java.lang.Integer,kotlin.Unit> itemContent);
diff --git a/compose/foundation/foundation/api/restricted_current.txt b/compose/foundation/foundation/api/restricted_current.txt
index 03f29a3..76355d9 100644
--- a/compose/foundation/foundation/api/restricted_current.txt
+++ b/compose/foundation/foundation/api/restricted_current.txt
@@ -381,6 +381,9 @@
   public final class LazyListMeasureKt {
   }
 
+  public final class LazyListPrefetcher_androidKt {
+  }
+
   @androidx.compose.foundation.lazy.LazyScopeMarker public interface LazyListScope {
     method public void item(optional Object? key, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.lazy.LazyItemScope,kotlin.Unit> content);
     method public void items(int count, optional kotlin.jvm.functions.Function1<? super java.lang.Integer,?>? key, kotlin.jvm.functions.Function2<? super androidx.compose.foundation.lazy.LazyItemScope,? super java.lang.Integer,kotlin.Unit> itemContent);
diff --git a/compose/foundation/foundation/benchmark/src/androidTest/java/androidx/compose/foundation/benchmark/text/TextDelegateBenchmark.kt b/compose/foundation/foundation/benchmark/src/androidTest/java/androidx/compose/foundation/benchmark/text/TextDelegateBenchmark.kt
index f6743c2..b58f8f1 100644
--- a/compose/foundation/foundation/benchmark/src/androidTest/java/androidx/compose/foundation/benchmark/text/TextDelegateBenchmark.kt
+++ b/compose/foundation/foundation/benchmark/src/androidTest/java/androidx/compose/foundation/benchmark/text/TextDelegateBenchmark.kt
@@ -22,15 +22,12 @@
 import androidx.benchmark.junit4.measureRepeated
 import androidx.compose.foundation.text.InternalFoundationTextApi
 import androidx.compose.foundation.text.TextDelegate
-import androidx.compose.testutils.benchmark.measureRepeatedRecordingCanvas
 import androidx.test.filters.LargeTest
 import androidx.test.platform.app.InstrumentationRegistry
 import androidx.compose.ui.unit.Constraints
 import androidx.compose.ui.unit.LayoutDirection
 import androidx.compose.ui.graphics.Canvas
-import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.graphics.ImageBitmap
-import androidx.compose.ui.graphics.Paint
 import androidx.compose.ui.text.benchmark.RandomTextGenerator
 import androidx.compose.ui.text.benchmark.TextBenchmarkTestRule
 import androidx.compose.ui.text.TextStyle
@@ -43,7 +40,6 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.Parameterized
-import kotlin.math.ceil
 import kotlin.math.roundToInt
 
 @OptIn(InternalFoundationTextApi::class)
@@ -212,34 +208,4 @@
             }
         }
     }
-
-    /**
-     * Measure the time taken by TextDelegate.paintBackground.
-     */
-    @Test
-    fun paintBackground() {
-        textBenchmarkTestRule.generator { textGenerator ->
-            val textDelegate = textDelegate(textGenerator)
-            val layoutResult = textDelegate.layout(
-                Constraints(maxWidth = width),
-                layoutDirection
-            )
-            val paint = Paint().also { it.color = Color.Yellow }
-
-            benchmarkRule.measureRepeatedRecordingCanvas(
-                width = layoutResult.size.width,
-                height = layoutResult.size.height
-            ) { canvas ->
-                TextDelegate.paintBackground(
-                    start = 0,
-                    end = textLength / 2,
-                    paint = paint,
-                    canvas = canvas,
-                    textLayoutResult = layoutResult
-                )
-            }
-        }
-    }
 }
-
-fun Float.toIntPx(): Int = ceil(this).roundToInt()
\ No newline at end of file
diff --git a/compose/foundation/foundation/build.gradle b/compose/foundation/foundation/build.gradle
index 7d4b5d7..ca1170a 100644
--- a/compose/foundation/foundation/build.gradle
+++ b/compose/foundation/foundation/build.gradle
@@ -48,7 +48,7 @@
         implementation(project(":compose:ui:ui-text"))
         implementation(project(":compose:ui:ui-util"))
 
-
+        testImplementation(project(":compose:test-utils"))
         testImplementation(ANDROIDX_TEST_RULES)
         testImplementation(ANDROIDX_TEST_RUNNER)
         testImplementation(JUNIT)
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/ScrollableTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/ScrollableTest.kt
index 4540a3a..ce9cd11 100644
--- a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/ScrollableTest.kt
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/ScrollableTest.kt
@@ -942,6 +942,84 @@
 
     @Test
     @OptIn(ExperimentalTestApi::class)
+    fun scrollable_nestedScroll_disabledConnectionNoOp() =
+        runBlockingWithManualClock { clock ->
+            var childValue = 0f
+            var parentValue = 0f
+            var selfValue = 0f
+            val childController = ScrollableState(
+                consumeScrollDelta = {
+                    childValue += it / 2
+                    it / 2
+                }
+            )
+            val middleController = ScrollableState(
+                consumeScrollDelta = {
+                    selfValue += it / 2
+                    it / 2
+                }
+            )
+            val parentController = ScrollableState(
+                consumeScrollDelta = {
+                    parentValue += it / 2
+                    it / 2
+                }
+            )
+
+            rule.setContent {
+                Box {
+                    Box(
+                        modifier = Modifier.size(300.dp)
+                            .scrollable(
+                                state = parentController,
+                                orientation = Orientation.Horizontal
+                            )
+                    ) {
+                        Box(
+                            Modifier.size(200.dp)
+                                .scrollable(
+                                    enabled = false,
+                                    orientation = Orientation.Horizontal,
+                                    state = middleController
+                                )
+                        ) {
+                            Box(
+                                Modifier.size(200.dp)
+                                    .testTag(scrollableBoxTag)
+                                    .scrollable(
+                                        orientation = Orientation.Horizontal,
+                                        state = childController
+                                    )
+                            )
+                        }
+                    }
+                }
+            }
+
+            rule.runOnIdle {
+                assertThat(parentValue).isEqualTo(0f)
+                assertThat(selfValue).isEqualTo(0f)
+                assertThat(childValue).isEqualTo(0f)
+            }
+
+            rule.onNodeWithTag(scrollableBoxTag)
+                .performGesture {
+                    swipe(center, center.copy(x = center.x + 100f))
+                }
+
+            advanceClockWhileAwaitersExist(clock)
+
+            rule.runOnIdle {
+                assertThat(childValue).isGreaterThan(0f)
+                // disabled middle node doesn't consume
+                assertThat(selfValue).isEqualTo(0f)
+                // but allow nested scroll to propagate up correctly
+                assertThat(parentValue).isGreaterThan(0f)
+            }
+        }
+
+    @Test
+    @OptIn(ExperimentalTestApi::class)
     fun scrollable_interactionSource() = runBlocking {
         val interactionSource = MutableInteractionSource()
         var total = 0f
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/LazyColumnTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/LazyColumnTest.kt
index 3ba18ed..c0abd58 100644
--- a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/LazyColumnTest.kt
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/LazyColumnTest.kt
@@ -36,12 +36,12 @@
 import androidx.compose.foundation.layout.size
 import androidx.compose.foundation.text.BasicText
 import androidx.compose.runtime.Composable
-import androidx.compose.runtime.CompositionLocalProvider
 import androidx.compose.runtime.DisposableEffect
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateListOf
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.setValue
+import androidx.compose.testutils.WithTouchSlop
 import androidx.compose.testutils.assertIsEqualTo
 import androidx.compose.testutils.assertPixels
 import androidx.compose.testutils.runBlockingWithManualClock
@@ -51,8 +51,6 @@
 import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.graphics.graphicsLayer
-import androidx.compose.ui.platform.LocalViewConfiguration
-import androidx.compose.ui.platform.ViewConfiguration
 import androidx.compose.ui.platform.testTag
 import androidx.compose.ui.test.ExperimentalTestApi
 import androidx.compose.ui.test.SemanticsNodeInteraction
@@ -1407,26 +1405,13 @@
     isIn(Range.closed(expected - tolerance, expected + tolerance))
 }
 
-internal val TestTouchSlop = 18f
-
-private val FakeViewConfiguration = object : ViewConfiguration {
-    override val longPressTimeoutMillis: Long
-        get() = 500L
-    override val doubleTapTimeoutMillis: Long
-        get() = 300L
-    override val doubleTapMinTimeMillis: Long
-        get() = 40L
-    override val touchSlop: Float
-        get() = TestTouchSlop
-}
+internal const val TestTouchSlop = 18f
 
 internal fun ComposeContentTestRule.setContentWithTestViewConfiguration(
     composable: @Composable () -> Unit
 ) {
     this.setContent {
-        CompositionLocalProvider(LocalViewConfiguration provides FakeViewConfiguration) {
-            composable()
-        }
+        WithTouchSlop(TestTouchSlop, composable)
     }
 }
 
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/LazyGridTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/LazyGridTest.kt
index 4788ab8..5e11763 100644
--- a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/LazyGridTest.kt
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/LazyGridTest.kt
@@ -18,14 +18,15 @@
 
 import androidx.compose.foundation.ExperimentalFoundationApi
 import androidx.compose.foundation.layout.Spacer
-import androidx.compose.foundation.layout.requiredHeight
 import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.requiredHeight
+import androidx.compose.foundation.layout.requiredWidth
 import androidx.compose.foundation.layout.size
 import androidx.compose.foundation.layout.width
-import androidx.compose.foundation.layout.requiredWidth
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.platform.testTag
 import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.assertIsNotDisplayed
 import androidx.compose.ui.test.assertLeftPositionInRootIsEqualTo
 import androidx.compose.ui.test.assertTopPositionInRootIsEqualTo
 import androidx.compose.ui.test.junit4.createComposeRule
@@ -125,13 +126,13 @@
             .assertIsDisplayed()
 
         rule.onNodeWithTag("7")
-            .assertDoesNotExist()
+            .assertIsNotDisplayed()
 
         rule.onNodeWithTag("8")
-            .assertDoesNotExist()
+            .assertIsNotDisplayed()
 
         rule.onNodeWithTag("9")
-            .assertDoesNotExist()
+            .assertIsNotDisplayed()
     }
 
     @Test
@@ -153,13 +154,13 @@
             .scrollBy(y = 103.dp, density = rule.density)
 
         rule.onNodeWithTag("1")
-            .assertDoesNotExist()
+            .assertIsNotDisplayed()
 
         rule.onNodeWithTag("2")
-            .assertDoesNotExist()
+            .assertIsNotDisplayed()
 
         rule.onNodeWithTag("3")
-            .assertDoesNotExist()
+            .assertIsNotDisplayed()
 
         rule.onNodeWithTag("4")
             .assertIsDisplayed()
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/LazyItemStateRestoration.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/LazyItemStateRestoration.kt
index 46d4c24..8604782 100644
--- a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/LazyItemStateRestoration.kt
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/LazyItemStateRestoration.kt
@@ -89,7 +89,7 @@
                 Modifier.requiredSize(20.dp),
                 state = rememberLazyListState().also { state = it }
             ) {
-                items((0..1).toList()) {
+                items((0..10).toList()) {
                     if (it == 0) {
                         realState = rememberSaveable { counter0++ }
                         DisposableEffect(Unit) {
@@ -106,7 +106,11 @@
         rule.runOnIdle {
             assertThat(realState).isEqualTo(1)
             runBlocking {
-                state.scrollToItem(1, 5)
+                // we scroll through multiple items to make sure the 0th element is not kept in
+                // the reusable items buffer
+                state.scrollToItem(3)
+                state.scrollToItem(5)
+                state.scrollToItem(8)
             }
         }
 
@@ -184,7 +188,7 @@
                 Modifier.requiredSize(20.dp),
                 state = rememberLazyListState().also { state = it }
             ) {
-                items((0..1).toList()) {
+                items((0..10).toList()) {
                     if (it == 0) {
                         LazyRow {
                             item {
@@ -207,7 +211,11 @@
         rule.runOnIdle {
             assertThat(realState).isEqualTo(1)
             runBlocking {
-                state.scrollToItem(1, 5)
+                // we scroll through multiple items to make sure the 0th element is not kept in
+                // the reusable items buffer
+                state.scrollToItem(3)
+                state.scrollToItem(5)
+                state.scrollToItem(8)
             }
         }
 
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/LazyListHeadersTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/LazyListHeadersTest.kt
index 276a499..f50f8e7 100644
--- a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/LazyListHeadersTest.kt
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/LazyListHeadersTest.kt
@@ -25,6 +25,7 @@
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.platform.testTag
 import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.assertIsNotDisplayed
 import androidx.compose.ui.test.assertLeftPositionInRootIsEqualTo
 import androidx.compose.ui.test.assertTopPositionInRootIsEqualTo
 import androidx.compose.ui.test.junit4.createComposeRule
@@ -162,7 +163,7 @@
             .scrollBy(y = 105.dp, density = rule.density)
 
         rule.onNodeWithTag(firstHeaderTag)
-            .assertDoesNotExist()
+            .assertIsNotDisplayed()
 
         rule.onNodeWithTag(secondHeaderTag)
             .assertIsDisplayed()
@@ -289,7 +290,7 @@
             .scrollBy(x = 105.dp, density = rule.density)
 
         rule.onNodeWithTag(firstHeaderTag)
-            .assertDoesNotExist()
+            .assertIsNotDisplayed()
 
         rule.onNodeWithTag(secondHeaderTag)
             .assertIsDisplayed()
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/LazyListPrefetcherTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/LazyListPrefetcherTest.kt
new file mode 100644
index 0000000..5dddbcf
--- /dev/null
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/LazyListPrefetcherTest.kt
@@ -0,0 +1,303 @@
+/*
+ * Copyright 2020 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.lazy
+
+import androidx.compose.foundation.gestures.scrollBy
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.height
+import androidx.compose.runtime.DisposableEffect
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.unit.dp
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.LargeTest
+import kotlinx.coroutines.runBlocking
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@LargeTest
+@RunWith(AndroidJUnit4::class)
+class LazyListPrefetcherTest {
+
+    @get:Rule
+    val rule = createComposeRule()
+
+    val itemsSizePx = 30f
+    val itemsSizeDp = with(rule.density) { itemsSizePx.toDp() }
+
+    lateinit var state: LazyListState
+
+    @Test
+    fun notPrefetchingForwardInitially() {
+        composeList()
+
+        rule.onNodeWithTag("2")
+            .assertDoesNotExist()
+    }
+
+    @Test
+    fun notPrefetchingBackwardInitially() {
+        composeList(firstItem = 2)
+
+        rule.onNodeWithTag("0")
+            .assertDoesNotExist()
+    }
+
+    @Test
+    fun prefetchingForwardAfterSmallScroll() {
+        composeList()
+
+        rule.runOnIdle {
+            runBlocking {
+                state.scrollBy(5f)
+            }
+        }
+
+        waitForPrefetch(2)
+
+        rule.onNodeWithTag("2")
+            .assertExists()
+        rule.onNodeWithTag("3")
+            .assertDoesNotExist()
+    }
+
+    @Test
+    fun prefetchingBackwardAfterSmallScroll() {
+        composeList(firstItem = 2, itemOffset = 10)
+
+        rule.runOnIdle {
+            runBlocking {
+                state.scrollBy(-5f)
+            }
+        }
+
+        waitForPrefetch(1)
+
+        rule.onNodeWithTag("1")
+            .assertExists()
+        rule.onNodeWithTag("0")
+            .assertDoesNotExist()
+    }
+
+    @Test
+    fun prefetchingForwardAndBackward() {
+        composeList(firstItem = 1)
+
+        rule.runOnIdle {
+            runBlocking {
+                state.scrollBy(5f)
+            }
+        }
+
+        waitForPrefetch(3)
+
+        rule.onNodeWithTag("3")
+            .assertExists()
+        rule.onNodeWithTag("0")
+            .assertDoesNotExist()
+
+        rule.runOnIdle {
+            runBlocking {
+                state.scrollBy(-2f)
+            }
+        }
+
+        waitForPrefetch(0)
+
+        rule.onNodeWithTag("0")
+            .assertExists()
+        rule.onNodeWithTag("3")
+            .assertDoesNotExist()
+    }
+
+    @Test
+    fun prefetchingForwardTwice() {
+        composeList()
+
+        rule.runOnIdle {
+            runBlocking {
+                state.scrollBy(5f)
+            }
+        }
+
+        waitForPrefetch(2)
+
+        rule.runOnIdle {
+            runBlocking {
+                state.scrollBy(itemsSizePx / 2f)
+                state.scrollBy(itemsSizePx / 2f)
+            }
+        }
+
+        waitForPrefetch(3)
+
+        rule.onNodeWithTag("2")
+            .assertIsDisplayed()
+        rule.onNodeWithTag("3")
+            .assertExists()
+        rule.onNodeWithTag("4")
+            .assertDoesNotExist()
+    }
+
+    @Test
+    fun prefetchingBackwardTwice() {
+        composeList(firstItem = 4)
+
+        rule.runOnIdle {
+            runBlocking {
+                state.scrollBy(-5f)
+            }
+        }
+
+        waitForPrefetch(2)
+
+        rule.runOnIdle {
+            runBlocking {
+                state.scrollBy(-itemsSizePx / 2f)
+                state.scrollBy(-itemsSizePx / 2f)
+            }
+        }
+
+        waitForPrefetch(1)
+
+        rule.onNodeWithTag("2")
+            .assertIsDisplayed()
+        rule.onNodeWithTag("1")
+            .assertExists()
+        rule.onNodeWithTag("0")
+            .assertDoesNotExist()
+    }
+
+    @Test
+    fun prefetchingForwardAndBackwardReverseLayout() {
+        composeList(firstItem = 1, reverseLayout = true)
+
+        rule.runOnIdle {
+            runBlocking {
+                state.scrollBy(5f)
+            }
+        }
+
+        waitForPrefetch(3)
+
+        rule.onNodeWithTag("3")
+            .assertExists()
+        rule.onNodeWithTag("0")
+            .assertDoesNotExist()
+
+        rule.runOnIdle {
+            runBlocking {
+                state.scrollBy(-2f)
+            }
+        }
+
+        waitForPrefetch(0)
+
+        rule.onNodeWithTag("0")
+            .assertExists()
+        rule.onNodeWithTag("3")
+            .assertDoesNotExist()
+    }
+
+    @Test
+    fun prefetchingForwardAndBackwardWithContentPadding() {
+        val halfItemSize = itemsSizeDp / 2f
+        composeList(
+            firstItem = 2,
+            itemOffset = 5,
+            contentPadding = PaddingValues(vertical = halfItemSize)
+        )
+
+        rule.onNodeWithTag("1")
+            .assertIsDisplayed()
+        rule.onNodeWithTag("2")
+            .assertIsDisplayed()
+        rule.onNodeWithTag("3")
+            .assertIsDisplayed()
+        rule.onNodeWithTag("0")
+            .assertDoesNotExist()
+        rule.onNodeWithTag("4")
+            .assertDoesNotExist()
+
+        rule.runOnIdle {
+            runBlocking {
+                state.scrollBy(5f)
+            }
+        }
+
+        waitForPrefetch(3)
+
+        rule.onNodeWithTag("4")
+            .assertExists()
+        rule.onNodeWithTag("0")
+            .assertDoesNotExist()
+
+        rule.runOnIdle {
+            runBlocking {
+                state.scrollBy(-2f)
+            }
+        }
+
+        waitForPrefetch(0)
+
+        rule.onNodeWithTag("0")
+            .assertExists()
+    }
+
+    private fun waitForPrefetch(index: Int) {
+        rule.waitUntil {
+            activeNodes.contains(index)
+        }
+    }
+
+    private val activeNodes = mutableSetOf<Int>()
+
+    private fun composeList(
+        firstItem: Int = 0,
+        itemOffset: Int = 0,
+        reverseLayout: Boolean = false,
+        contentPadding: PaddingValues = PaddingValues(0.dp)
+    ) {
+        rule.setContent {
+            state = rememberLazyListState(
+                initialFirstVisibleItemIndex = firstItem,
+                initialFirstVisibleItemScrollOffset = itemOffset
+            )
+            LazyColumn(
+                Modifier.height(itemsSizeDp * 1.5f),
+                state,
+                reverseLayout = reverseLayout,
+                contentPadding = contentPadding
+            ) {
+                items(100) {
+                    DisposableEffect(it) {
+                        activeNodes.add(it)
+                        onDispose {
+                            activeNodes.remove(it)
+                        }
+                    }
+                    Spacer(Modifier.height(itemsSizeDp).fillParentMaxWidth().testTag("$it"))
+                }
+            }
+        }
+    }
+}
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/LazyListSlotsReuseTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/LazyListSlotsReuseTest.kt
new file mode 100644
index 0000000..8f626d0
--- /dev/null
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/LazyListSlotsReuseTest.kt
@@ -0,0 +1,325 @@
+/*
+ * Copyright 2020 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.lazy
+
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.height
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.assertIsNotDisplayed
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithTag
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.LargeTest
+import com.google.common.truth.Truth
+import kotlinx.coroutines.runBlocking
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@LargeTest
+@RunWith(AndroidJUnit4::class)
+class LazyListSlotsReuseTest {
+
+    @get:Rule
+    val rule = createComposeRule()
+
+    val itemsSizePx = 30f
+    val itemsSizeDp = with(rule.density) { itemsSizePx.toDp() }
+
+    @Test
+    fun scroll1ItemScrolledOffItemIsKeptForReuse() {
+        lateinit var state: LazyListState
+        rule.setContent {
+            state = rememberLazyListState()
+            LazyColumn(
+                Modifier.height(itemsSizeDp * 1.5f),
+                state
+            ) {
+                items(100) {
+                    Spacer(Modifier.height(itemsSizeDp).fillParentMaxWidth().testTag("$it"))
+                }
+            }
+        }
+
+        rule.onNodeWithTag("0")
+            .assertIsDisplayed()
+
+        rule.runOnIdle {
+            runBlocking {
+                state.scrollToItem(1)
+            }
+        }
+
+        rule.onNodeWithTag("0")
+            .assertExists()
+            .assertIsNotDisplayed()
+        rule.onNodeWithTag("1")
+            .assertIsDisplayed()
+    }
+
+    @Test
+    fun scroll2ItemsScrolledOffItemsAreKeptForReuse() {
+        lateinit var state: LazyListState
+        rule.setContent {
+            state = rememberLazyListState()
+            LazyColumn(
+                Modifier.height(itemsSizeDp * 1.5f),
+                state
+            ) {
+                items(100) {
+                    Spacer(Modifier.height(itemsSizeDp).fillParentMaxWidth().testTag("$it"))
+                }
+            }
+        }
+
+        rule.onNodeWithTag("0")
+            .assertIsDisplayed()
+        rule.onNodeWithTag("1")
+            .assertIsDisplayed()
+
+        rule.runOnIdle {
+            runBlocking {
+                state.scrollToItem(2)
+            }
+        }
+
+        rule.onNodeWithTag("0")
+            .assertExists()
+            .assertIsNotDisplayed()
+        rule.onNodeWithTag("1")
+            .assertExists()
+            .assertIsNotDisplayed()
+        rule.onNodeWithTag("2")
+            .assertIsDisplayed()
+    }
+
+    @Test
+    fun scroll3Items2OfScrolledOffItemsAreKeptForReuse() {
+        lateinit var state: LazyListState
+        rule.setContent {
+            state = rememberLazyListState()
+            LazyColumn(
+                Modifier.height(itemsSizeDp * 1.5f),
+                state
+            ) {
+                items(100) {
+                    Spacer(Modifier.height(itemsSizeDp).fillParentMaxWidth().testTag("$it"))
+                }
+            }
+        }
+
+        rule.onNodeWithTag("0")
+            .assertIsDisplayed()
+        rule.onNodeWithTag("1")
+            .assertIsDisplayed()
+
+        rule.runOnIdle {
+            runBlocking {
+                // after this step 0 and 1 are in reusable buffer
+                state.scrollToItem(2)
+
+                // this step requires one item and will take the last item from the buffer - item
+                // 1 plus will put 2 in the buffer. so expected buffer is items 2 and 0
+                state.scrollToItem(3)
+            }
+        }
+
+        // recycled
+        rule.onNodeWithTag("1")
+            .assertDoesNotExist()
+
+        // in buffer
+        rule.onNodeWithTag("0")
+            .assertExists()
+            .assertIsNotDisplayed()
+        rule.onNodeWithTag("2")
+            .assertExists()
+            .assertIsNotDisplayed()
+
+        // visible
+        rule.onNodeWithTag("3")
+            .assertIsDisplayed()
+        rule.onNodeWithTag("4")
+            .assertIsDisplayed()
+    }
+
+    @Test
+    fun doMultipleScrollsOneByOne() {
+        lateinit var state: LazyListState
+        rule.setContent {
+            state = rememberLazyListState()
+            LazyColumn(
+                Modifier.height(itemsSizeDp * 1.5f),
+                state
+            ) {
+                items(100) {
+                    Spacer(Modifier.height(itemsSizeDp).fillParentMaxWidth().testTag("$it"))
+                }
+            }
+        }
+        rule.runOnIdle {
+            runBlocking {
+                state.scrollToItem(1) // buffer is [0]
+                state.scrollToItem(2) // 0 used, buffer is [1]
+                state.scrollToItem(3) // 1 used, buffer is [2]
+                state.scrollToItem(4) // 2 used, buffer is [3]
+            }
+        }
+
+        // recycled
+        rule.onNodeWithTag("0")
+            .assertDoesNotExist()
+        rule.onNodeWithTag("1")
+            .assertDoesNotExist()
+        rule.onNodeWithTag("2")
+            .assertDoesNotExist()
+
+        // in buffer
+        rule.onNodeWithTag("3")
+            .assertExists()
+            .assertIsNotDisplayed()
+
+        // visible
+        rule.onNodeWithTag("4")
+            .assertIsDisplayed()
+        rule.onNodeWithTag("5")
+            .assertIsDisplayed()
+    }
+
+    @Test
+    fun scrollBackwardOnce() {
+        lateinit var state: LazyListState
+        rule.setContent {
+            state = rememberLazyListState(10)
+            LazyColumn(
+                Modifier.height(itemsSizeDp * 1.5f),
+                state
+            ) {
+                items(100) {
+                    Spacer(Modifier.height(itemsSizeDp).fillParentMaxWidth().testTag("$it"))
+                }
+            }
+        }
+        rule.runOnIdle {
+            runBlocking {
+                state.scrollToItem(8) // buffer is [10, 11]
+            }
+        }
+
+        // in buffer
+        rule.onNodeWithTag("10")
+            .assertExists()
+            .assertIsNotDisplayed()
+        rule.onNodeWithTag("11")
+            .assertExists()
+            .assertIsNotDisplayed()
+
+        // visible
+        rule.onNodeWithTag("8")
+            .assertIsDisplayed()
+        rule.onNodeWithTag("9")
+            .assertIsDisplayed()
+    }
+
+    @Test
+    fun scrollBackwardOneByOne() {
+        lateinit var state: LazyListState
+        rule.setContent {
+            state = rememberLazyListState(10)
+            LazyColumn(
+                Modifier.height(itemsSizeDp * 1.5f),
+                state
+            ) {
+                items(100) {
+                    Spacer(Modifier.height(itemsSizeDp).fillParentMaxWidth().testTag("$it"))
+                }
+            }
+        }
+        rule.runOnIdle {
+            runBlocking {
+                state.scrollToItem(9) // buffer is [11]
+                state.scrollToItem(7) // 11 reused, buffer is [9]
+                state.scrollToItem(6) // 9 reused, buffer is [8]
+            }
+        }
+
+        // in buffer
+        rule.onNodeWithTag("8")
+            .assertExists()
+            .assertIsNotDisplayed()
+
+        // visible
+        rule.onNodeWithTag("6")
+            .assertIsDisplayed()
+        rule.onNodeWithTag("7")
+            .assertIsDisplayed()
+    }
+
+    @Test
+    fun scrollingBackReusesTheSameSlot() {
+        lateinit var state: LazyListState
+        var counter0 = 0
+        var counter1 = 10
+        var rememberedValue0 = -1
+        var rememberedValue1 = -1
+        rule.setContent {
+            state = rememberLazyListState()
+            LazyColumn(
+                Modifier.height(itemsSizeDp * 1.5f),
+                state
+            ) {
+                items(100) {
+                    if (it == 0) {
+                        rememberedValue0 = remember { counter0++ }
+                    }
+                    if (it == 1) {
+                        rememberedValue1 = remember { counter1++ }
+                    }
+                    Spacer(Modifier.height(itemsSizeDp).fillParentMaxWidth().testTag("$it"))
+                }
+            }
+        }
+        rule.runOnIdle {
+            runBlocking {
+                state.scrollToItem(2) // buffer is [0, 1]
+                state.scrollToItem(0) // scrolled back, 0 and 1 are reused back. buffer: [2, 3]
+            }
+        }
+
+        rule.runOnIdle {
+            Truth.assertWithMessage("Item 0 restored remembered value is $rememberedValue0")
+                .that(rememberedValue0).isEqualTo(0)
+            Truth.assertWithMessage("Item 1 restored remembered value is $rememberedValue1")
+                .that(rememberedValue1).isEqualTo(10)
+        }
+
+        rule.onNodeWithTag("0")
+            .assertIsDisplayed()
+        rule.onNodeWithTag("1")
+            .assertIsDisplayed()
+
+        rule.onNodeWithTag("2")
+            .assertExists()
+            .assertIsNotDisplayed()
+        rule.onNodeWithTag("3")
+            .assertExists()
+            .assertIsNotDisplayed()
+    }
+}
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/LazyRowTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/LazyRowTest.kt
index bd6fae3..31062be 100644
--- a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/LazyRowTest.kt
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/LazyRowTest.kt
@@ -42,6 +42,7 @@
 import androidx.compose.ui.test.SemanticsNodeInteraction
 import androidx.compose.ui.test.assertHeightIsEqualTo
 import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.assertIsNotDisplayed
 import androidx.compose.ui.test.assertLeftPositionInRootIsEqualTo
 import androidx.compose.ui.test.assertPositionInRootIsEqualTo
 import androidx.compose.ui.test.assertWidthIsEqualTo
@@ -215,7 +216,7 @@
             .assertIsDisplayed()
 
         rule.onNodeWithTag("4")
-            .assertDoesNotExist()
+            .assertIsNotDisplayed()
     }
 
     @Test
@@ -236,7 +237,7 @@
             .scrollBy(x = 105.dp, density = rule.density)
 
         rule.onNodeWithTag("1")
-            .assertDoesNotExist()
+            .assertIsNotDisplayed()
 
         rule.onNodeWithTag("2")
             .assertIsDisplayed()
@@ -263,7 +264,7 @@
             .scrollBy(x = 150.dp, density = rule.density)
 
         rule.onNodeWithTag("1")
-            .assertDoesNotExist()
+            .assertIsNotDisplayed()
 
         rule.onNodeWithTag("2")
             .assertIsDisplayed()
@@ -523,10 +524,12 @@
 
     @Test
     fun scrollsLeftInRtl() {
+        lateinit var state: LazyListState
         rule.setContentWithTestViewConfiguration {
             CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Rtl) {
                 Box(Modifier.width(100.dp)) {
-                    LazyRow(Modifier.testTag(LazyListTag)) {
+                    state = rememberLazyListState()
+                    LazyRow(Modifier.testTag(LazyListTag), state) {
                         items(4) {
                             Spacer(
                                 Modifier.width(101.dp).fillParentMaxHeight().testTag("$it")
@@ -540,11 +543,10 @@
         rule.onNodeWithTag(LazyListTag)
             .scrollBy(x = (-150).dp, density = rule.density)
 
-        rule.onNodeWithTag("0")
-            .assertDoesNotExist()
-
-        rule.onNodeWithTag("1")
-            .assertIsDisplayed()
+        rule.runOnIdle {
+            assertThat(state.firstVisibleItemIndex).isEqualTo(1)
+            assertThat(state.firstVisibleItemScrollOffset).isGreaterThan(0)
+        }
     }
 
     @Test
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text/CoreTextInlineContentTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text/CoreTextInlineContentTest.kt
new file mode 100644
index 0000000..dfb9deb
--- /dev/null
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text/CoreTextInlineContentTest.kt
@@ -0,0 +1,97 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.foundation.text
+
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.layout.onSizeChanged
+import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.text.Placeholder
+import androidx.compose.ui.text.PlaceholderVerticalAlign
+import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.text.buildAnnotatedString
+import androidx.compose.ui.text.style.TextOverflow
+import androidx.compose.ui.unit.Density
+import androidx.compose.ui.unit.IntSize
+import androidx.compose.ui.unit.sp
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.LargeTest
+import com.nhaarman.mockitokotlin2.mock
+import com.nhaarman.mockitokotlin2.verify
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@LargeTest
+@RunWith(AndroidJUnit4::class)
+class CoreTextInlineContentTest {
+
+    @get:Rule
+    val rule = createComposeRule()
+
+    @Test
+    fun placeholder_changeSize_updateInlineContentSize() {
+        // Callback to monitor the size changes of a composable.
+        val onSizeChanged: (IntSize) -> Unit = mock()
+        var size by mutableStateOf(IntSize(50, 50))
+
+        rule.setContent {
+            val inlineTextContent = InlineTextContent(
+                placeholder = Placeholder(
+                    size.width.sp,
+                    size.height.sp,
+                    PlaceholderVerticalAlign.AboveBaseline
+                )
+            ) {
+                Box(modifier = Modifier.fillMaxSize().onSizeChanged(onSizeChanged))
+            }
+
+            CompositionLocalProvider(
+                LocalDensity provides Density(density = 1f, fontScale = 1f)
+            ) {
+                CoreText(
+                    text = buildAnnotatedString {
+                        append("Hello")
+                        appendInlineContent("box")
+                        append("World")
+                    },
+                    style = TextStyle(fontSize = 100.sp),
+                    inlineContent = mapOf("box" to inlineTextContent),
+                    maxLines = Int.MAX_VALUE,
+                    onTextLayout = {},
+                    overflow = TextOverflow.Clip,
+                    softWrap = true
+                )
+            }
+        }
+
+        rule.runOnIdle {
+            // Verify that the initial size is (50, 50).
+            verify(onSizeChanged).invoke(IntSize(50, 50))
+            size = IntSize(100, 100)
+        }
+        rule.waitForIdle()
+        // Verify that the size has been updated to (100, 100).
+        verify(onSizeChanged).invoke(IntSize(100, 100))
+    }
+}
\ No newline at end of file
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text/TextDelegateIntegrationTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text/TextDelegateIntegrationTest.kt
index 1345fae..05616c7 100644
--- a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text/TextDelegateIntegrationTest.kt
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text/TextDelegateIntegrationTest.kt
@@ -17,15 +17,10 @@
 package androidx.compose.foundation.text
 
 import android.graphics.Bitmap
-import androidx.compose.ui.geometry.Rect
-import androidx.compose.ui.graphics.Canvas
-import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.graphics.Paint
 import androidx.compose.ui.text.AnnotatedString
 import androidx.compose.ui.text.SpanStyle
 import androidx.compose.ui.text.TextLayoutResult
 import androidx.compose.ui.text.TextStyle
-import androidx.compose.foundation.text.matchers.assertThat
 import androidx.compose.ui.text.style.TextOverflow
 import androidx.compose.ui.unit.Constraints
 import androidx.compose.ui.unit.Density
@@ -90,240 +85,6 @@
         }
     }
 
-    @Test
-    fun testBackgroundPaint_paint_wrap_multiLines() {
-        with(density) {
-            // Setup test.
-            val fontSize = 20.sp
-            val fontSizeInPx = fontSize.toPx()
-            val text = "HelloHello"
-            val spanStyle = SpanStyle(fontSize = fontSize, fontFamily = fontFamily)
-            val annotatedString = AnnotatedString(text, spanStyle)
-            val textDelegate = TextDelegate(
-                text = annotatedString,
-                style = TextStyle.Default,
-                density = this,
-                resourceLoader = resourceLoader
-            )
-            val layoutResult = textDelegate.layout(
-                Constraints(maxWidth = 120),
-                LayoutDirection.Ltr
-            )
-
-            val expectedBitmap = layoutResult.toBitmap()
-            val expectedCanvas = Canvas(android.graphics.Canvas(expectedBitmap))
-            val expectedPaint = Paint()
-            val defaultSelectionColor = Color(0x6633B5E5)
-            val selectionPaint = Paint().also { it.color = defaultSelectionColor }
-            expectedPaint.color = defaultSelectionColor
-
-            val firstLineLeft = layoutResult.multiParagraph.getLineLeft(0)
-            val secondLineLeft = layoutResult.multiParagraph.getLineLeft(1)
-            val firstLineRight = layoutResult.multiParagraph.getLineRight(0)
-            val secondLineRight = layoutResult.multiParagraph.getLineRight(1)
-            expectedCanvas.drawRect(
-                Rect(firstLineLeft, 0f, firstLineRight, fontSizeInPx),
-                expectedPaint
-            )
-            expectedCanvas.drawRect(
-                Rect(
-                    secondLineLeft,
-                    fontSizeInPx,
-                    secondLineRight,
-                    layoutResult.multiParagraph.height
-                ),
-                expectedPaint
-            )
-
-            val actualBitmap = layoutResult.toBitmap()
-            val actualCanvas = Canvas(android.graphics.Canvas(actualBitmap))
-
-            // Run.
-            // Select all.
-            TextDelegate.paintBackground(
-                start = 0,
-                end = text.length,
-                paint = selectionPaint,
-                canvas = actualCanvas,
-                textLayoutResult = layoutResult
-            )
-
-            // Assert.
-            assertThat(actualBitmap).isEqualToBitmap(expectedBitmap)
-        }
-    }
-
-    @Test
-    fun testBackgroundPaint_paint_with_default_color() {
-        with(density) {
-            // Setup test.
-            val selectionStart = 0
-            val selectionEnd = 3
-            val fontSize = 20.sp
-            val fontSizeInPx = fontSize.toPx()
-            val text = "Hello"
-            val spanStyle = SpanStyle(fontSize = fontSize, fontFamily = fontFamily)
-            val annotatedString = AnnotatedString(text, spanStyle)
-            val textDelegate = TextDelegate(
-                text = annotatedString,
-                style = TextStyle.Default,
-                density = this,
-                resourceLoader = resourceLoader
-            )
-            val layoutResult = textDelegate.layout(Constraints(), LayoutDirection.Ltr)
-
-            val expectedBitmap = layoutResult.toBitmap()
-            val expectedCanvas = Canvas(android.graphics.Canvas(expectedBitmap))
-            val expectedPaint = Paint()
-            val defaultSelectionColor = Color(0x6633B5E5)
-            val selectionPaint = Paint().also { it.color = defaultSelectionColor }
-            expectedPaint.color = defaultSelectionColor
-            expectedCanvas.drawRect(
-                Rect(
-                    left = 0f,
-                    top = 0f,
-                    right = fontSizeInPx * (selectionEnd - selectionStart),
-                    bottom = fontSizeInPx
-                ),
-                expectedPaint
-            )
-
-            val actualBitmap = layoutResult.toBitmap()
-            val actualCanvas = Canvas(android.graphics.Canvas(actualBitmap))
-
-            // Run.
-            TextDelegate.paintBackground(
-                start = selectionStart,
-                end = selectionEnd,
-                paint = selectionPaint,
-                canvas = actualCanvas,
-                textLayoutResult = layoutResult
-            )
-
-            // Assert
-            assertThat(actualBitmap).isEqualToBitmap(expectedBitmap)
-        }
-    }
-
-    @Test
-    fun testBackgroundPaint_paint_with_default_color_bidi() {
-        with(density) {
-            // Setup test.
-            val textLTR = "Hello"
-            // From right to left: שלום
-            val textRTL = "\u05e9\u05dc\u05d5\u05dd"
-            val text = textLTR + textRTL
-            val selectionLTRStart = 2
-            val selectionRTLEnd = 2
-            val fontSize = 20.sp
-            val fontSizeInPx = fontSize.toPx()
-            val spanStyle = SpanStyle(fontSize = fontSize, fontFamily = fontFamily)
-            val annotatedString = AnnotatedString(text, spanStyle)
-            val textDelegate = TextDelegate(
-                text = annotatedString,
-                style = TextStyle.Default,
-                density = this,
-                resourceLoader = resourceLoader
-            )
-            val layoutResult = textDelegate.layout(Constraints(), LayoutDirection.Ltr)
-
-            val expectedBitmap = layoutResult.toBitmap()
-            val expectedCanvas = Canvas(android.graphics.Canvas(expectedBitmap))
-            val expectedPaint = Paint()
-            val defaultSelectionColor = Color(0x6633B5E5)
-            val selectionPaint = Paint().also { it.color = defaultSelectionColor }
-            expectedPaint.color = defaultSelectionColor
-            // Select "llo".
-            expectedCanvas.drawRect(
-                Rect(
-                    left = fontSizeInPx * selectionLTRStart,
-                    top = 0f,
-                    right = textLTR.length * fontSizeInPx,
-                    bottom = fontSizeInPx
-                ),
-                expectedPaint
-            )
-
-            // Select "של"
-            expectedCanvas.drawRect(
-                Rect(
-                    left = (textLTR.length + textRTL.length - selectionRTLEnd) * fontSizeInPx,
-                    top = 0f,
-                    right = (textLTR.length + textRTL.length) * fontSizeInPx,
-                    bottom = fontSizeInPx
-                ),
-                expectedPaint
-            )
-
-            val actualBitmap = layoutResult.toBitmap()
-            val actualCanvas = Canvas(android.graphics.Canvas(actualBitmap))
-
-            // Run.
-            TextDelegate.paintBackground(
-                start = selectionLTRStart,
-                end = textLTR.length + selectionRTLEnd,
-                paint = selectionPaint,
-                canvas = actualCanvas,
-                textLayoutResult = layoutResult
-            )
-
-            // Assert
-            assertThat(actualBitmap).isEqualToBitmap(expectedBitmap)
-        }
-    }
-
-    @Test
-    fun testBackgroundPaint_paint_with_customized_color() {
-        with(density) {
-            // Setup test.
-            val selectionStart = 0
-            val selectionEnd = 3
-            val fontSize = 20.sp
-            val fontSizeInPx = fontSize.toPx()
-            val text = "Hello"
-            val spanStyle = SpanStyle(fontSize = fontSize, fontFamily = fontFamily)
-            val annotatedString = AnnotatedString(text, spanStyle)
-            val selectionColor = Color(0x66AABB33)
-            val selectionPaint = Paint().also { it.color = selectionColor }
-            val textDelegate = TextDelegate(
-                text = annotatedString,
-                style = TextStyle.Default,
-                density = this,
-                resourceLoader = resourceLoader
-            )
-            val layoutResult = textDelegate.layout(Constraints(), LayoutDirection.Ltr)
-
-            val expectedBitmap = layoutResult.toBitmap()
-            val expectedCanvas = Canvas(android.graphics.Canvas(expectedBitmap))
-            val expectedPaint = Paint()
-            expectedPaint.color = selectionColor
-            expectedCanvas.drawRect(
-                Rect(
-                    left = 0f,
-                    top = 0f,
-                    right = fontSizeInPx * (selectionEnd - selectionStart),
-                    bottom = fontSizeInPx
-                ),
-                expectedPaint
-            )
-
-            val actualBitmap = layoutResult.toBitmap()
-            val actualCanvas = Canvas(android.graphics.Canvas(actualBitmap))
-
-            // Run.
-            TextDelegate.paintBackground(
-                start = selectionStart,
-                end = selectionEnd,
-                paint = selectionPaint,
-                canvas = actualCanvas,
-                textLayoutResult = layoutResult
-            )
-
-            // Assert
-            assertThat(actualBitmap).isEqualToBitmap(expectedBitmap)
-        }
-    }
-
 //    @Test
 //    fun multiParagraphIntrinsics_isReused() {
 //        val textDelegate = TextDelegate(
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text/TextFieldDelegateIntegrationTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text/TextFieldDelegateIntegrationTest.kt
index b37ba843..b96602f 100644
--- a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text/TextFieldDelegateIntegrationTest.kt
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text/TextFieldDelegateIntegrationTest.kt
@@ -60,13 +60,9 @@
 
         val expectedBitmap = layoutResult.toBitmap()
         val expectedCanvas = Canvas(android.graphics.Canvas(expectedBitmap))
-        TextDelegate.paintBackground(
-            0,
-            1,
-            Paint().apply { color = selectionColor },
-            expectedCanvas,
-            layoutResult
-        )
+        val selectionPath = layoutResult.multiParagraph.getPathForRange(0, 1)
+        expectedCanvas.drawPath(selectionPath, Paint().apply { color = selectionColor })
+
         TextPainter.paint(expectedCanvas, layoutResult)
 
         val actualBitmap = layoutResult.toBitmap()
@@ -201,4 +197,4 @@
     size.width,
     size.height,
     Bitmap.Config.ARGB_8888
-)
\ No newline at end of file
+)
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text/selection/SelectionContainerFocusTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text/selection/SelectionContainerFocusTest.kt
index 2b0bd94..258fb19 100644
--- a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text/selection/SelectionContainerFocusTest.kt
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text/selection/SelectionContainerFocusTest.kt
@@ -34,6 +34,7 @@
 import androidx.compose.ui.layout.onGloballyPositioned
 import androidx.compose.ui.platform.LocalHapticFeedback
 import androidx.compose.ui.platform.LocalLayoutDirection
+import androidx.compose.ui.platform.LocalTextToolbar
 import androidx.compose.ui.platform.testTag
 import androidx.compose.ui.test.click
 import androidx.compose.ui.test.hasTestTag
@@ -155,7 +156,8 @@
         rule.setContent {
             CompositionLocalProvider(
                 LocalHapticFeedback provides hapticFeedback,
-                LocalLayoutDirection provides layoutDirection
+                LocalLayoutDirection provides layoutDirection,
+                LocalTextToolbar provides mock()
             ) {
                 Column {
                     SelectionContainer(
diff --git a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/lazy/LazyListPrefetcher.android.kt b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/lazy/LazyListPrefetcher.android.kt
new file mode 100644
index 0000000..b8e2b9a
--- /dev/null
+++ b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/lazy/LazyListPrefetcher.android.kt
@@ -0,0 +1,283 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.foundation.lazy
+
+import android.view.Choreographer
+import android.view.Display
+import android.view.View
+import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.RememberObserver
+import androidx.compose.runtime.State
+import androidx.compose.runtime.remember
+import androidx.compose.ui.layout.SubcomposeLayoutState
+import androidx.compose.ui.layout.SubcomposeLayoutState.PrecomposedSlotHandle
+import androidx.compose.ui.platform.LocalView
+import androidx.compose.ui.util.trace
+import java.util.concurrent.TimeUnit
+
+@OptIn(ExperimentalFoundationApi::class)
+@Composable
+internal actual fun LazyListPrefetcher(
+    lazyListState: LazyListState,
+    stateOfItemsProvider: State<LazyListItemsProvider>,
+    itemContentFactory: LazyListItemContentFactory,
+    subcomposeLayoutState: SubcomposeLayoutState
+) {
+    val view = LocalView.current
+    remember(subcomposeLayoutState, lazyListState, view) {
+        LazyListPrefetcher(
+            subcomposeLayoutState,
+            lazyListState,
+            stateOfItemsProvider,
+            itemContentFactory,
+            view
+        )
+    }
+}
+
+/**
+ * Android specific prefetch implementation. The only platform specific things are:
+ * 1) Calculating the deadline
+ * 2) Posting the delayed runnable
+ * This could be refactored in the future in order to keep the most logic platform agnostic to
+ * enable the prefetching on desktop.
+ *
+ * The differences with the implementation in RecyclerView:
+ *
+ * 1) Prefetch is per-list-index, and performed on whole item.
+ *    With RecyclerView, nested scrolling RecyclerViews would prefetch incrementally, e.g. items
+ *    like the following in a scrolling vertical list could be broken up within a frame:
+ *    [Row1 [a], [b], [c]]
+ *    [Row2 [d], [e]]
+ *    [Row3 [f], [g], [h]]
+ *    You could have frames that break up this work arbitrarily:
+ *    Frame 1 - prefetch [a]
+ *    Frame 2 - prefetch [b], [c]
+ *    Frame 3 - prefetch [d]
+ *    Frame 4 - prefetch [e], [f]
+ *    Something similar is not possible with LazyColumn yet. Also, if we nest LazyRow inside
+ *    LazyColumn the content of LazyRow will not be composed at all during the prefetch stage as
+ *    we only compose during the measuring and the pre-measuring is not yet possible with this
+ *    prefetch implementation. Tracking bug: b/187392865.
+ *
+ * 2) Prefetching time estimation only captured during the prefetch.
+ *    We currently don't track the time of the regular subcompose call happened during the regular
+ *    measure pass, only the ones which are done during the prefetching. The downside is we build
+ *    our prefetch information only after scrolling has started and items are showing up. Your very
+ *    first scroll won't know if it's safe to prefetch. Why:
+ *    a) SubcomposeLayout is not exposing an API to understand if subcompose() call is going to
+ *    do the real work. The work could be skipped if the same lambda was passed as for the
+ *    previous invocation or if there were no recompositions scheduled. We could workaround it
+ *    by keeping the extra state in LazyListState about what items we already composed and to
+ *    only measure the first composition for the given slot, or consider exposing extra
+ *    information in SubcomposeLayoutState API.
+ *    b) It allows us to nicely decouple the logic, now the prefetching logic is build on
+ *    top of the regular LazyColumn measuring functionallity and the main logic knows nothing
+ *    about prefetch
+ *    c) Maybe the better approach would be to wait till the low-level runtime infra is ready to
+ *    do subcompositions on the different threads which illuminates the need to calculate the
+ *    deadlines completely.
+ *    Tracking bug: b/187393381.
+ *
+ * 3) Prefetch is not aware of item type.
+ *    RecyclerView separates timing metadata about different item types. For example, in play
+ *    store style UI, this allows RecyclerView to separately estimate the cost of a header,
+ *    separator, and item row. In this implementation, all of these would be averaged together in
+ *    the same estimation.
+ *    There is no view type concept in LazyColumn at all. What can we possible do:
+ *    a) Think of different item/items calls in the builder dsl as different view types
+ *    automatically. It is close enough but still not entirely correct if the user have something
+ *    like a list of elements which are objects of some sealed classes and they consider
+ *    different classes as completely different types
+ *    b) Maybe if we would be able to precompose on the different thread this issue is also not
+ *    so critical given that we don't need to calculate the deadline.
+ *    Tracking bug: 187393922
+ */
+private class LazyListPrefetcher(
+    private val subcomposeLayoutState: SubcomposeLayoutState,
+    private val lazyListState: LazyListState,
+    private val stateOfItemsProvider: State<LazyListItemsProvider>,
+    private val itemContentFactory: LazyListItemContentFactory,
+    private val view: View
+) : RememberObserver, LazyListOnScrolledListener, Runnable, Choreographer.FrameCallback {
+
+    /**
+     * Keeps the scrolling direction during the previous calculation in order to be able to
+     * detect the scrolling direction change.
+     */
+    private var wasScrollingForward: Boolean = false
+
+    /**
+     * The index scheduled to be prefetched (or the last prefetched index if the prefetch is
+     * done, in this case [precomposedSlotHandle] is not null and associated with this index.
+     */
+    private var indexToPrefetch: Int = -1
+
+    /**
+     * Non-null when the item with [indexToPrefetch] index was prefetched.
+     */
+    private var precomposedSlotHandle: PrecomposedSlotHandle? = null
+
+    /**
+     * Average time the prefetching operation takes. Keeping it allows us to not start the work
+     * if in this frame we are most likely not going to finish the work in time to not delay the
+     * next frame.
+     */
+    private var averagePrefetchTimeNs: Long = 0
+
+    private var prefetchScheduled = false
+
+    private val choreographer = Choreographer.getInstance()
+
+    init {
+        calculateFrameIntervalIfNeeded(view)
+    }
+
+    /**
+     * Callback to be executed when the prefetching is needed.
+     * [indexToPrefetch] will be used as an input.
+     */
+    override fun run() {
+        if (precomposedSlotHandle != null) {
+            // the precomposition happened already.
+            return
+        }
+        trace("compose:lazylist:prefetch") {
+            val latestFrameVsyncNs = TimeUnit.MILLISECONDS.toNanos(view.drawingTime)
+            val nextFrameNs = latestFrameVsyncNs + frameIntervalNs
+            val beforeNs = System.nanoTime()
+            if (beforeNs > nextFrameNs || beforeNs + averagePrefetchTimeNs < nextFrameNs) {
+                val index = indexToPrefetch
+                val itemProvider = stateOfItemsProvider.value
+                if (view.windowVisibility == View.VISIBLE &&
+                    index in 0 until itemProvider.itemsCount
+                ) {
+                    precomposedSlotHandle = subcompose(itemProvider, index)
+                    val prefetchTime = System.nanoTime() - beforeNs
+                    updateAveragePrefetchTime(prefetchTime)
+                }
+                prefetchScheduled = false
+            } else {
+                // there is not enough time left in this frame. we schedule a next frame callback
+                // in which we are going to post the message in the handler again.
+                choreographer.postFrameCallback(this)
+            }
+        }
+    }
+
+    /**
+     * Choreographer frame callback. It will be called when during the previous frame we didn't
+     * have enough time left. We will post a new message in the handler in order to try to
+     * prefetch again after this frame.
+     */
+    override fun doFrame(frameTimeNanos: Long) {
+        view.post(this)
+    }
+
+    private fun subcompose(
+        itemProvider: LazyListItemsProvider,
+        index: Int
+    ): PrecomposedSlotHandle {
+        val key = itemProvider.getKey(index)
+        val content = itemContentFactory.getContent(index, key)
+        return subcomposeLayoutState.precompose(key, content)
+    }
+
+    private fun updateAveragePrefetchTime(prefetchTime: Long) {
+        // Calculate a weighted moving average of time taken to compose an item. We use weighted
+        // moving average to bias toward more recent measurements, and to minimize storage /
+        // computation cost. (the idea is taken from RecycledViewPool)
+        averagePrefetchTimeNs = if (averagePrefetchTimeNs == 0L) {
+            prefetchTime
+        } else {
+            averagePrefetchTimeNs / 4 * 3 + prefetchTime / 4
+        }
+    }
+
+    /**
+     * The callback to be executed on every scroll.
+     */
+    override fun onScrolled(delta: Float) {
+        val info = lazyListState.layoutInfo
+        if (info.visibleItemsInfo.isNotEmpty()) {
+            val scrollingForward = delta < 0
+            val indexToPrefetch = if (scrollingForward) {
+                info.visibleItemsInfo.last().index + 1
+            } else {
+                info.visibleItemsInfo.first().index - 1
+            }
+            if (indexToPrefetch != this.indexToPrefetch &&
+                indexToPrefetch in 0 until info.totalItemsCount
+            ) {
+                val precomposedSlot = precomposedSlotHandle
+                if (precomposedSlot != null) {
+                    if (wasScrollingForward != scrollingForward) {
+                        // the scrolling direction has been changed which means the last prefetched
+                        // is not going to be reached anytime soon so it is safer to dispose it.
+                        // if this item is already visible it is safe to call the method anyway
+                        // as it will be no-op
+                        precomposedSlot.dispose()
+                    }
+                }
+                this.wasScrollingForward = scrollingForward
+                this.indexToPrefetch = indexToPrefetch
+                this.precomposedSlotHandle = null
+                if (!prefetchScheduled) {
+                    prefetchScheduled = true
+                    // schedule the prefetching
+                    view.post(this)
+                }
+            }
+        }
+    }
+
+    override fun onRemembered() {
+        lazyListState.onScrolledListener = this
+    }
+
+    override fun onForgotten() {
+        lazyListState.onScrolledListener = null
+        view.removeCallbacks(this)
+    }
+
+    override fun onAbandoned() {}
+
+    companion object {
+
+        /**
+         * The static cache in order to not gather the display refresh rate to often (expensive operation).
+         */
+        private var frameIntervalNs: Long = 0
+
+        private fun calculateFrameIntervalIfNeeded(view: View) {
+            // we only do this query once, statically, because it's very expensive (> 1ms)
+            if (frameIntervalNs == 0L) {
+                val display: Display? = view.display
+                var refreshRate = 60f
+                if (!view.isInEditMode && display != null) {
+                    val displayRefreshRate = display.refreshRate
+                    if (displayRefreshRate >= 30f) {
+                        // break 60 fps assumption if data from display appears valid
+                        refreshRate = displayRefreshRate
+                    }
+                }
+                frameIntervalNs = (1000000000 / refreshRate).toLong()
+            }
+        }
+    }
+}
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Border.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Border.kt
index 20cc811..75c988a 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Border.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Border.kt
@@ -36,8 +36,7 @@
 import androidx.compose.ui.unit.Dp
 
 /**
- * Modify element to add border with appearance specified with a [border] and a [shape], pad the
- * content by the [BorderStroke.width] and clip it.
+ * Modify element to add border with appearance specified with a [border] and a [shape] and clip it.
  *
  * @sample androidx.compose.foundation.samples.BorderSample()
  *
@@ -48,8 +47,8 @@
     border(width = border.width, brush = border.brush, shape = shape)
 
 /**
- * Returns a [Modifier] that adds border with appearance specified with [width], [color] and a
- * [shape], pads the content by the [width] and clips it.
+ * Modify element to add border with appearance specified with a [width], a [color] and a [shape]
+ * and clip it.
  *
  * @sample androidx.compose.foundation.samples.BorderSampleWithDataClass()
  *
@@ -61,8 +60,8 @@
     border(width, SolidColor(color), shape)
 
 /**
- * Returns a [Modifier] that adds border with appearance specified with [width], [brush] and a
- * [shape], pads the content by the [width] and clips it.
+ * Modify element to add border with appearance specified with a [width], a [brush] and a [shape]
+ * and clip it.
  *
  * @sample androidx.compose.foundation.samples.BorderSampleWithBrush()
  *
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/Scrollable.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/Scrollable.kt
index 1aa5b59..bbcdc14 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/Scrollable.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/Scrollable.kt
@@ -137,7 +137,9 @@
     val scrollLogic = rememberUpdatedState(
         ScrollingLogic(orientation, reverseDirection, nestedScrollDispatcher, controller, fling)
     )
-    val nestedScrollConnection = remember { scrollableNestedScrollConnection(scrollLogic) }
+    val nestedScrollConnection = remember(enabled) {
+        scrollableNestedScrollConnection(scrollLogic, enabled)
+    }
     val draggableState = remember { ScrollDraggableState(scrollLogic) }
 
     return draggable(
@@ -269,20 +271,29 @@
 }
 
 private fun scrollableNestedScrollConnection(
-    scrollLogic: State<ScrollingLogic>
+    scrollLogic: State<ScrollingLogic>,
+    enabled: Boolean
 ): NestedScrollConnection = object : NestedScrollConnection {
     override fun onPostScroll(
         consumed: Offset,
         available: Offset,
         source: NestedScrollSource
-    ): Offset = scrollLogic.value.performRawScroll(available)
+    ): Offset = if (enabled) {
+        scrollLogic.value.performRawScroll(available)
+    } else {
+        Offset.Zero
+    }
 
     override suspend fun onPostFling(
         consumed: Velocity,
         available: Velocity
     ): Velocity {
-        val velocityLeft = scrollLogic.value.doFlingAnimation(available)
-        return available - velocityLeft
+        return if (enabled) {
+            val velocityLeft = scrollLogic.value.doFlingAnimation(available)
+            available - velocityLeft
+        } else {
+            Velocity.Zero
+        }
     }
 }
 
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyList.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyList.kt
index 457e676e..141341d 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyList.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyList.kt
@@ -27,11 +27,13 @@
 import androidx.compose.foundation.layout.padding
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.State
+import androidx.compose.runtime.remember
 import androidx.compose.runtime.rememberCoroutineScope
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.draw.clipToBounds
 import androidx.compose.ui.layout.SubcomposeLayout
+import androidx.compose.ui.layout.SubcomposeLayoutState
 import androidx.compose.ui.platform.LocalLayoutDirection
 import androidx.compose.ui.unit.Density
 import androidx.compose.ui.unit.LayoutDirection
@@ -68,7 +70,11 @@
 
     val itemContentFactory = rememberItemContentFactory(stateOfItemsProvider, state)
 
+    val subcomposeLayoutState = remember { SubcomposeLayoutState(MaxItemsToRetainForReuse) }
+    LazyListPrefetcher(state, stateOfItemsProvider, itemContentFactory, subcomposeLayoutState)
+
     SubcomposeLayout(
+        subcomposeLayoutState,
         modifier
             .lazyListSemantics(
                 stateOfItemsProvider = stateOfItemsProvider,
@@ -177,3 +183,17 @@
         )
     }
 }
+
+private const val MaxItemsToRetainForReuse = 2
+
+/**
+ * Platform specific implementation of lazy list prefetching - precomposing next items in
+ * advance during the scrolling.
+ */
+@Composable
+internal expect fun LazyListPrefetcher(
+    lazyListState: LazyListState,
+    stateOfItemsProvider: State<LazyListItemsProvider>,
+    itemContentFactory: LazyListItemContentFactory,
+    subcomposeLayoutState: SubcomposeLayoutState
+)
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListItemContentFactory.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListItemContentFactory.kt
index f32e896..19d0e06 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListItemContentFactory.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListItemContentFactory.kt
@@ -100,8 +100,11 @@
         val content: @Composable () -> Unit = @Composable {
             val itemsProvider = itemsProvider.value
             if (index < itemsProvider.itemsCount) {
-                val content = itemsProvider.getContent(index, scope)
-                saveableStateHolder.SaveableStateProvider(key, content)
+                val key = itemsProvider.getKey(index)
+                if (key == this.key) {
+                    val content = itemsProvider.getContent(index, scope)
+                    saveableStateHolder.SaveableStateProvider(key, content)
+                }
             }
         }
     }
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListState.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListState.kt
index 1c9fe3f..082c787 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListState.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListState.kt
@@ -214,6 +214,8 @@
     override val isScrollInProgress: Boolean
         get() = scrollableState.isScrollInProgress
 
+    internal var onScrolledListener: LazyListOnScrolledListener? = null
+
     // TODO: Coroutine scrolling APIs will allow this to be private again once we have more
     //  fine-grained control over scrolling
     /*@VisibleForTesting*/
@@ -232,7 +234,9 @@
         // inside measuring we do scrollToBeConsumed.roundToInt() so there will be no scroll if
         // we have less than 0.5 pixels
         if (abs(scrollToBeConsumed) > 0.5f) {
+            val preScrollToBeConsumed = scrollToBeConsumed
             remeasurement.forceRemeasure()
+            onScrolledListener?.onScrolled(preScrollToBeConsumed - scrollToBeConsumed)
         }
 
         // here scrollToBeConsumed is already consumed during the forceRemeasure invocation
@@ -356,3 +360,7 @@
     override val viewportEndOffset = 0
     override val totalItemsCount = 0
 }
+
+internal interface LazyListOnScrolledListener {
+    fun onScrolled(delta: Float)
+}
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/CoreText.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/CoreText.kt
index 559b2da..c0c452c 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/CoreText.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/CoreText.kt
@@ -37,7 +37,7 @@
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.draw.drawBehind
 import androidx.compose.ui.geometry.Offset
-import androidx.compose.ui.graphics.Paint
+import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.graphics.drawscope.drawIntoCanvas
 import androidx.compose.ui.graphics.graphicsLayer
 import androidx.compose.ui.input.pointer.pointerInput
@@ -154,7 +154,7 @@
         placeholders = placeholders
     )
     state.onTextLayout = onTextLayout
-    state.selectionPaint.color = selectionBackgroundColor
+    state.selectionBackgroundColor = selectionBackgroundColor
 
     val controller = remember { TextController(state) }
     controller.update(selectionRegistrar)
@@ -491,12 +491,9 @@
     @OptIn(InternalFoundationTextApi::class)
     private fun Modifier.drawTextAndSelectionBehind(): Modifier =
         this.graphicsLayer().drawBehind {
-            drawIntoCanvas { canvas ->
-                val textLayoutResult = state.layoutResult
-                val selectionPaint = state.selectionPaint
+            state.layoutResult?.let {
                 val selection = selectionRegistrar?.subselections?.get(state.selectableId)
 
-                if (textLayoutResult == null) return@drawIntoCanvas
                 if (selection != null) {
                     val start = if (!selection.handlesCrossed) {
                         selection.start.offset
@@ -508,15 +505,15 @@
                     } else {
                         selection.start.offset
                     }
-                    TextDelegate.paintBackground(
-                        start,
-                        end,
-                        selectionPaint,
-                        canvas,
-                        textLayoutResult
-                    )
+
+                    if (start != end) {
+                        val selectionPath = it.multiParagraph.getPathForRange(start, end)
+                        drawPath(selectionPath, state.selectionBackgroundColor)
+                    }
                 }
-                TextDelegate.paint(canvas, textLayoutResult)
+                drawIntoCanvas { canvas ->
+                    TextDelegate.paint(canvas, it)
+                }
             }
         }
 }
@@ -542,8 +539,8 @@
     /** The global position calculated during the last notifyPosition callback */
     var previousGlobalPosition: Offset = Offset.Zero
 
-    /** The paint used to draw highlight background for selected text. */
-    val selectionPaint: Paint = Paint()
+    /** The background color of selection */
+    var selectionBackgroundColor: Color = Color.Unspecified
 }
 
 /**
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/TextDelegate.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/TextDelegate.kt
index 00b3410..ba4f6e4 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/TextDelegate.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/TextDelegate.kt
@@ -18,7 +18,6 @@
 
 import androidx.compose.runtime.Stable
 import androidx.compose.ui.graphics.Canvas
-import androidx.compose.ui.graphics.Paint
 import androidx.compose.ui.text.AnnotatedString
 import androidx.compose.ui.text.MultiParagraph
 import androidx.compose.ui.text.MultiParagraphIntrinsics
@@ -89,7 +88,7 @@
     val overflow: TextOverflow = TextOverflow.Clip,
     val density: Density,
     val resourceLoader: Font.ResourceLoader,
-    val placeholders: List<AnnotatedString.Range<Placeholder>> = listOf()
+    val placeholders: List<AnnotatedString.Range<Placeholder>> = emptyList()
 ) {
     /*@VisibleForTesting*/
     internal var paragraphIntrinsics: MultiParagraphIntrinsics? = null
@@ -203,7 +202,7 @@
         prevResult: TextLayoutResult? = null
     ): TextLayoutResult {
         if (prevResult != null && prevResult.canReuse(
-                text, style, maxLines, softWrap, overflow, density, layoutDirection,
+                text, style, placeholders, maxLines, softWrap, overflow, density, layoutDirection,
                 resourceLoader, constraints
             )
         ) {
@@ -270,27 +269,5 @@
         fun paint(canvas: Canvas, textLayoutResult: TextLayoutResult) {
             TextPainter.paint(canvas, textLayoutResult)
         }
-
-        /**
-         * Draws text background of the given range.
-         *
-         * If the given range is empty, do nothing.
-         *
-         * @param start inclusive start character offset of the drawing range.
-         * @param end exclusive end character offset of the drawing range.
-         * @param paint used to draw background.
-         * @param canvas the target canvas.
-         */
-        fun paintBackground(
-            start: Int,
-            end: Int,
-            paint: Paint,
-            canvas: Canvas,
-            textLayoutResult: TextLayoutResult
-        ) {
-            if (start == end) return
-            val selectionPath = textLayoutResult.multiParagraph.getPathForRange(start, end)
-            canvas.drawPath(selectionPath, paint)
-        }
     }
 }
\ No newline at end of file
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/TextLayoutHelper.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/TextLayoutHelper.kt
index f581b48..2f2051e 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/TextLayoutHelper.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/TextLayoutHelper.kt
@@ -17,6 +17,7 @@
 package androidx.compose.foundation.text
 
 import androidx.compose.ui.text.AnnotatedString
+import androidx.compose.ui.text.Placeholder
 import androidx.compose.ui.text.TextLayoutResult
 import androidx.compose.ui.text.TextStyle
 import androidx.compose.ui.text.font.Font
@@ -30,17 +31,19 @@
  *
  * @param text a text to be used for computing text layout.
  * @param style a text style to be used for computing text layout.
+ * @param placeholders a list of [Placeholder]s to be used for computing text layout.
  * @param maxLines a maximum number of lines to be used for computing text layout.
  * @param softWrap whether doing softwrap or not to be used for computing text layout.
  * @param overflow an overflow type to be used for computing text layout.
  * @param density a density to be used for computing text layout.
  * @param layoutDirection a layout direction to be used for computing text layout.
- * @param resourceLoader a resource loader to be used for computing text layout.
  * @param constraints a constraint to be used for computing text layout.
  */
+@OptIn(InternalFoundationTextApi::class)
 internal fun TextLayoutResult.canReuse(
     text: AnnotatedString,
     style: TextStyle,
+    placeholders: List<AnnotatedString.Range<Placeholder>>,
     maxLines: Int,
     softWrap: Boolean,
     overflow: TextOverflow,
@@ -55,6 +58,7 @@
     if (!(
         layoutInput.text == text &&
             layoutInput.style.canReuseLayout(style) &&
+            layoutInput.placeholders == placeholders &&
             layoutInput.maxLines == maxLines &&
             layoutInput.softWrap == softWrap &&
             layoutInput.overflow == overflow &&
@@ -70,7 +74,7 @@
     if (constraints.minWidth != layoutInput.constraints.minWidth) return false
 
     if (!(softWrap || overflow == TextOverflow.Ellipsis)) {
-        // If width does not mattter, we can result the same layout.
+        // If width does not matter, we can result the same layout.
         return true
     }
     return constraints.maxWidth == layoutInput.constraints.maxWidth
diff --git a/compose/foundation/foundation/src/desktopMain/kotlin/androidx/compose/foundation/Scrollbar.desktop.kt b/compose/foundation/foundation/src/desktopMain/kotlin/androidx/compose/foundation/Scrollbar.desktop.kt
index e7ff6c5..cbacbb1 100644
--- a/compose/foundation/foundation/src/desktopMain/kotlin/androidx/compose/foundation/Scrollbar.desktop.kt
+++ b/compose/foundation/foundation/src/desktopMain/kotlin/androidx/compose/foundation/Scrollbar.desktop.kt
@@ -32,9 +32,11 @@
 import androidx.compose.runtime.Immutable
 import androidx.compose.runtime.LaunchedEffect
 import androidx.compose.runtime.MutableState
+import androidx.compose.runtime.derivedStateOf
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberUpdatedState
 import androidx.compose.runtime.setValue
 import androidx.compose.runtime.staticCompositionLocalOf
 import androidx.compose.ui.Modifier
@@ -103,7 +105,7 @@
  *     val state = rememberScrollState(0f)
  *
  *     Box(Modifier.fillMaxSize()) {
- *         ScrollableColumn(state = state) {
+ *         Box(modifier = Modifier.verticalScroll(state)) {
  *             ...
  *         }
  *
@@ -135,7 +137,7 @@
 
 /**
  * Horizontal scrollbar that can be attached to some scrollable
- * component (ScrollableRow, LazyRow) and share common state with it.
+ * component (Modifier.verticalScroll(), LazyRow) and share common state with it.
  *
  * Can be placed independently.
  *
@@ -143,7 +145,7 @@
  *     val state = rememberScrollState(0f)
  *
  *     Box(Modifier.fillMaxSize()) {
- *         ScrollableRow(state = state) {
+ *         Box(modifier = Modifier.verticalScroll(state)) {
  *             ...
  *         }
  *
@@ -194,7 +196,13 @@
     }
 
     var containerSize by remember { mutableStateOf(0) }
-    var isHover by remember { mutableStateOf(false) }
+    var isHovered by remember { mutableStateOf(false) }
+
+    val isHighlighted by remember {
+        derivedStateOf {
+            isHovered || dragInteraction.value is DragInteraction.Start
+        }
+    }
 
     val minimalHeight = style.minimalHeight.toPx()
     val sliderAdapter = remember(adapter, containerSize, minimalHeight) {
@@ -213,7 +221,7 @@
     }
 
     val color by animateColorAsState(
-        if (isHover) style.hoverColor else style.unhoverColor,
+        if (isHighlighted) style.hoverColor else style.unhoverColor,
         animationSpec = TweenSpec(durationMillis = style.hoverDurationMillis)
     )
 
@@ -231,8 +239,8 @@
         },
         modifier
             .pointerMoveFilter(
-                onExit = { isHover = false; true },
-                onEnter = { isHover = true; true }
+                onExit = { isHovered = false; false },
+                onEnter = { isHovered = true; false }
             )
             .scrollOnPressOutsideSlider(isVertical, sliderAdapter, adapter, containerSize),
         measurePolicy
@@ -243,24 +251,29 @@
     interactionSource: MutableInteractionSource,
     draggedInteraction: MutableState<DragInteraction.Start?>,
     onDelta: (Offset) -> Unit
-): Modifier = pointerInput(interactionSource, draggedInteraction, onDelta) {
-    forEachGesture {
-        awaitPointerEventScope {
-            val down = awaitFirstDown(requireUnconsumed = false)
-            val interaction = DragInteraction.Start()
-            interactionSource.tryEmit(interaction)
-            draggedInteraction.value = interaction
-            val isSuccess = drag(down.id) { change ->
-                onDelta.invoke(change.positionChange())
-                change.consumePositionChange()
+): Modifier = composed {
+    val currentInteractionSource by rememberUpdatedState(interactionSource)
+    val currentDraggedInteraction by rememberUpdatedState(draggedInteraction)
+    val currentOnDelta by rememberUpdatedState(onDelta)
+    pointerInput(Unit) {
+        forEachGesture {
+            awaitPointerEventScope {
+                val down = awaitFirstDown(requireUnconsumed = false)
+                val interaction = DragInteraction.Start()
+                currentInteractionSource.tryEmit(interaction)
+                currentDraggedInteraction.value = interaction
+                val isSuccess = drag(down.id) { change ->
+                    currentOnDelta.invoke(change.positionChange())
+                    change.consumePositionChange()
+                }
+                val finishInteraction = if (isSuccess) {
+                    DragInteraction.Stop(interaction)
+                } else {
+                    DragInteraction.Cancel(interaction)
+                }
+                currentInteractionSource.tryEmit(finishInteraction)
+                currentDraggedInteraction.value = null
             }
-            val finishInteraction = if (isSuccess) {
-                DragInteraction.Stop(interaction)
-            } else {
-                DragInteraction.Cancel(interaction)
-            }
-            interactionSource.tryEmit(finishInteraction)
-            draggedInteraction.value = null
         }
     }
 }
@@ -338,7 +351,7 @@
 }
 
 /**
- * ScrollbarAdapter for ScrollableColumn and ScrollableRow
+ * ScrollbarAdapter for Modifier.verticalScroll and Modifier.horizontalScroll
  *
  * [scrollState] is instance of [ScrollState] which is used by scrollable component
  *
@@ -346,7 +359,7 @@
  *     val state = rememberScrollState(0f)
  *
  *     Box(Modifier.fillMaxSize()) {
- *         ScrollableColumn(state = state) {
+ *         Box(modifier = Modifier.verticalScroll(state)) {
  *             ...
  *         }
  *
@@ -483,7 +496,12 @@
             .coerceAtLeast(minHeight)
             .coerceAtMost(containerSize.toFloat())
 
-    private val scrollScale get() = (containerSize - size) / (contentSize - containerSize)
+    private val scrollScale: Float
+        get() {
+            val extraScrollbarSpace = containerSize - size
+            val extraContentSpace = contentSize - containerSize
+            return if (extraContentSpace == 0f) 1f else extraScrollbarSpace / extraContentSpace
+        }
 
     var position: Float
         get() = scrollScale * adapter.scrollOffset
diff --git a/compose/foundation/foundation/src/desktopMain/kotlin/androidx/compose/foundation/lazy/Lazy.desktop.kt b/compose/foundation/foundation/src/desktopMain/kotlin/androidx/compose/foundation/lazy/Lazy.desktop.kt
index 8f78d81..20e25b8 100644
--- a/compose/foundation/foundation/src/desktopMain/kotlin/androidx/compose/foundation/lazy/Lazy.desktop.kt
+++ b/compose/foundation/foundation/src/desktopMain/kotlin/androidx/compose/foundation/lazy/Lazy.desktop.kt
@@ -16,6 +16,20 @@
 
 package androidx.compose.foundation.lazy
 
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.State
+import androidx.compose.ui.layout.SubcomposeLayoutState
+
 internal actual fun getDefaultLazyKeyFor(index: Int): Any = DefaultLazyKey(index)
 
 private data class DefaultLazyKey(private val index: Int)
+
+@Composable
+internal actual fun LazyListPrefetcher(
+    lazyListState: LazyListState,
+    stateOfItemsProvider: State<LazyListItemsProvider>,
+    itemContentFactory: LazyListItemContentFactory,
+    subcomposeLayoutState: SubcomposeLayoutState
+) {
+    // there is no prefetch implementation on desktop yet
+}
diff --git a/compose/foundation/foundation/src/desktopTest/kotlin/androidx/compose/foundation/ScrollbarTest.kt b/compose/foundation/foundation/src/desktopTest/kotlin/androidx/compose/foundation/ScrollbarTest.kt
index 4e59d85..4b1be7e 100644
--- a/compose/foundation/foundation/src/desktopTest/kotlin/androidx/compose/foundation/ScrollbarTest.kt
+++ b/compose/foundation/foundation/src/desktopTest/kotlin/androidx/compose/foundation/ScrollbarTest.kt
@@ -56,7 +56,6 @@
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.delay
 import kotlinx.coroutines.runBlocking
-import org.jetbrains.skija.Surface
 import org.junit.Assert.assertEquals
 import org.junit.Ignore
 import org.junit.Rule
@@ -68,10 +67,6 @@
     @get:Rule
     val rule = createComposeRule()
 
-    // don't inline, surface controls canvas life time
-    private val surface = Surface.makeRasterN32Premul(100, 100)
-    private val canvas = surface.canvas
-
     @Test
     fun `drag slider to the middle`() {
         runBlocking(Dispatchers.Main) {
@@ -89,6 +84,21 @@
     }
 
     @Test
+    fun `drag slider when it is hidden`() {
+        runBlocking(Dispatchers.Main) {
+            rule.setContent {
+                TestBox(size = 100.dp, childSize = 20.dp, childCount = 1, scrollbarWidth = 10.dp)
+            }
+            rule.awaitIdle()
+            rule.onNodeWithTag("scrollbar").performGesture {
+                instantSwipe(start = Offset(0f, 25f), end = Offset(0f, 50f))
+            }
+            rule.awaitIdle()
+            rule.onNodeWithTag("box0").assertTopPositionInRootIsEqualTo(0.dp)
+        }
+    }
+
+    @Test
     fun `drag slider to the edges`() {
         runBlocking(Dispatchers.Main) {
             rule.setContent {
@@ -126,6 +136,22 @@
         }
     }
 
+    // TODO(demin): write a test when we support DesktopComposeTestRule.mainClock:
+    //  see https://github.com/JetBrains/compose-jb/issues/637
+//    fun `move mouse to the slider and drag it`() {
+//        ...
+//        rule.performMouseMove(0, 25)
+//        rule.mainClock.advanceTimeByFrame()
+//        down(Offset(0f, 25f))
+//        rule.mainClock.advanceTimeByFrame()
+//        moveTo(Offset(0f, 30f))
+//        rule.mainClock.advanceTimeByFrame()
+//        moveTo(Offset(0f, 50f))
+//        rule.mainClock.advanceTimeByFrame()
+//        up()
+//        ...
+//    }
+
     // TODO(demin): enable after we resolve b/171889442
     @Ignore("Enable after we resolve b/171889442")
     @Test
@@ -365,6 +391,10 @@
         )
     }
 
+    private fun ComposeTestRule.performMouseMove(x: Int, y: Int) {
+        (this as DesktopComposeTestRule).window.onMouseMoved(x, y)
+    }
+
     @Composable
     private fun TestBox(
         size: Dp,
diff --git a/compose/foundation/foundation/src/test/kotlin/androidx/compose/foundation/gestures/SuspendingGestureTestUtil.kt b/compose/foundation/foundation/src/test/kotlin/androidx/compose/foundation/gestures/SuspendingGestureTestUtil.kt
index 27ba0c3..48f0ef9 100644
--- a/compose/foundation/foundation/src/test/kotlin/androidx/compose/foundation/gestures/SuspendingGestureTestUtil.kt
+++ b/compose/foundation/foundation/src/test/kotlin/androidx/compose/foundation/gestures/SuspendingGestureTestUtil.kt
@@ -19,13 +19,14 @@
 import androidx.compose.runtime.Applier
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.Composer
-import androidx.compose.runtime.InternalComposeApi
 import androidx.compose.runtime.CompositionLocalProvider
-import androidx.compose.runtime.Recomposer
 import androidx.compose.runtime.ControlledComposition
-import androidx.compose.runtime.currentComposer
+import androidx.compose.runtime.InternalComposeApi
 import androidx.compose.runtime.MonotonicFrameClock
+import androidx.compose.runtime.Recomposer
+import androidx.compose.runtime.currentComposer
 import androidx.compose.runtime.withRunningRecomposer
+import androidx.compose.testutils.TestViewConfiguration
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.input.pointer.ConsumedData
@@ -39,7 +40,6 @@
 import androidx.compose.ui.materialize
 import androidx.compose.ui.platform.LocalDensity
 import androidx.compose.ui.platform.LocalViewConfiguration
-import androidx.compose.ui.platform.ViewConfiguration
 import androidx.compose.ui.unit.Density
 import androidx.compose.ui.unit.IntSize
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -346,18 +346,4 @@
         }
         override fun clear() {}
     }
-
-    private class TestViewConfiguration : ViewConfiguration {
-        override val longPressTimeoutMillis: Long
-            get() = 500
-
-        override val doubleTapTimeoutMillis: Long
-            get() = 300
-
-        override val doubleTapMinTimeMillis: Long
-            get() = 40
-
-        override val touchSlop: Float
-            get() = 18f
-    }
 }
\ No newline at end of file
diff --git a/compose/foundation/foundation/src/test/kotlin/androidx/compose/foundation/text/TextLayoutHelperTest.kt b/compose/foundation/foundation/src/test/kotlin/androidx/compose/foundation/text/TextLayoutHelperTest.kt
index 5242735..7af3454 100644
--- a/compose/foundation/foundation/src/test/kotlin/androidx/compose/foundation/text/TextLayoutHelperTest.kt
+++ b/compose/foundation/foundation/src/test/kotlin/androidx/compose/foundation/text/TextLayoutHelperTest.kt
@@ -17,6 +17,8 @@
 package androidx.compose.foundation.text
 
 import androidx.compose.ui.text.AnnotatedString
+import androidx.compose.ui.text.Placeholder
+import androidx.compose.ui.text.PlaceholderVerticalAlign
 import androidx.compose.ui.text.TextLayoutInput
 import androidx.compose.ui.text.TextLayoutResult
 import androidx.compose.ui.text.TextStyle
@@ -27,6 +29,7 @@
 import androidx.compose.ui.unit.IntSize
 import androidx.compose.ui.unit.LayoutDirection
 import androidx.compose.ui.unit.em
+import androidx.compose.ui.unit.sp
 import com.google.common.truth.Truth.assertThat
 import com.nhaarman.mockitokotlin2.mock
 import org.junit.Before
@@ -64,12 +67,13 @@
     }
 
     @Test
-    fun testCanResue_same() {
+    fun testCanReuse_same() {
         val constraints = Constraints.fixedWidth(100)
         assertThat(
             referenceResult.canReuse(
                 text = AnnotatedString.Builder("Hello, World").toAnnotatedString(),
                 style = TextStyle(),
+                placeholders = emptyList(),
                 maxLines = 1,
                 softWrap = true,
                 overflow = TextOverflow.Ellipsis,
@@ -82,12 +86,13 @@
     }
 
     @Test
-    fun testCanResue_different_text() {
+    fun testCanReuse_different_text() {
         val constraints = Constraints.fixedWidth(100)
         assertThat(
             referenceResult.canReuse(
                 text = AnnotatedString.Builder("Hello, Android").toAnnotatedString(),
                 style = TextStyle(),
+                placeholders = emptyList(),
                 maxLines = 1,
                 softWrap = true,
                 overflow = TextOverflow.Ellipsis,
@@ -100,12 +105,13 @@
     }
 
     @Test
-    fun testCanResue_different_style() {
+    fun testCanReuse_different_style() {
         val constraints = Constraints.fixedWidth(100)
         assertThat(
             referenceResult.canReuse(
                 text = AnnotatedString.Builder("Hello, World").toAnnotatedString(),
                 style = TextStyle(fontSize = 1.5.em),
+                placeholders = emptyList(),
                 maxLines = 1,
                 softWrap = true,
                 overflow = TextOverflow.Ellipsis,
@@ -118,12 +124,13 @@
     }
 
     @Test
-    fun testCanResue_different_maxLines() {
+    fun testCanReuse_different_maxLines() {
         val constraints = Constraints.fixedWidth(100)
         assertThat(
             referenceResult.canReuse(
                 text = AnnotatedString.Builder("Hello, World").toAnnotatedString(),
                 style = TextStyle(),
+                placeholders = emptyList(),
                 maxLines = 2,
                 softWrap = true,
                 overflow = TextOverflow.Ellipsis,
@@ -136,12 +143,13 @@
     }
 
     @Test
-    fun testCanResue_different_softWrap() {
+    fun testCanReuse_different_softWrap() {
         val constraints = Constraints.fixedWidth(100)
         assertThat(
             referenceResult.canReuse(
                 text = AnnotatedString.Builder("Hello, World").toAnnotatedString(),
                 style = TextStyle(),
+                placeholders = emptyList(),
                 maxLines = 1,
                 softWrap = false,
                 overflow = TextOverflow.Ellipsis,
@@ -154,12 +162,13 @@
     }
 
     @Test
-    fun testCanResue_different_overflow() {
+    fun testCanReuse_different_overflow() {
         val constraints = Constraints.fixedWidth(100)
         assertThat(
             referenceResult.canReuse(
                 text = AnnotatedString.Builder("Hello, World").toAnnotatedString(),
                 style = TextStyle(),
+                placeholders = emptyList(),
                 maxLines = 1,
                 softWrap = true,
                 overflow = TextOverflow.Clip,
@@ -172,12 +181,13 @@
     }
 
     @Test
-    fun testCanResue_different_density() {
+    fun testCanReuse_different_density() {
         val constraints = Constraints.fixedWidth(100)
         assertThat(
             referenceResult.canReuse(
                 text = AnnotatedString.Builder("Hello, World").toAnnotatedString(),
                 style = TextStyle(),
+                placeholders = emptyList(),
                 maxLines = 1,
                 softWrap = true,
                 overflow = TextOverflow.Ellipsis,
@@ -190,12 +200,13 @@
     }
 
     @Test
-    fun testCanResue_different_layoutDirection() {
+    fun testCanReuse_different_layoutDirection() {
         val constraints = Constraints.fixedWidth(100)
         assertThat(
             referenceResult.canReuse(
                 text = AnnotatedString.Builder("Hello, World").toAnnotatedString(),
                 style = TextStyle(),
+                placeholders = emptyList(),
                 maxLines = 1,
                 softWrap = true,
                 overflow = TextOverflow.Ellipsis,
@@ -208,12 +219,13 @@
     }
 
     @Test
-    fun testCanResue_different_resourceLoader() {
+    fun testCanReuse_different_resourceLoader() {
         val constraints = Constraints.fixedWidth(100)
         assertThat(
             referenceResult.canReuse(
                 text = AnnotatedString.Builder("Hello, World").toAnnotatedString(),
                 style = TextStyle(),
+                placeholders = emptyList(),
                 maxLines = 1,
                 softWrap = true,
                 overflow = TextOverflow.Ellipsis,
@@ -226,11 +238,36 @@
     }
 
     @Test
-    fun testCanResue_different_constraints() {
+    fun testCanReuse_different_constraints() {
         assertThat(
             referenceResult.canReuse(
                 text = AnnotatedString.Builder("Hello, World").toAnnotatedString(),
                 style = TextStyle(),
+                placeholders = emptyList(),
+                maxLines = 1,
+                softWrap = true,
+                overflow = TextOverflow.Ellipsis,
+                density = Density(1.0f),
+                layoutDirection = LayoutDirection.Ltr,
+                resourceLoader = resourceLoader,
+                constraints = Constraints.fixedWidth(200)
+            )
+        ).isFalse()
+    }
+
+    @Test
+    fun testCanReuse_different_placeholders() {
+        assertThat(
+            referenceResult.canReuse(
+                text = AnnotatedString.Builder("Hello, World").toAnnotatedString(),
+                style = TextStyle(),
+                placeholders = listOf(
+                    AnnotatedString.Range(
+                        item = Placeholder(10.sp, 20.sp, PlaceholderVerticalAlign.AboveBaseline),
+                        start = 0,
+                        end = 5
+                    )
+                ),
                 maxLines = 1,
                 softWrap = true,
                 overflow = TextOverflow.Ellipsis,
diff --git a/compose/integration-tests/docs-snippets/src/main/java/androidx/compose/integration/docs/interoperability/InteropArchitecture.kt b/compose/integration-tests/docs-snippets/src/main/java/androidx/compose/integration/docs/interoperability/InteropArchitecture.kt
new file mode 100644
index 0000000..566c2ab
--- /dev/null
+++ b/compose/integration-tests/docs-snippets/src/main/java/androidx/compose/integration/docs/interoperability/InteropArchitecture.kt
@@ -0,0 +1,217 @@
+/*
+ * 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.
+ */
+
+// ktlint-disable indent https://github.com/pinterest/ktlint/issues/967
+// Ignore lint warnings in documentation snippets
+@file:Suppress(
+    "unused", "UNUSED_PARAMETER", "UNUSED_VARIABLE", "UNUSED_ANONYMOUS_PARAMETER",
+    "RedundantSuspendModifier", "CascadeIf", "ClassName", "RemoveExplicitTypeArguments",
+    "ControlFlowWithEmptyBody", "PropertyName", "CanBeParameter"
+)
+
+package androidx.compose.integration.docs.interoperability
+
+import android.content.Context
+import android.os.Bundle
+import android.util.AttributeSet
+import android.widget.LinearLayout
+import android.widget.TextView
+import androidx.activity.OnBackPressedCallback
+import androidx.activity.OnBackPressedDispatcher
+import androidx.activity.compose.setContent
+import androidx.appcompat.app.AppCompatActivity
+import androidx.compose.foundation.layout.Column
+import androidx.compose.material.MaterialTheme
+import androidx.compose.material.Text
+import androidx.compose.material.TextField
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.DisposableEffect
+import androidx.compose.runtime.SideEffect
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.livedata.observeAsState
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberUpdatedState
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.platform.ComposeView
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.ViewModelProvider
+import androidx.lifecycle.viewmodel.compose.viewModel
+import androidx.navigation.compose.NavHost
+import androidx.navigation.compose.composable
+import androidx.navigation.compose.rememberNavController
+
+/**
+ * This file lets DevRel track changes to snippets present in
+ * https://developer.android.com/jetpack/compose/interop/compose-in-existing-arch
+ *
+ * No action required if it's modified.
+ */
+
+private object InteropArchitectureSnippet1 {
+    class ExampleActivity : AppCompatActivity() {
+        override fun onCreate(savedInstanceState: Bundle?) {
+            super.onCreate(savedInstanceState)
+
+            setContent {
+                MaterialTheme {
+                    Column {
+                        Greeting("user1")
+                        Greeting("user2")
+                    }
+                }
+            }
+        }
+    }
+
+    @Composable
+    fun Greeting(userId: String) {
+        val greetingViewModel: GreetingViewModel = viewModel(
+            factory = GreetingViewModelFactory(userId)
+        )
+        val messageUser by greetingViewModel.message.observeAsState("")
+
+        Text(messageUser)
+    }
+
+    class GreetingViewModel(private val userId: String) : ViewModel() {
+        private val _message = MutableLiveData("Hi $userId")
+        val message: LiveData<String> = _message
+    }
+
+    class GreetingViewModelFactory(private val userId: String) : ViewModelProvider.Factory {
+        @Suppress("UNCHECKED_CAST")
+        override fun <T : ViewModel?> create(modelClass: Class<T>): T {
+            return GreetingViewModel(userId) as T
+        }
+    }
+}
+
+private object InteropArchitectureSnippet2 {
+    @Composable
+    fun MyScreen() {
+        NavHost(rememberNavController(), startDestination = "profile/{userId}") {
+            /* ... */
+            composable("profile/{userId}") { backStackEntry ->
+                Greeting(backStackEntry.arguments?.getString("userId") ?: "")
+            }
+        }
+    }
+
+    @Composable
+    fun Greeting(userId: String) {
+        val greetingViewModel: GreetingViewModel = viewModel(
+            factory = GreetingViewModelFactory(userId)
+        )
+        val messageUser by greetingViewModel.message.observeAsState("")
+
+        Text(messageUser)
+    }
+}
+
+private object InteropArchitectureSnippet3 {
+    @Composable
+    fun BackHandler(
+        enabled: Boolean,
+        backDispatcher: OnBackPressedDispatcher,
+        onBack: () -> Unit
+    ) {
+
+        // Safely update the current `onBack` lambda when a new one is provided
+        val currentOnBack by rememberUpdatedState(onBack)
+
+        // Remember in Composition a back callback that calls the `onBack` lambda
+        val backCallback = remember {
+            // Always intercept back events. See the SideEffect for a more complete version
+            object : OnBackPressedCallback(true) {
+                override fun handleOnBackPressed() {
+                    currentOnBack()
+                }
+            }
+        }
+
+        // On every successful composition, update the callback with the `enabled` value
+        // to tell `backCallback` whether back events should be intercepted or not
+        SideEffect {
+            backCallback.isEnabled = enabled
+        }
+
+        // If `backDispatcher` changes, dispose and reset the effect
+        DisposableEffect(backDispatcher) {
+            // Add callback to the backDispatcher
+            backDispatcher.addCallback(backCallback)
+
+            // When the effect leaves the Composition, remove the callback
+            onDispose {
+                backCallback.remove()
+            }
+        }
+    }
+}
+
+private object InteropArchitectureSnippet4 {
+    class CustomViewGroup @JvmOverloads constructor(
+        context: Context,
+        attrs: AttributeSet? = null,
+        defStyle: Int = 0
+    ) : LinearLayout(context, attrs, defStyle) {
+
+        // Source of truth in the View system as mutableStateOf
+        // to make it thread-safe for Compose
+        private var text by mutableStateOf("")
+
+        private val textView: TextView
+
+        init {
+            orientation = VERTICAL
+
+            textView = TextView(context)
+            val composeView = ComposeView(context).apply {
+                setContent {
+                    MaterialTheme {
+                        TextField(value = text, onValueChange = { updateState(it) })
+                    }
+                }
+            }
+
+            addView(textView)
+            addView(composeView)
+        }
+
+        // Update both the source of truth and the TextView
+        private fun updateState(newValue: String) {
+            text = newValue
+            textView.text = newValue
+        }
+    }
+}
+
+/*
+Fakes needed for snippets to build:
+ */
+
+private class GreetingViewModel(userId: String) : ViewModel() {
+    val _message = MutableLiveData("")
+    val message: LiveData<String> = _message
+}
+private class GreetingViewModelFactory(private val userId: String) : ViewModelProvider.Factory {
+    @Suppress("UNCHECKED_CAST")
+    override fun <T : ViewModel?> create(modelClass: Class<T>): T {
+        return GreetingViewModel(userId) as T
+    }
+}
diff --git a/compose/integration-tests/docs-snippets/src/main/java/androidx/compose/integration/docs/interoperability/InteropUi.kt b/compose/integration-tests/docs-snippets/src/main/java/androidx/compose/integration/docs/interoperability/InteropUi.kt
new file mode 100644
index 0000000..5dc4770
--- /dev/null
+++ b/compose/integration-tests/docs-snippets/src/main/java/androidx/compose/integration/docs/interoperability/InteropUi.kt
@@ -0,0 +1,248 @@
+/*
+ * 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.
+ */
+
+// ktlint-disable indent https://github.com/pinterest/ktlint/issues/967
+// Ignore lint warnings in documentation snippets
+@file:Suppress(
+    "unused", "UNUSED_PARAMETER", "UNUSED_VARIABLE", "UNUSED_ANONYMOUS_PARAMETER",
+    "RedundantSuspendModifier", "CascadeIf", "ClassName", "RemoveExplicitTypeArguments",
+    "ControlFlowWithEmptyBody", "PropertyName", "CanBeParameter", "PackageDirectoryMismatch"
+)
+
+package androidx.compose.integration.docs.interoperabilityui
+
+import android.app.Activity
+import android.content.Context
+import android.os.Bundle
+import android.util.AttributeSet
+import android.view.LayoutInflater
+import androidx.activity.compose.setContent
+import androidx.appcompat.app.AppCompatActivity
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.BoxWithConstraints
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material.Button
+import androidx.compose.material.ButtonDefaults
+import androidx.compose.material.FloatingActionButton
+import androidx.compose.material.MaterialTheme
+import androidx.compose.material.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.AbstractComposeView
+import androidx.compose.ui.unit.dp
+
+/**
+ * This file lets DevRel track changes to snippets present in
+ * https://developer.android.com/jetpack/compose/interop/compose-in-existing-ui
+ *
+ * No action required if it's modified.
+ */
+
+private object InteropUiSnippet1 {
+    @Composable
+    fun CallToActionButton(
+        text: String,
+        onClick: () -> Unit,
+        modifier: Modifier = Modifier,
+    ) {
+        Button(
+            colors = ButtonDefaults.buttonColors(
+                backgroundColor = MaterialTheme.colors.secondary
+            ),
+            onClick = onClick,
+            modifier = modifier,
+        ) {
+            Text(text)
+        }
+    }
+
+    class CallToActionViewButton @JvmOverloads constructor(
+        context: Context,
+        attrs: AttributeSet? = null,
+        defStyle: Int = 0
+    ) : AbstractComposeView(context, attrs, defStyle) {
+
+        var text by mutableStateOf<String>("")
+        var onClick by mutableStateOf<() -> Unit>({})
+
+        @Composable
+        override fun Content() {
+            YourAppTheme {
+                CallToActionButton(text, onClick)
+            }
+        }
+    }
+}
+
+private object InteropUiSnippet2 {
+    class ExampleActivity : Activity() {
+
+        private lateinit var binding: ActivityExampleBinding
+
+        override fun onCreate(savedInstanceState: Bundle?) {
+            super.onCreate(savedInstanceState)
+            binding = ActivityExampleBinding.inflate(layoutInflater)
+            setContentView(binding.root)
+
+            binding.callToAction.apply {
+                text = getString(R.string.something)
+                onClick = { /* Do something */ }
+            }
+        }
+    }
+}
+
+private object InteropUiSnippet3 {
+    // import com.google.android.material.composethemeadapter.MdcTheme
+
+    class ExampleActivity : AppCompatActivity() {
+        override fun onCreate(savedInstanceState: Bundle?) {
+            super.onCreate(savedInstanceState)
+
+            setContent {
+                // Use MdcTheme instead of MaterialTheme
+                // Colors, typography, and shape have been read from the
+                // View-based theme used in this Activity
+                MdcTheme {
+                    ExampleComposable(/*...*/)
+                }
+            }
+        }
+    }
+}
+
+private object InteropUiSnippet4 {
+    class ExampleActivity : AppCompatActivity() {
+        override fun onCreate(savedInstanceState: Bundle?) {
+            super.onCreate(savedInstanceState)
+
+            setContent {
+                AppCompatTheme {
+                    // Colors, typography, and shape have been read from the
+                    // View-based theme used in this Activity
+                    ExampleComposable(/*...*/)
+                }
+            }
+        }
+    }
+}
+
+private object InteropUiSnippet5 {
+    class ExampleActivity : AppCompatActivity() {
+        override fun onCreate(savedInstanceState: Bundle?) {
+            super.onCreate(savedInstanceState)
+
+            WindowCompat.setDecorFitsSystemWindows(window, false)
+
+            setContent {
+                MaterialTheme {
+                    ProvideWindowInsets {
+                        MyScreen()
+                    }
+                }
+            }
+        }
+    }
+
+    @Composable
+    fun MyScreen() {
+        Box {
+            FloatingActionButton(
+                modifier = Modifier
+                    .align(Alignment.BottomEnd)
+                    .padding(16.dp) // normal 16dp of padding for FABs
+                    .navigationBarsPadding(), // Move it out from under the nav bar
+                onClick = { }
+            ) {
+                Icon( /* ... */)
+            }
+        }
+    }
+}
+
+private object InteropUiSnippet6 {
+    @Composable
+    fun MyComposable() {
+        BoxWithConstraints {
+            if (minWidth < 480.dp) {
+                /* Show grid with 4 columns */
+            } else if (minWidth < 720.dp) {
+                /* Show grid with 8 columns */
+            } else {
+                /* Show grid with 12 columns */
+            }
+        }
+    }
+}
+
+/*
+Fakes needed for snippets to build:
+ */
+
+private object R {
+    object string {
+        const val something = 1
+    }
+}
+
+private fun ExampleComposable() {}
+@Composable
+private fun MdcTheme(content: @Composable () -> Unit) {
+}
+
+@Composable
+private fun AppCompatTheme(content: @Composable () -> Unit) {
+}
+
+@Composable
+private fun BlueTheme(content: @Composable () -> Unit) {
+}
+
+@Composable
+private fun PinkTheme(content: @Composable () -> Unit) {
+}
+
+@Composable
+private fun YourAppTheme(content: @Composable () -> Unit) {
+}
+
+@Composable
+private fun ProvideWindowInsets(content: @Composable () -> Unit) {
+}
+
+@Composable
+private fun Icon() {
+}
+
+private class WindowCompat {
+    companion object {
+        fun setDecorFitsSystemWindows(window: Any, bool: Boolean) {}
+    }
+}
+
+private fun Modifier.navigationBarsPadding(): Modifier = this
+
+private class ActivityExampleBinding {
+    val root: Int = 0
+    lateinit var callToAction: InteropUiSnippet1.CallToActionViewButton
+    companion object {
+        fun inflate(li: LayoutInflater): ActivityExampleBinding { TODO() }
+    }
+}
diff --git a/compose/integration-tests/docs-snippets/src/main/java/androidx/compose/integration/docs/interoperability/Interoperability.kt b/compose/integration-tests/docs-snippets/src/main/java/androidx/compose/integration/docs/interoperability/Interoperability.kt
index ab8da92..82f2c4a 100644
--- a/compose/integration-tests/docs-snippets/src/main/java/androidx/compose/integration/docs/interoperability/Interoperability.kt
+++ b/compose/integration-tests/docs-snippets/src/main/java/androidx/compose/integration/docs/interoperability/Interoperability.kt
@@ -24,7 +24,6 @@
 
 package androidx.compose.integration.docs.interoperability
 
-import android.app.Activity
 import android.content.BroadcastReceiver
 import android.content.Context
 import android.content.Intent
@@ -32,58 +31,36 @@
 import android.graphics.Bitmap
 import android.graphics.Color
 import android.os.Bundle
-import android.util.AttributeSet
 import android.view.LayoutInflater
 import android.view.View
 import android.view.ViewGroup
 import android.widget.LinearLayout
-import android.widget.TextView
-import androidx.activity.OnBackPressedCallback
-import androidx.activity.OnBackPressedDispatcher
 import androidx.activity.compose.setContent
 import androidx.appcompat.app.AppCompatActivity
-import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.BoxWithConstraints
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.foundation.layout.padding
 import androidx.compose.integration.docs.databinding.ExampleLayoutBinding
 import androidx.compose.material.Button
-import androidx.compose.material.ButtonDefaults
-import androidx.compose.material.FloatingActionButton
 import androidx.compose.material.MaterialTheme
 import androidx.compose.material.Text
-import androidx.compose.material.TextField
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.DisposableEffect
-import androidx.compose.runtime.SideEffect
 import androidx.compose.runtime.State
 import androidx.compose.runtime.getValue
-import androidx.compose.runtime.livedata.observeAsState
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
 import androidx.compose.runtime.rememberUpdatedState
-import androidx.compose.runtime.setValue
-import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
-import androidx.compose.ui.platform.AbstractComposeView
 import androidx.compose.ui.platform.ComposeView
 import androidx.compose.ui.platform.LocalContext
-import androidx.compose.ui.unit.dp
 import androidx.compose.ui.viewinterop.AndroidView
 import androidx.compose.ui.viewinterop.AndroidViewBinding
-import androidx.lifecycle.LiveData
 import androidx.lifecycle.MutableLiveData
 import androidx.lifecycle.ViewModel
-import androidx.lifecycle.ViewModelProvider
-import androidx.lifecycle.viewmodel.compose.viewModel
-import androidx.navigation.compose.NavHost
-import androidx.navigation.compose.composable
-import androidx.navigation.compose.rememberNavController
 
 /**
  * This file lets DevRel track changes to snippets present in
- * https://developer.android.com/jetpack/compose/interop
+ * https://developer.android.com/jetpack/compose/interop/interop-apis
  *
  * No action required if it's modified.
  */
@@ -251,274 +228,6 @@
     }
 }
 
-private object InteropSnippet9 {
-    // import com.google.android.material.composethemeadapter.MdcTheme
-
-    class ExampleActivity : AppCompatActivity() {
-        override fun onCreate(savedInstanceState: Bundle?) {
-            super.onCreate(savedInstanceState)
-
-            setContent {
-                // Use MdcTheme instead of MaterialTheme
-                // Colors, typography, and shape have been read from the
-                // View-based theme used in this Activity
-                MdcTheme {
-                    ExampleComposable(/*...*/)
-                }
-            }
-        }
-    }
-}
-
-private object InteropSnippet10 {
-    class ExampleActivity : AppCompatActivity() {
-        override fun onCreate(savedInstanceState: Bundle?) {
-            super.onCreate(savedInstanceState)
-
-            setContent {
-                AppCompatTheme {
-                    // Colors, typography, and shape have been read from the
-                    // View-based theme used in this Activity
-                    ExampleComposable(/*...*/)
-                }
-            }
-        }
-    }
-}
-
-private object InteropSnippet11 {
-    class ExampleActivity : AppCompatActivity() {
-        override fun onCreate(savedInstanceState: Bundle?) {
-            super.onCreate(savedInstanceState)
-
-            WindowCompat.setDecorFitsSystemWindows(window, false)
-
-            setContent {
-                MaterialTheme {
-                    ProvideWindowInsets {
-                        MyScreen()
-                    }
-                }
-            }
-        }
-    }
-
-    @Composable
-    fun MyScreen() {
-        Box {
-            FloatingActionButton(
-                modifier = Modifier
-                    .align(Alignment.BottomEnd)
-                    .padding(16.dp) // normal 16dp of padding for FABs
-                    .navigationBarsPadding(), // Move it out from under the nav bar
-                onClick = { }
-            ) {
-                Icon( /* ... */)
-            }
-        }
-    }
-}
-
-private object InteropSnippet12 {
-    @Composable
-    fun MyComposable() {
-        BoxWithConstraints {
-            if (minWidth < 480.dp) {
-                /* Show grid with 4 columns */
-            } else if (minWidth < 720.dp) {
-                /* Show grid with 8 columns */
-            } else {
-                /* Show grid with 12 columns */
-            }
-        }
-    }
-}
-
-private object InteropSnippet13 {
-    class ExampleActivity : AppCompatActivity() {
-        override fun onCreate(savedInstanceState: Bundle?) {
-            super.onCreate(savedInstanceState)
-
-            setContent {
-                MaterialTheme {
-                    Column {
-                        Greeting("user1")
-                        Greeting("user2")
-                    }
-                }
-            }
-        }
-    }
-
-    @Composable
-    fun Greeting(userId: String) {
-        val greetingViewModel: GreetingViewModel = viewModel(
-            factory = GreetingViewModelFactory(userId)
-        )
-        val messageUser by greetingViewModel.message.observeAsState("")
-
-        Text(messageUser)
-    }
-
-    class GreetingViewModel(private val userId: String) : ViewModel() {
-        private val _message = MutableLiveData("Hi $userId")
-        val message: LiveData<String> = _message
-    }
-}
-
-private object InteropSnippet14 {
-    @Composable
-    fun MyScreen() {
-        NavHost(rememberNavController(), startDestination = "profile/{userId}") {
-            /* ... */
-            composable("profile/{userId}") { backStackEntry ->
-                Greeting(backStackEntry.arguments?.getString("userId") ?: "")
-            }
-        }
-    }
-
-    @Composable
-    fun Greeting(userId: String) {
-        val greetingViewModel: GreetingViewModel = viewModel(
-            factory = GreetingViewModelFactory(userId)
-        )
-        val messageUser by greetingViewModel.message.observeAsState("")
-
-        Text(messageUser)
-    }
-}
-
-private object InteropSnippet15 {
-    @Composable
-    fun BackHandler(
-        enabled: Boolean,
-        backDispatcher: OnBackPressedDispatcher,
-        onBack: () -> Unit
-    ) {
-
-        // Safely update the current `onBack` lambda when a new one is provided
-        val currentOnBack by rememberUpdatedState(onBack)
-
-        // Remember in Composition a back callback that calls the `onBack` lambda
-        val backCallback = remember {
-            // Always intercept back events. See the SideEffect for a more complete version
-            object : OnBackPressedCallback(true) {
-                override fun handleOnBackPressed() {
-                    currentOnBack()
-                }
-            }
-        }
-
-        // On every successful composition, update the callback with the `enabled` value
-        // to tell `backCallback` whether back events should be intercepted or not
-        SideEffect {
-            backCallback.isEnabled = enabled
-        }
-
-        // If `backDispatcher` changes, dispose and reset the effect
-        DisposableEffect(backDispatcher) {
-            // Add callback to the backDispatcher
-            backDispatcher.addCallback(backCallback)
-
-            // When the effect leaves the Composition, remove the callback
-            onDispose {
-                backCallback.remove()
-            }
-        }
-    }
-}
-
-private object InteropSnippet16 {
-    class CustomViewGroup @JvmOverloads constructor(
-        context: Context,
-        attrs: AttributeSet? = null,
-        defStyle: Int = 0
-    ) : LinearLayout(context, attrs, defStyle) {
-
-        // Source of truth in the View system as mutableStateOf
-        // to make it thread-safe for Compose
-        private var text by mutableStateOf("")
-
-        private val textView: TextView
-
-        init {
-            orientation = VERTICAL
-
-            textView = TextView(context)
-            val composeView = ComposeView(context).apply {
-                setContent {
-                    MaterialTheme {
-                        TextField(value = text, onValueChange = { updateState(it) })
-                    }
-                }
-            }
-
-            addView(textView)
-            addView(composeView)
-        }
-
-        // Update both the source of truth and the TextView
-        private fun updateState(newValue: String) {
-            text = newValue
-            textView.text = newValue
-        }
-    }
-}
-
-private object InteropSnippet17 {
-    @Composable
-    fun CallToActionButton(
-        text: String,
-        onClick: () -> Unit,
-        modifier: Modifier = Modifier,
-    ) {
-        Button(
-            colors = ButtonDefaults.buttonColors(
-                backgroundColor = MaterialTheme.colors.secondary
-            ),
-            onClick = onClick,
-            modifier = modifier,
-        ) {
-            Text(text)
-        }
-    }
-
-    class CallToActionViewButton @JvmOverloads constructor(
-        context: Context,
-        attrs: AttributeSet? = null,
-        defStyle: Int = 0
-    ) : AbstractComposeView(context, attrs, defStyle) {
-
-        var text by mutableStateOf<String>("")
-        var onClick by mutableStateOf<() -> Unit>({})
-
-        @Composable
-        override fun Content() {
-            YourAppTheme {
-                CallToActionButton(text, onClick)
-            }
-        }
-    }
-}
-
-private object InteropSnippet18 {
-    class ExampleActivity : Activity() {
-
-        private lateinit var binding: ActivityExampleBinding
-
-        override fun onCreate(savedInstanceState: Bundle?) {
-            super.onCreate(savedInstanceState)
-            binding = ActivityExampleBinding.inflate(layoutInflater)
-            setContentView(binding.root)
-
-            binding.callToAction.apply {
-                text = getString(R.string.something)
-                onClick = { /* Do something */ }
-            }
-        }
-    }
-}
-
 private object InteropSnippet19 {
     @Composable
     fun SystemBroadcastReceiver(
@@ -578,19 +287,18 @@
     object string {
         const val ok = 4
         const val plane_description = 5
-        const val something = 6
     }
 
     object dimen {
-        const val padding_small = 7
+        const val padding_small = 6
     }
 
     object drawable {
-        const val ic_plane = 8
+        const val ic_plane = 7
     }
 
     object color {
-        const val Blue700 = 9
+        const val Blue700 = 8
     }
 }
 
@@ -605,7 +313,7 @@
 
 private val data = DataExample()
 private fun startActivity(): Nothing = TODO()
-class ExampleViewModel : ViewModel() {
+private class ExampleViewModel : ViewModel() {
     val exampleLiveData = MutableLiveData(" ")
 }
 
@@ -627,35 +335,6 @@
     fun into(listener: ExampleImageLoader.Listener) {}
 }
 
-private fun ExampleComposable() {}
-@Composable
-private fun MdcTheme(content: @Composable () -> Unit) {
-}
-
-@Composable
-private fun AppCompatTheme(content: @Composable () -> Unit) {
-}
-
-@Composable
-private fun BlueTheme(content: @Composable () -> Unit) {
-}
-
-@Composable
-private fun PinkTheme(content: @Composable () -> Unit) {
-}
-
-@Composable
-private fun YourAppTheme(content: @Composable () -> Unit) {
-}
-
-@Composable
-private fun ProvideWindowInsets(content: @Composable () -> Unit) {
-}
-
-@Composable
-private fun Icon() {
-}
-
 private open class Fragment {
 
     lateinit var context: Context
@@ -669,28 +348,3 @@
 
     fun requireContext(): Context = TODO()
 }
-
-private class WindowCompat {
-    companion object {
-        fun setDecorFitsSystemWindows(window: Any, bool: Boolean) {}
-    }
-}
-
-private fun Modifier.navigationBarsPadding(): Modifier = this
-
-private class GreetingViewModel : ViewModel() {
-    val _message = MutableLiveData("")
-    val message: LiveData<String> = _message
-}
-private class GreetingViewModelFactory(val userId: String) : ViewModelProvider.Factory {
-    override fun <T : ViewModel?> create(modelClass: Class<T>): T {
-        TODO("Not yet implemented")
-    }
-}
-private class ActivityExampleBinding {
-    val root: Int = 0
-    lateinit var callToAction: InteropSnippet17.CallToActionViewButton
-    companion object {
-        fun inflate(li: LayoutInflater): ActivityExampleBinding { TODO() }
-    }
-}
\ No newline at end of file
diff --git a/compose/integration-tests/docs-snippets/src/main/java/androidx/compose/integration/docs/libraries/Libraries.kt b/compose/integration-tests/docs-snippets/src/main/java/androidx/compose/integration/docs/libraries/Libraries.kt
index 5f24b0b..58784ab 100644
--- a/compose/integration-tests/docs-snippets/src/main/java/androidx/compose/integration/docs/libraries/Libraries.kt
+++ b/compose/integration-tests/docs-snippets/src/main/java/androidx/compose/integration/docs/libraries/Libraries.kt
@@ -44,8 +44,7 @@
 import androidx.navigation.NavHostController
 import androidx.navigation.compose.NavHost
 import androidx.navigation.compose.composable
-import androidx.navigation.compose.getBackStackEntry
-import androidx.navigation.compose.navigation
+import androidx.navigation.navigation
 import kotlinx.coroutines.flow.Flow
 
 /**
diff --git a/compose/integration-tests/docs-snippets/src/main/java/androidx/compose/integration/docs/navigation/Navigation.kt b/compose/integration-tests/docs-snippets/src/main/java/androidx/compose/integration/docs/navigation/Navigation.kt
index 6dc9a73..c70a8db 100644
--- a/compose/integration-tests/docs-snippets/src/main/java/androidx/compose/integration/docs/navigation/Navigation.kt
+++ b/compose/integration-tests/docs-snippets/src/main/java/androidx/compose/integration/docs/navigation/Navigation.kt
@@ -40,12 +40,10 @@
 import androidx.navigation.NavGraphBuilder
 import androidx.navigation.NavHostController
 import androidx.navigation.NavType
-import androidx.navigation.compose.KEY_ROUTE
 import androidx.navigation.compose.NavHost
 import androidx.navigation.compose.composable
 import androidx.navigation.compose.currentBackStackEntryAsState
 import androidx.navigation.compose.navArgument
-import androidx.navigation.compose.navigate
 import androidx.navigation.compose.rememberNavController
 import androidx.navigation.navDeepLink
 
@@ -160,7 +158,7 @@
         bottomBar = {
             BottomNavigation {
                 val navBackStackEntry by navController.currentBackStackEntryAsState()
-                val currentRoute = navBackStackEntry?.arguments?.getString(KEY_ROUTE)
+                val currentRoute = navBackStackEntry?.destination?.route
                 items.forEach { screen ->
                     BottomNavigationItem(
                         icon = { Icon(Icons.Filled.Favorite, contentDescription = null) },
@@ -168,7 +166,9 @@
                         selected = currentRoute == screen.route,
                         onClick = {
                             // This is the equivalent to popUpTo the start destination
-                            navController.popBackStack(navController.graph.startDestination, false)
+                            navController.popBackStack(
+                                navController.graph.startDestinationId, false
+                            )
 
                             // This if check gives us a "singleTop" behavior where we do not create a
                             // second instance of the composable if we are already on that destination
diff --git a/compose/integration-tests/macrobenchmark/build.gradle b/compose/integration-tests/macrobenchmark/build.gradle
index 6568c2a..f400f4d 100644
--- a/compose/integration-tests/macrobenchmark/build.gradle
+++ b/compose/integration-tests/macrobenchmark/build.gradle
@@ -35,6 +35,7 @@
 dependencies {
     androidTestImplementation(project(":benchmark:benchmark-junit4"))
     androidTestImplementation(project(":benchmark:benchmark-macro-junit4"))
+    androidTestImplementation(project(":internal-testutils-macrobenchmark"))
     androidTestImplementation(ANDROIDX_TEST_RULES)
     androidTestImplementation(ANDROIDX_TEST_EXT_JUNIT)
     androidTestImplementation(ANDROIDX_TEST_CORE)
diff --git a/compose/integration-tests/macrobenchmark/src/androidTest/java/androidx/compose/integration/macrobenchmark/SmallListStartupBenchmark.kt b/compose/integration-tests/macrobenchmark/src/androidTest/java/androidx/compose/integration/macrobenchmark/SmallListStartupBenchmark.kt
index 0fe6f16..cb9e113 100644
--- a/compose/integration-tests/macrobenchmark/src/androidTest/java/androidx/compose/integration/macrobenchmark/SmallListStartupBenchmark.kt
+++ b/compose/integration-tests/macrobenchmark/src/androidTest/java/androidx/compose/integration/macrobenchmark/SmallListStartupBenchmark.kt
@@ -20,6 +20,8 @@
 import androidx.benchmark.macro.StartupMode
 import androidx.benchmark.macro.junit4.MacrobenchmarkRule
 import androidx.test.filters.LargeTest
+import androidx.testutils.createStartupCompilationParams
+import androidx.testutils.measureStartup
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -37,7 +39,8 @@
     @Test
     fun startup() = benchmarkRule.measureStartup(
         compilationMode = compilationMode,
-        startupMode = startupMode
+        startupMode = startupMode,
+        packageName = "androidx.compose.integration.macrobenchmark.target"
     ) {
         action = "androidx.compose.integration.macrobenchmark.target.LAZY_COLUMN_ACTIVITY"
         putExtra("ITEM_COUNT", 5)
diff --git a/compose/integration-tests/macrobenchmark/src/androidTest/java/androidx/compose/integration/macrobenchmark/TrivialListScrollBenchmark.kt b/compose/integration-tests/macrobenchmark/src/androidTest/java/androidx/compose/integration/macrobenchmark/TrivialListScrollBenchmark.kt
index 1be3b5c..6d0939c 100644
--- a/compose/integration-tests/macrobenchmark/src/androidTest/java/androidx/compose/integration/macrobenchmark/TrivialListScrollBenchmark.kt
+++ b/compose/integration-tests/macrobenchmark/src/androidTest/java/androidx/compose/integration/macrobenchmark/TrivialListScrollBenchmark.kt
@@ -26,6 +26,7 @@
 import androidx.test.uiautomator.By
 import androidx.test.uiautomator.UiDevice
 import androidx.test.uiautomator.Until
+import androidx.testutils.createCompilationParams
 import org.junit.Before
 import org.junit.Rule
 import org.junit.Test
diff --git a/compose/integration-tests/macrobenchmark/src/androidTest/java/androidx/compose/integration/macrobenchmark/TrivialStartupBenchmark.kt b/compose/integration-tests/macrobenchmark/src/androidTest/java/androidx/compose/integration/macrobenchmark/TrivialStartupBenchmark.kt
index 45167ff..1a6254b 100644
--- a/compose/integration-tests/macrobenchmark/src/androidTest/java/androidx/compose/integration/macrobenchmark/TrivialStartupBenchmark.kt
+++ b/compose/integration-tests/macrobenchmark/src/androidTest/java/androidx/compose/integration/macrobenchmark/TrivialStartupBenchmark.kt
@@ -20,6 +20,8 @@
 import androidx.benchmark.macro.StartupMode
 import androidx.benchmark.macro.junit4.MacrobenchmarkRule
 import androidx.test.filters.LargeTest
+import androidx.testutils.createStartupCompilationParams
+import androidx.testutils.measureStartup
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -37,7 +39,8 @@
     @Test
     fun startup() = benchmarkRule.measureStartup(
         compilationMode = compilationMode,
-        startupMode = startupMode
+        startupMode = startupMode,
+        packageName = "androidx.compose.integration.macrobenchmark.target"
     ) {
         action = "androidx.compose.integration.macrobenchmark.target.TRIVIAL_STARTUP_ACTIVITY"
     }
diff --git a/compose/material/material/integration-tests/material-catalog/src/main/java/androidx/compose/material/catalog/NavGraph.kt b/compose/material/material/integration-tests/material-catalog/src/main/java/androidx/compose/material/catalog/NavGraph.kt
index 390c6b2..e690e58 100644
--- a/compose/material/material/integration-tests/material-catalog/src/main/java/androidx/compose/material/catalog/NavGraph.kt
+++ b/compose/material/material/integration-tests/material-catalog/src/main/java/androidx/compose/material/catalog/NavGraph.kt
@@ -27,7 +27,6 @@
 import androidx.navigation.compose.NavHost
 import androidx.navigation.compose.composable
 import androidx.navigation.compose.navArgument
-import androidx.navigation.compose.navigate
 import androidx.navigation.compose.rememberNavController
 
 @Composable
diff --git a/compose/material/material/samples/src/main/java/androidx/compose/material/samples/ScaffoldSamples.kt b/compose/material/material/samples/src/main/java/androidx/compose/material/samples/ScaffoldSamples.kt
index dbb3ff1..3d6c1b2 100644
--- a/compose/material/material/samples/src/main/java/androidx/compose/material/samples/ScaffoldSamples.kt
+++ b/compose/material/material/samples/src/main/java/androidx/compose/material/samples/ScaffoldSamples.kt
@@ -103,7 +103,7 @@
         },
         content = { innerPadding ->
             LazyColumn(contentPadding = innerPadding) {
-                items(100) {
+                items(count = 100) {
                     Box(
                         Modifier
                             .fillMaxWidth()
@@ -178,7 +178,7 @@
         isFloatingActionButtonDocked = true,
         content = { innerPadding ->
             LazyColumn(contentPadding = innerPadding) {
-                items(100) {
+                items(count = 100) {
                     Box(
                         Modifier
                             .fillMaxWidth()
diff --git a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Button.kt b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Button.kt
index 13c198f..7649e03 100644
--- a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Button.kt
+++ b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Button.kt
@@ -60,9 +60,6 @@
  * Contained buttons are high-emphasis, distinguished by their use of elevation and fill. They
  * contain actions that are primary to your app.
  *
- * To make a button clickable, you must provide an onClick. If no onClick is provided, this button
- * will display itself as disabled.
- *
  * The default text style for internal [Text] components will be set to [Typography.button]. Text
  * color will try to match the correlated color for the background color. For example if the
  * background color is set to [Colors.primary] then the text will by default use
@@ -155,9 +152,6 @@
  * Outlined buttons are also a lower emphasis alternative to contained buttons, or a higher emphasis
  * alternative to text buttons.
  *
- * To make a button clickable, you must provide an onClick. If no onClick is provided, this button
- * will display itself as disabled.
- *
  * The default text style for internal [Text] components will be set to [Typography.button]. Text
  * color will try to match the correlated color for the background color. For example if the
  * background color is set to [Colors.primary] then the text will by default use
@@ -213,9 +207,6 @@
  * Text buttons are typically used for less-pronounced actions, including those located in cards and
  * dialogs.
  *
- * To make a button clickable, you must provide an onClick. If no onClick is provided, this button
- * will display itself as disabled.
- *
  * The default text style for internal [Text] components will be set to [Typography.button]. Text
  * color will try to match the correlated color for the background color. For example if the
  * background color is set to [Colors.primary] then the text will by default use
diff --git a/compose/runtime/runtime-saveable/src/commonMain/kotlin/androidx/compose/runtime/saveable/SaveableStateHolder.kt b/compose/runtime/runtime-saveable/src/commonMain/kotlin/androidx/compose/runtime/saveable/SaveableStateHolder.kt
index f9f5377..30224a5 100644
--- a/compose/runtime/runtime-saveable/src/commonMain/kotlin/androidx/compose/runtime/saveable/SaveableStateHolder.kt
+++ b/compose/runtime/runtime-saveable/src/commonMain/kotlin/androidx/compose/runtime/saveable/SaveableStateHolder.kt
@@ -17,9 +17,9 @@
 package androidx.compose.runtime.saveable
 
 import androidx.compose.runtime.Composable
-import androidx.compose.runtime.DisposableEffect
 import androidx.compose.runtime.CompositionLocalProvider
-import androidx.compose.runtime.key
+import androidx.compose.runtime.DisposableEffect
+import androidx.compose.runtime.ReusableContent
 import androidx.compose.runtime.remember
 
 /**
@@ -73,7 +73,7 @@
 
     @Composable
     override fun SaveableStateProvider(key: Any, content: @Composable () -> Unit) {
-        key(key) {
+        ReusableContent(key) {
             val registryHolder = remember {
                 require(parentSaveableStateRegistry?.canBeSaved(key) ?: true) {
                     "Type of the key $key is not supported. On Android you can only use types " +
@@ -85,7 +85,7 @@
                 LocalSaveableStateRegistry provides registryHolder.registry,
                 content = content
             )
-            DisposableEffect(key) {
+            DisposableEffect(Unit) {
                 require(key !in registryHolders) { "Key $key was used multiple times " }
                 savedStates -= key
                 registryHolders[key] = registryHolder
diff --git a/compose/runtime/runtime/api/1.0.0-beta07.txt b/compose/runtime/runtime/api/1.0.0-beta07.txt
index b1f3cf8..6b7c331 100644
--- a/compose/runtime/runtime/api/1.0.0-beta07.txt
+++ b/compose/runtime/runtime/api/1.0.0-beta07.txt
@@ -56,6 +56,10 @@
     method @androidx.compose.runtime.Composable public static inline <T extends java.lang.Object, reified E extends androidx.compose.runtime.Applier<?>> void ComposeNode(kotlin.jvm.functions.Function0<? extends T> factory, kotlin.jvm.functions.Function1<? super androidx.compose.runtime.Updater<T>,? extends kotlin.Unit> update);
     method @androidx.compose.runtime.Composable public static inline <T extends java.lang.Object, reified E extends androidx.compose.runtime.Applier<?>> void ComposeNode(kotlin.jvm.functions.Function0<? extends T> factory, kotlin.jvm.functions.Function1<? super androidx.compose.runtime.Updater<T>,? extends kotlin.Unit> update, kotlin.jvm.functions.Function0<? extends kotlin.Unit> content);
     method @androidx.compose.runtime.Composable @androidx.compose.runtime.ExplicitGroupsComposable public static inline <T, reified E extends androidx.compose.runtime.Applier<?>> void ComposeNode(kotlin.jvm.functions.Function0<? extends T> factory, kotlin.jvm.functions.Function1<? super androidx.compose.runtime.Updater<T>,? extends kotlin.Unit> update, kotlin.jvm.functions.Function1<? super androidx.compose.runtime.SkippableUpdater<T>,? extends kotlin.Unit> skippableUpdate, kotlin.jvm.functions.Function0<? extends kotlin.Unit> content);
+    method @androidx.compose.runtime.Composable public static inline <T extends java.lang.Object, reified E extends androidx.compose.runtime.Applier<?>> void ReusableComposeNode(kotlin.jvm.functions.Function0<? extends T> factory, kotlin.jvm.functions.Function1<? super androidx.compose.runtime.Updater<T>,? extends kotlin.Unit> update);
+    method @androidx.compose.runtime.Composable public static inline <T extends java.lang.Object, reified E extends androidx.compose.runtime.Applier<?>> void ReusableComposeNode(kotlin.jvm.functions.Function0<? extends T> factory, kotlin.jvm.functions.Function1<? super androidx.compose.runtime.Updater<T>,? extends kotlin.Unit> update, kotlin.jvm.functions.Function0<? extends kotlin.Unit> content);
+    method @androidx.compose.runtime.Composable @androidx.compose.runtime.ExplicitGroupsComposable public static inline <T, reified E extends androidx.compose.runtime.Applier<?>> void ReusableComposeNode(kotlin.jvm.functions.Function0<? extends T> factory, kotlin.jvm.functions.Function1<? super androidx.compose.runtime.Updater<T>,? extends kotlin.Unit> update, kotlin.jvm.functions.Function1<? super androidx.compose.runtime.SkippableUpdater<T>,? extends kotlin.Unit> skippableUpdate, kotlin.jvm.functions.Function0<? extends kotlin.Unit> content);
+    method @androidx.compose.runtime.Composable public static inline void ReusableContent(Object? key, kotlin.jvm.functions.Function0<kotlin.Unit> content);
     method @androidx.compose.runtime.Composable @androidx.compose.runtime.ReadOnlyComposable public static androidx.compose.runtime.Composer getCurrentComposer();
     method @androidx.compose.runtime.Composable public static int getCurrentCompositeKeyHash();
     method @androidx.compose.runtime.Composable @androidx.compose.runtime.ReadOnlyComposable public static androidx.compose.runtime.RecomposeScope getCurrentRecomposeScope();
@@ -83,11 +87,14 @@
     method @androidx.compose.runtime.ComposeCompilerApi public default boolean changed(long value);
     method @androidx.compose.runtime.ComposeCompilerApi public default boolean changed(double value);
     method @androidx.compose.runtime.ComposeCompilerApi public <T> void createNode(kotlin.jvm.functions.Function0<? extends T> factory);
+    method @androidx.compose.runtime.ComposeCompilerApi public void disableReusing();
+    method @androidx.compose.runtime.ComposeCompilerApi public void enableReusing();
     method @androidx.compose.runtime.ComposeCompilerApi public void endDefaults();
     method @androidx.compose.runtime.ComposeCompilerApi public void endMovableGroup();
     method @androidx.compose.runtime.ComposeCompilerApi public void endNode();
     method @androidx.compose.runtime.ComposeCompilerApi public void endReplaceableGroup();
     method @androidx.compose.runtime.ComposeCompilerApi public androidx.compose.runtime.ScopeUpdateScope? endRestartGroup();
+    method @androidx.compose.runtime.ComposeCompilerApi public void endReusableGroup();
     method public androidx.compose.runtime.Applier<?> getApplier();
     method @org.jetbrains.annotations.TestOnly public kotlin.coroutines.CoroutineContext getApplyCoroutineContext();
     method @org.jetbrains.annotations.TestOnly public androidx.compose.runtime.ControlledComposition getComposition();
@@ -101,14 +108,16 @@
     method @androidx.compose.runtime.ComposeCompilerApi public Object? rememberedValue();
     method @androidx.compose.runtime.ComposeCompilerApi public void skipCurrentGroup();
     method @androidx.compose.runtime.ComposeCompilerApi public void skipToGroupEnd();
+    method public void sourceInformation(String sourceInformation);
+    method public void sourceInformationMarkerEnd();
+    method public void sourceInformationMarkerStart(int key, String sourceInformation);
     method @androidx.compose.runtime.ComposeCompilerApi public void startDefaults();
     method @androidx.compose.runtime.ComposeCompilerApi public void startMovableGroup(int key, Object? dataKey);
-    method @androidx.compose.runtime.ComposeCompilerApi public void startMovableGroup(int key, Object? dataKey, String? sourceInformation);
     method @androidx.compose.runtime.ComposeCompilerApi public void startNode();
     method @androidx.compose.runtime.ComposeCompilerApi public void startReplaceableGroup(int key);
-    method @androidx.compose.runtime.ComposeCompilerApi public void startReplaceableGroup(int key, String? sourceInformation);
     method @androidx.compose.runtime.ComposeCompilerApi public androidx.compose.runtime.Composer startRestartGroup(int key);
-    method @androidx.compose.runtime.ComposeCompilerApi public androidx.compose.runtime.Composer startRestartGroup(int key, String? sourceInformation);
+    method @androidx.compose.runtime.ComposeCompilerApi public void startReusableGroup(int key, Object? dataKey);
+    method @androidx.compose.runtime.ComposeCompilerApi public void startReusableNode();
     method @androidx.compose.runtime.ComposeCompilerApi public void updateRememberedValue(Object? value);
     method @androidx.compose.runtime.ComposeCompilerApi public void useNode();
     property public abstract androidx.compose.runtime.Applier<?> applier;
@@ -130,6 +139,9 @@
 
   public final class ComposerKt {
     method @androidx.compose.runtime.ComposeCompilerApi public static inline <T> T! cache(androidx.compose.runtime.Composer, boolean invalid, kotlin.jvm.functions.Function0<? extends T> block);
+    method @androidx.compose.runtime.ComposeCompilerApi public static void sourceInformation(androidx.compose.runtime.Composer composer, String sourceInformation);
+    method @androidx.compose.runtime.ComposeCompilerApi public static void sourceInformationMarkerEnd(androidx.compose.runtime.Composer composer);
+    method @androidx.compose.runtime.ComposeCompilerApi public static void sourceInformationMarkerStart(androidx.compose.runtime.Composer composer, int key, String sourceInformation);
   }
 
   public interface Composition {
@@ -489,16 +501,16 @@
   }
 
   public final class ComposableLambdaKt {
-    method @androidx.compose.runtime.ComposeCompilerApi public static androidx.compose.runtime.internal.ComposableLambda composableLambda(androidx.compose.runtime.Composer composer, int key, boolean tracked, String? sourceInformation, Object block);
-    method @androidx.compose.runtime.ComposeCompilerApi public static androidx.compose.runtime.internal.ComposableLambda composableLambdaInstance(int key, boolean tracked, String? sourceInformation, Object block);
+    method @androidx.compose.runtime.ComposeCompilerApi public static androidx.compose.runtime.internal.ComposableLambda composableLambda(androidx.compose.runtime.Composer composer, int key, boolean tracked, Object block);
+    method @androidx.compose.runtime.ComposeCompilerApi public static androidx.compose.runtime.internal.ComposableLambda composableLambdaInstance(int key, boolean tracked, Object block);
   }
 
   @androidx.compose.runtime.ComposeCompilerApi @androidx.compose.runtime.Stable public interface ComposableLambdaN extends kotlin.jvm.functions.FunctionN<java.lang.Object> {
   }
 
   public final class ComposableLambdaN_jvmKt {
-    method @androidx.compose.runtime.ComposeCompilerApi public static androidx.compose.runtime.internal.ComposableLambdaN composableLambdaN(androidx.compose.runtime.Composer composer, int key, boolean tracked, String? sourceInformation, int arity, Object block);
-    method @androidx.compose.runtime.ComposeCompilerApi public static androidx.compose.runtime.internal.ComposableLambdaN composableLambdaNInstance(int key, boolean tracked, String? sourceInformation, int arity, Object block);
+    method @androidx.compose.runtime.ComposeCompilerApi public static androidx.compose.runtime.internal.ComposableLambdaN composableLambdaN(androidx.compose.runtime.Composer composer, int key, boolean tracked, int arity, Object block);
+    method @androidx.compose.runtime.ComposeCompilerApi public static androidx.compose.runtime.internal.ComposableLambdaN composableLambdaNInstance(int key, boolean tracked, int arity, Object block);
   }
 
   public final class DecoyKt {
diff --git a/compose/runtime/runtime/api/current.ignore b/compose/runtime/runtime/api/current.ignore
index 23a3504..87dd33f 100644
--- a/compose/runtime/runtime/api/current.ignore
+++ b/compose/runtime/runtime/api/current.ignore
@@ -1,45 +1,33 @@
 // Baseline format: 1.0
-AddedAbstractMethod: androidx.compose.runtime.Composer#apply(V, kotlin.jvm.functions.Function2<? super T,? super V,kotlin.Unit>):
-    Added method androidx.compose.runtime.Composer.apply(V,kotlin.jvm.functions.Function2<? super T,? super V,kotlin.Unit>)
-AddedAbstractMethod: androidx.compose.runtime.Composer#changed(Object):
-    Added method androidx.compose.runtime.Composer.changed(Object)
-AddedAbstractMethod: androidx.compose.runtime.Composer#createNode(kotlin.jvm.functions.Function0<? extends T>):
-    Added method androidx.compose.runtime.Composer.createNode(kotlin.jvm.functions.Function0<? extends T>)
-AddedAbstractMethod: androidx.compose.runtime.Composer#endDefaults():
-    Added method androidx.compose.runtime.Composer.endDefaults()
-AddedAbstractMethod: androidx.compose.runtime.Composer#endMovableGroup():
-    Added method androidx.compose.runtime.Composer.endMovableGroup()
-AddedAbstractMethod: androidx.compose.runtime.Composer#endNode():
-    Added method androidx.compose.runtime.Composer.endNode()
-AddedAbstractMethod: androidx.compose.runtime.Composer#endReplaceableGroup():
-    Added method androidx.compose.runtime.Composer.endReplaceableGroup()
-AddedAbstractMethod: androidx.compose.runtime.Composer#endRestartGroup():
-    Added method androidx.compose.runtime.Composer.endRestartGroup()
-AddedAbstractMethod: androidx.compose.runtime.Composer#joinKey(Object, Object):
-    Added method androidx.compose.runtime.Composer.joinKey(Object,Object)
-AddedAbstractMethod: androidx.compose.runtime.Composer#rememberedValue():
-    Added method androidx.compose.runtime.Composer.rememberedValue()
-AddedAbstractMethod: androidx.compose.runtime.Composer#skipCurrentGroup():
-    Added method androidx.compose.runtime.Composer.skipCurrentGroup()
-AddedAbstractMethod: androidx.compose.runtime.Composer#skipToGroupEnd():
-    Added method androidx.compose.runtime.Composer.skipToGroupEnd()
-AddedAbstractMethod: androidx.compose.runtime.Composer#startDefaults():
-    Added method androidx.compose.runtime.Composer.startDefaults()
-AddedAbstractMethod: androidx.compose.runtime.Composer#startMovableGroup(int, Object):
-    Added method androidx.compose.runtime.Composer.startMovableGroup(int,Object)
-AddedAbstractMethod: androidx.compose.runtime.Composer#startMovableGroup(int, Object, String):
-    Added method androidx.compose.runtime.Composer.startMovableGroup(int,Object,String)
-AddedAbstractMethod: androidx.compose.runtime.Composer#startNode():
-    Added method androidx.compose.runtime.Composer.startNode()
-AddedAbstractMethod: androidx.compose.runtime.Composer#startReplaceableGroup(int):
-    Added method androidx.compose.runtime.Composer.startReplaceableGroup(int)
-AddedAbstractMethod: androidx.compose.runtime.Composer#startReplaceableGroup(int, String):
-    Added method androidx.compose.runtime.Composer.startReplaceableGroup(int,String)
-AddedAbstractMethod: androidx.compose.runtime.Composer#startRestartGroup(int):
-    Added method androidx.compose.runtime.Composer.startRestartGroup(int)
-AddedAbstractMethod: androidx.compose.runtime.Composer#startRestartGroup(int, String):
-    Added method androidx.compose.runtime.Composer.startRestartGroup(int,String)
-AddedAbstractMethod: androidx.compose.runtime.Composer#updateRememberedValue(Object):
-    Added method androidx.compose.runtime.Composer.updateRememberedValue(Object)
-AddedAbstractMethod: androidx.compose.runtime.Composer#useNode():
-    Added method androidx.compose.runtime.Composer.useNode()
+AddedAbstractMethod: androidx.compose.runtime.Composer#disableReusing():
+    Added method androidx.compose.runtime.Composer.disableReusing()
+AddedAbstractMethod: androidx.compose.runtime.Composer#enableReusing():
+    Added method androidx.compose.runtime.Composer.enableReusing()
+AddedAbstractMethod: androidx.compose.runtime.Composer#endReusableGroup():
+    Added method androidx.compose.runtime.Composer.endReusableGroup()
+AddedAbstractMethod: androidx.compose.runtime.Composer#sourceInformation(String):
+    Added method androidx.compose.runtime.Composer.sourceInformation(String)
+AddedAbstractMethod: androidx.compose.runtime.Composer#sourceInformationMarkerEnd():
+    Added method androidx.compose.runtime.Composer.sourceInformationMarkerEnd()
+AddedAbstractMethod: androidx.compose.runtime.Composer#sourceInformationMarkerStart(int, String):
+    Added method androidx.compose.runtime.Composer.sourceInformationMarkerStart(int,String)
+AddedAbstractMethod: androidx.compose.runtime.Composer#startReusableGroup(int, Object):
+    Added method androidx.compose.runtime.Composer.startReusableGroup(int,Object)
+AddedAbstractMethod: androidx.compose.runtime.Composer#startReusableNode():
+    Added method androidx.compose.runtime.Composer.startReusableNode()
+
+
+RemovedMethod: androidx.compose.runtime.Composer#startMovableGroup(int, Object, String):
+    Removed method androidx.compose.runtime.Composer.startMovableGroup(int,Object,String)
+RemovedMethod: androidx.compose.runtime.Composer#startReplaceableGroup(int, String):
+    Removed method androidx.compose.runtime.Composer.startReplaceableGroup(int,String)
+RemovedMethod: androidx.compose.runtime.Composer#startRestartGroup(int, String):
+    Removed method androidx.compose.runtime.Composer.startRestartGroup(int,String)
+RemovedMethod: androidx.compose.runtime.internal.ComposableLambdaKt#composableLambda(androidx.compose.runtime.Composer, int, boolean, String, Object):
+    Removed method androidx.compose.runtime.internal.ComposableLambdaKt.composableLambda(androidx.compose.runtime.Composer,int,boolean,String,Object)
+RemovedMethod: androidx.compose.runtime.internal.ComposableLambdaKt#composableLambdaInstance(int, boolean, String, Object):
+    Removed method androidx.compose.runtime.internal.ComposableLambdaKt.composableLambdaInstance(int,boolean,String,Object)
+RemovedMethod: androidx.compose.runtime.internal.ComposableLambdaN_jvmKt#composableLambdaN(androidx.compose.runtime.Composer, int, boolean, String, int, Object):
+    Removed method androidx.compose.runtime.internal.ComposableLambdaN_jvmKt.composableLambdaN(androidx.compose.runtime.Composer,int,boolean,String,int,Object)
+RemovedMethod: androidx.compose.runtime.internal.ComposableLambdaN_jvmKt#composableLambdaNInstance(int, boolean, String, int, Object):
+    Removed method androidx.compose.runtime.internal.ComposableLambdaN_jvmKt.composableLambdaNInstance(int,boolean,String,int,Object)
diff --git a/compose/runtime/runtime/api/current.txt b/compose/runtime/runtime/api/current.txt
index b1f3cf8..6b7c331 100644
--- a/compose/runtime/runtime/api/current.txt
+++ b/compose/runtime/runtime/api/current.txt
@@ -56,6 +56,10 @@
     method @androidx.compose.runtime.Composable public static inline <T extends java.lang.Object, reified E extends androidx.compose.runtime.Applier<?>> void ComposeNode(kotlin.jvm.functions.Function0<? extends T> factory, kotlin.jvm.functions.Function1<? super androidx.compose.runtime.Updater<T>,? extends kotlin.Unit> update);
     method @androidx.compose.runtime.Composable public static inline <T extends java.lang.Object, reified E extends androidx.compose.runtime.Applier<?>> void ComposeNode(kotlin.jvm.functions.Function0<? extends T> factory, kotlin.jvm.functions.Function1<? super androidx.compose.runtime.Updater<T>,? extends kotlin.Unit> update, kotlin.jvm.functions.Function0<? extends kotlin.Unit> content);
     method @androidx.compose.runtime.Composable @androidx.compose.runtime.ExplicitGroupsComposable public static inline <T, reified E extends androidx.compose.runtime.Applier<?>> void ComposeNode(kotlin.jvm.functions.Function0<? extends T> factory, kotlin.jvm.functions.Function1<? super androidx.compose.runtime.Updater<T>,? extends kotlin.Unit> update, kotlin.jvm.functions.Function1<? super androidx.compose.runtime.SkippableUpdater<T>,? extends kotlin.Unit> skippableUpdate, kotlin.jvm.functions.Function0<? extends kotlin.Unit> content);
+    method @androidx.compose.runtime.Composable public static inline <T extends java.lang.Object, reified E extends androidx.compose.runtime.Applier<?>> void ReusableComposeNode(kotlin.jvm.functions.Function0<? extends T> factory, kotlin.jvm.functions.Function1<? super androidx.compose.runtime.Updater<T>,? extends kotlin.Unit> update);
+    method @androidx.compose.runtime.Composable public static inline <T extends java.lang.Object, reified E extends androidx.compose.runtime.Applier<?>> void ReusableComposeNode(kotlin.jvm.functions.Function0<? extends T> factory, kotlin.jvm.functions.Function1<? super androidx.compose.runtime.Updater<T>,? extends kotlin.Unit> update, kotlin.jvm.functions.Function0<? extends kotlin.Unit> content);
+    method @androidx.compose.runtime.Composable @androidx.compose.runtime.ExplicitGroupsComposable public static inline <T, reified E extends androidx.compose.runtime.Applier<?>> void ReusableComposeNode(kotlin.jvm.functions.Function0<? extends T> factory, kotlin.jvm.functions.Function1<? super androidx.compose.runtime.Updater<T>,? extends kotlin.Unit> update, kotlin.jvm.functions.Function1<? super androidx.compose.runtime.SkippableUpdater<T>,? extends kotlin.Unit> skippableUpdate, kotlin.jvm.functions.Function0<? extends kotlin.Unit> content);
+    method @androidx.compose.runtime.Composable public static inline void ReusableContent(Object? key, kotlin.jvm.functions.Function0<kotlin.Unit> content);
     method @androidx.compose.runtime.Composable @androidx.compose.runtime.ReadOnlyComposable public static androidx.compose.runtime.Composer getCurrentComposer();
     method @androidx.compose.runtime.Composable public static int getCurrentCompositeKeyHash();
     method @androidx.compose.runtime.Composable @androidx.compose.runtime.ReadOnlyComposable public static androidx.compose.runtime.RecomposeScope getCurrentRecomposeScope();
@@ -83,11 +87,14 @@
     method @androidx.compose.runtime.ComposeCompilerApi public default boolean changed(long value);
     method @androidx.compose.runtime.ComposeCompilerApi public default boolean changed(double value);
     method @androidx.compose.runtime.ComposeCompilerApi public <T> void createNode(kotlin.jvm.functions.Function0<? extends T> factory);
+    method @androidx.compose.runtime.ComposeCompilerApi public void disableReusing();
+    method @androidx.compose.runtime.ComposeCompilerApi public void enableReusing();
     method @androidx.compose.runtime.ComposeCompilerApi public void endDefaults();
     method @androidx.compose.runtime.ComposeCompilerApi public void endMovableGroup();
     method @androidx.compose.runtime.ComposeCompilerApi public void endNode();
     method @androidx.compose.runtime.ComposeCompilerApi public void endReplaceableGroup();
     method @androidx.compose.runtime.ComposeCompilerApi public androidx.compose.runtime.ScopeUpdateScope? endRestartGroup();
+    method @androidx.compose.runtime.ComposeCompilerApi public void endReusableGroup();
     method public androidx.compose.runtime.Applier<?> getApplier();
     method @org.jetbrains.annotations.TestOnly public kotlin.coroutines.CoroutineContext getApplyCoroutineContext();
     method @org.jetbrains.annotations.TestOnly public androidx.compose.runtime.ControlledComposition getComposition();
@@ -101,14 +108,16 @@
     method @androidx.compose.runtime.ComposeCompilerApi public Object? rememberedValue();
     method @androidx.compose.runtime.ComposeCompilerApi public void skipCurrentGroup();
     method @androidx.compose.runtime.ComposeCompilerApi public void skipToGroupEnd();
+    method public void sourceInformation(String sourceInformation);
+    method public void sourceInformationMarkerEnd();
+    method public void sourceInformationMarkerStart(int key, String sourceInformation);
     method @androidx.compose.runtime.ComposeCompilerApi public void startDefaults();
     method @androidx.compose.runtime.ComposeCompilerApi public void startMovableGroup(int key, Object? dataKey);
-    method @androidx.compose.runtime.ComposeCompilerApi public void startMovableGroup(int key, Object? dataKey, String? sourceInformation);
     method @androidx.compose.runtime.ComposeCompilerApi public void startNode();
     method @androidx.compose.runtime.ComposeCompilerApi public void startReplaceableGroup(int key);
-    method @androidx.compose.runtime.ComposeCompilerApi public void startReplaceableGroup(int key, String? sourceInformation);
     method @androidx.compose.runtime.ComposeCompilerApi public androidx.compose.runtime.Composer startRestartGroup(int key);
-    method @androidx.compose.runtime.ComposeCompilerApi public androidx.compose.runtime.Composer startRestartGroup(int key, String? sourceInformation);
+    method @androidx.compose.runtime.ComposeCompilerApi public void startReusableGroup(int key, Object? dataKey);
+    method @androidx.compose.runtime.ComposeCompilerApi public void startReusableNode();
     method @androidx.compose.runtime.ComposeCompilerApi public void updateRememberedValue(Object? value);
     method @androidx.compose.runtime.ComposeCompilerApi public void useNode();
     property public abstract androidx.compose.runtime.Applier<?> applier;
@@ -130,6 +139,9 @@
 
   public final class ComposerKt {
     method @androidx.compose.runtime.ComposeCompilerApi public static inline <T> T! cache(androidx.compose.runtime.Composer, boolean invalid, kotlin.jvm.functions.Function0<? extends T> block);
+    method @androidx.compose.runtime.ComposeCompilerApi public static void sourceInformation(androidx.compose.runtime.Composer composer, String sourceInformation);
+    method @androidx.compose.runtime.ComposeCompilerApi public static void sourceInformationMarkerEnd(androidx.compose.runtime.Composer composer);
+    method @androidx.compose.runtime.ComposeCompilerApi public static void sourceInformationMarkerStart(androidx.compose.runtime.Composer composer, int key, String sourceInformation);
   }
 
   public interface Composition {
@@ -489,16 +501,16 @@
   }
 
   public final class ComposableLambdaKt {
-    method @androidx.compose.runtime.ComposeCompilerApi public static androidx.compose.runtime.internal.ComposableLambda composableLambda(androidx.compose.runtime.Composer composer, int key, boolean tracked, String? sourceInformation, Object block);
-    method @androidx.compose.runtime.ComposeCompilerApi public static androidx.compose.runtime.internal.ComposableLambda composableLambdaInstance(int key, boolean tracked, String? sourceInformation, Object block);
+    method @androidx.compose.runtime.ComposeCompilerApi public static androidx.compose.runtime.internal.ComposableLambda composableLambda(androidx.compose.runtime.Composer composer, int key, boolean tracked, Object block);
+    method @androidx.compose.runtime.ComposeCompilerApi public static androidx.compose.runtime.internal.ComposableLambda composableLambdaInstance(int key, boolean tracked, Object block);
   }
 
   @androidx.compose.runtime.ComposeCompilerApi @androidx.compose.runtime.Stable public interface ComposableLambdaN extends kotlin.jvm.functions.FunctionN<java.lang.Object> {
   }
 
   public final class ComposableLambdaN_jvmKt {
-    method @androidx.compose.runtime.ComposeCompilerApi public static androidx.compose.runtime.internal.ComposableLambdaN composableLambdaN(androidx.compose.runtime.Composer composer, int key, boolean tracked, String? sourceInformation, int arity, Object block);
-    method @androidx.compose.runtime.ComposeCompilerApi public static androidx.compose.runtime.internal.ComposableLambdaN composableLambdaNInstance(int key, boolean tracked, String? sourceInformation, int arity, Object block);
+    method @androidx.compose.runtime.ComposeCompilerApi public static androidx.compose.runtime.internal.ComposableLambdaN composableLambdaN(androidx.compose.runtime.Composer composer, int key, boolean tracked, int arity, Object block);
+    method @androidx.compose.runtime.ComposeCompilerApi public static androidx.compose.runtime.internal.ComposableLambdaN composableLambdaNInstance(int key, boolean tracked, int arity, Object block);
   }
 
   public final class DecoyKt {
diff --git a/compose/runtime/runtime/api/public_plus_experimental_1.0.0-beta07.txt b/compose/runtime/runtime/api/public_plus_experimental_1.0.0-beta07.txt
index b73e719..3cbeb22 100644
--- a/compose/runtime/runtime/api/public_plus_experimental_1.0.0-beta07.txt
+++ b/compose/runtime/runtime/api/public_plus_experimental_1.0.0-beta07.txt
@@ -56,6 +56,10 @@
     method @androidx.compose.runtime.Composable public static inline <T extends java.lang.Object, reified E extends androidx.compose.runtime.Applier<?>> void ComposeNode(kotlin.jvm.functions.Function0<? extends T> factory, kotlin.jvm.functions.Function1<? super androidx.compose.runtime.Updater<T>,? extends kotlin.Unit> update);
     method @androidx.compose.runtime.Composable public static inline <T extends java.lang.Object, reified E extends androidx.compose.runtime.Applier<?>> void ComposeNode(kotlin.jvm.functions.Function0<? extends T> factory, kotlin.jvm.functions.Function1<? super androidx.compose.runtime.Updater<T>,? extends kotlin.Unit> update, kotlin.jvm.functions.Function0<? extends kotlin.Unit> content);
     method @androidx.compose.runtime.Composable @androidx.compose.runtime.ExplicitGroupsComposable public static inline <T, reified E extends androidx.compose.runtime.Applier<?>> void ComposeNode(kotlin.jvm.functions.Function0<? extends T> factory, kotlin.jvm.functions.Function1<? super androidx.compose.runtime.Updater<T>,? extends kotlin.Unit> update, kotlin.jvm.functions.Function1<? super androidx.compose.runtime.SkippableUpdater<T>,? extends kotlin.Unit> skippableUpdate, kotlin.jvm.functions.Function0<? extends kotlin.Unit> content);
+    method @androidx.compose.runtime.Composable public static inline <T extends java.lang.Object, reified E extends androidx.compose.runtime.Applier<?>> void ReusableComposeNode(kotlin.jvm.functions.Function0<? extends T> factory, kotlin.jvm.functions.Function1<? super androidx.compose.runtime.Updater<T>,? extends kotlin.Unit> update);
+    method @androidx.compose.runtime.Composable public static inline <T extends java.lang.Object, reified E extends androidx.compose.runtime.Applier<?>> void ReusableComposeNode(kotlin.jvm.functions.Function0<? extends T> factory, kotlin.jvm.functions.Function1<? super androidx.compose.runtime.Updater<T>,? extends kotlin.Unit> update, kotlin.jvm.functions.Function0<? extends kotlin.Unit> content);
+    method @androidx.compose.runtime.Composable @androidx.compose.runtime.ExplicitGroupsComposable public static inline <T, reified E extends androidx.compose.runtime.Applier<?>> void ReusableComposeNode(kotlin.jvm.functions.Function0<? extends T> factory, kotlin.jvm.functions.Function1<? super androidx.compose.runtime.Updater<T>,? extends kotlin.Unit> update, kotlin.jvm.functions.Function1<? super androidx.compose.runtime.SkippableUpdater<T>,? extends kotlin.Unit> skippableUpdate, kotlin.jvm.functions.Function0<? extends kotlin.Unit> content);
+    method @androidx.compose.runtime.Composable public static inline void ReusableContent(Object? key, kotlin.jvm.functions.Function0<kotlin.Unit> content);
     method @androidx.compose.runtime.Composable @androidx.compose.runtime.ReadOnlyComposable public static androidx.compose.runtime.Composer getCurrentComposer();
     method @androidx.compose.runtime.Composable public static int getCurrentCompositeKeyHash();
     method @androidx.compose.runtime.Composable @androidx.compose.runtime.ReadOnlyComposable public static androidx.compose.runtime.RecomposeScope getCurrentRecomposeScope();
@@ -86,12 +90,15 @@
     method @androidx.compose.runtime.InternalComposeApi public void collectParameterInformation();
     method @androidx.compose.runtime.InternalComposeApi public <T> T! consume(androidx.compose.runtime.CompositionLocal<T> key);
     method @androidx.compose.runtime.ComposeCompilerApi public <T> void createNode(kotlin.jvm.functions.Function0<? extends T> factory);
+    method @androidx.compose.runtime.ComposeCompilerApi public void disableReusing();
+    method @androidx.compose.runtime.ComposeCompilerApi public void enableReusing();
     method @androidx.compose.runtime.ComposeCompilerApi public void endDefaults();
     method @androidx.compose.runtime.ComposeCompilerApi public void endMovableGroup();
     method @androidx.compose.runtime.ComposeCompilerApi public void endNode();
     method @androidx.compose.runtime.InternalComposeApi public void endProviders();
     method @androidx.compose.runtime.ComposeCompilerApi public void endReplaceableGroup();
     method @androidx.compose.runtime.ComposeCompilerApi public androidx.compose.runtime.ScopeUpdateScope? endRestartGroup();
+    method @androidx.compose.runtime.ComposeCompilerApi public void endReusableGroup();
     method public androidx.compose.runtime.Applier<?> getApplier();
     method @org.jetbrains.annotations.TestOnly public kotlin.coroutines.CoroutineContext getApplyCoroutineContext();
     method @org.jetbrains.annotations.TestOnly public androidx.compose.runtime.ControlledComposition getComposition();
@@ -107,15 +114,17 @@
     method @androidx.compose.runtime.ComposeCompilerApi public Object? rememberedValue();
     method @androidx.compose.runtime.ComposeCompilerApi public void skipCurrentGroup();
     method @androidx.compose.runtime.ComposeCompilerApi public void skipToGroupEnd();
+    method public void sourceInformation(String sourceInformation);
+    method public void sourceInformationMarkerEnd();
+    method public void sourceInformationMarkerStart(int key, String sourceInformation);
     method @androidx.compose.runtime.ComposeCompilerApi public void startDefaults();
     method @androidx.compose.runtime.ComposeCompilerApi public void startMovableGroup(int key, Object? dataKey);
-    method @androidx.compose.runtime.ComposeCompilerApi public void startMovableGroup(int key, Object? dataKey, String? sourceInformation);
     method @androidx.compose.runtime.ComposeCompilerApi public void startNode();
     method @androidx.compose.runtime.InternalComposeApi public void startProviders(androidx.compose.runtime.ProvidedValue<?>![] values);
     method @androidx.compose.runtime.ComposeCompilerApi public void startReplaceableGroup(int key);
-    method @androidx.compose.runtime.ComposeCompilerApi public void startReplaceableGroup(int key, String? sourceInformation);
     method @androidx.compose.runtime.ComposeCompilerApi public androidx.compose.runtime.Composer startRestartGroup(int key);
-    method @androidx.compose.runtime.ComposeCompilerApi public androidx.compose.runtime.Composer startRestartGroup(int key, String? sourceInformation);
+    method @androidx.compose.runtime.ComposeCompilerApi public void startReusableGroup(int key, Object? dataKey);
+    method @androidx.compose.runtime.ComposeCompilerApi public void startReusableNode();
     method @androidx.compose.runtime.ComposeCompilerApi public void updateRememberedValue(Object? value);
     method @androidx.compose.runtime.ComposeCompilerApi public void useNode();
     property public abstract androidx.compose.runtime.Applier<?> applier;
@@ -137,6 +146,9 @@
 
   public final class ComposerKt {
     method @androidx.compose.runtime.ComposeCompilerApi public static inline <T> T! cache(androidx.compose.runtime.Composer, boolean invalid, kotlin.jvm.functions.Function0<? extends T> block);
+    method @androidx.compose.runtime.ComposeCompilerApi public static void sourceInformation(androidx.compose.runtime.Composer composer, String sourceInformation);
+    method @androidx.compose.runtime.ComposeCompilerApi public static void sourceInformationMarkerEnd(androidx.compose.runtime.Composer composer);
+    method @androidx.compose.runtime.ComposeCompilerApi public static void sourceInformationMarkerStart(androidx.compose.runtime.Composer composer, int key, String sourceInformation);
   }
 
   public interface Composition {
@@ -507,16 +519,16 @@
   }
 
   public final class ComposableLambdaKt {
-    method @androidx.compose.runtime.ComposeCompilerApi public static androidx.compose.runtime.internal.ComposableLambda composableLambda(androidx.compose.runtime.Composer composer, int key, boolean tracked, String? sourceInformation, Object block);
-    method @androidx.compose.runtime.ComposeCompilerApi public static androidx.compose.runtime.internal.ComposableLambda composableLambdaInstance(int key, boolean tracked, String? sourceInformation, Object block);
+    method @androidx.compose.runtime.ComposeCompilerApi public static androidx.compose.runtime.internal.ComposableLambda composableLambda(androidx.compose.runtime.Composer composer, int key, boolean tracked, Object block);
+    method @androidx.compose.runtime.ComposeCompilerApi public static androidx.compose.runtime.internal.ComposableLambda composableLambdaInstance(int key, boolean tracked, Object block);
   }
 
   @androidx.compose.runtime.ComposeCompilerApi @androidx.compose.runtime.Stable public interface ComposableLambdaN extends kotlin.jvm.functions.FunctionN<java.lang.Object> {
   }
 
   public final class ComposableLambdaN_jvmKt {
-    method @androidx.compose.runtime.ComposeCompilerApi public static androidx.compose.runtime.internal.ComposableLambdaN composableLambdaN(androidx.compose.runtime.Composer composer, int key, boolean tracked, String? sourceInformation, int arity, Object block);
-    method @androidx.compose.runtime.ComposeCompilerApi public static androidx.compose.runtime.internal.ComposableLambdaN composableLambdaNInstance(int key, boolean tracked, String? sourceInformation, int arity, Object block);
+    method @androidx.compose.runtime.ComposeCompilerApi public static androidx.compose.runtime.internal.ComposableLambdaN composableLambdaN(androidx.compose.runtime.Composer composer, int key, boolean tracked, int arity, Object block);
+    method @androidx.compose.runtime.ComposeCompilerApi public static androidx.compose.runtime.internal.ComposableLambdaN composableLambdaNInstance(int key, boolean tracked, int arity, Object block);
   }
 
   @androidx.compose.runtime.ExperimentalComposeApi @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget, kotlin.annotation.AnnotationTarget}) public @interface Decoy {
diff --git a/compose/runtime/runtime/api/public_plus_experimental_current.txt b/compose/runtime/runtime/api/public_plus_experimental_current.txt
index b73e719..3cbeb22 100644
--- a/compose/runtime/runtime/api/public_plus_experimental_current.txt
+++ b/compose/runtime/runtime/api/public_plus_experimental_current.txt
@@ -56,6 +56,10 @@
     method @androidx.compose.runtime.Composable public static inline <T extends java.lang.Object, reified E extends androidx.compose.runtime.Applier<?>> void ComposeNode(kotlin.jvm.functions.Function0<? extends T> factory, kotlin.jvm.functions.Function1<? super androidx.compose.runtime.Updater<T>,? extends kotlin.Unit> update);
     method @androidx.compose.runtime.Composable public static inline <T extends java.lang.Object, reified E extends androidx.compose.runtime.Applier<?>> void ComposeNode(kotlin.jvm.functions.Function0<? extends T> factory, kotlin.jvm.functions.Function1<? super androidx.compose.runtime.Updater<T>,? extends kotlin.Unit> update, kotlin.jvm.functions.Function0<? extends kotlin.Unit> content);
     method @androidx.compose.runtime.Composable @androidx.compose.runtime.ExplicitGroupsComposable public static inline <T, reified E extends androidx.compose.runtime.Applier<?>> void ComposeNode(kotlin.jvm.functions.Function0<? extends T> factory, kotlin.jvm.functions.Function1<? super androidx.compose.runtime.Updater<T>,? extends kotlin.Unit> update, kotlin.jvm.functions.Function1<? super androidx.compose.runtime.SkippableUpdater<T>,? extends kotlin.Unit> skippableUpdate, kotlin.jvm.functions.Function0<? extends kotlin.Unit> content);
+    method @androidx.compose.runtime.Composable public static inline <T extends java.lang.Object, reified E extends androidx.compose.runtime.Applier<?>> void ReusableComposeNode(kotlin.jvm.functions.Function0<? extends T> factory, kotlin.jvm.functions.Function1<? super androidx.compose.runtime.Updater<T>,? extends kotlin.Unit> update);
+    method @androidx.compose.runtime.Composable public static inline <T extends java.lang.Object, reified E extends androidx.compose.runtime.Applier<?>> void ReusableComposeNode(kotlin.jvm.functions.Function0<? extends T> factory, kotlin.jvm.functions.Function1<? super androidx.compose.runtime.Updater<T>,? extends kotlin.Unit> update, kotlin.jvm.functions.Function0<? extends kotlin.Unit> content);
+    method @androidx.compose.runtime.Composable @androidx.compose.runtime.ExplicitGroupsComposable public static inline <T, reified E extends androidx.compose.runtime.Applier<?>> void ReusableComposeNode(kotlin.jvm.functions.Function0<? extends T> factory, kotlin.jvm.functions.Function1<? super androidx.compose.runtime.Updater<T>,? extends kotlin.Unit> update, kotlin.jvm.functions.Function1<? super androidx.compose.runtime.SkippableUpdater<T>,? extends kotlin.Unit> skippableUpdate, kotlin.jvm.functions.Function0<? extends kotlin.Unit> content);
+    method @androidx.compose.runtime.Composable public static inline void ReusableContent(Object? key, kotlin.jvm.functions.Function0<kotlin.Unit> content);
     method @androidx.compose.runtime.Composable @androidx.compose.runtime.ReadOnlyComposable public static androidx.compose.runtime.Composer getCurrentComposer();
     method @androidx.compose.runtime.Composable public static int getCurrentCompositeKeyHash();
     method @androidx.compose.runtime.Composable @androidx.compose.runtime.ReadOnlyComposable public static androidx.compose.runtime.RecomposeScope getCurrentRecomposeScope();
@@ -86,12 +90,15 @@
     method @androidx.compose.runtime.InternalComposeApi public void collectParameterInformation();
     method @androidx.compose.runtime.InternalComposeApi public <T> T! consume(androidx.compose.runtime.CompositionLocal<T> key);
     method @androidx.compose.runtime.ComposeCompilerApi public <T> void createNode(kotlin.jvm.functions.Function0<? extends T> factory);
+    method @androidx.compose.runtime.ComposeCompilerApi public void disableReusing();
+    method @androidx.compose.runtime.ComposeCompilerApi public void enableReusing();
     method @androidx.compose.runtime.ComposeCompilerApi public void endDefaults();
     method @androidx.compose.runtime.ComposeCompilerApi public void endMovableGroup();
     method @androidx.compose.runtime.ComposeCompilerApi public void endNode();
     method @androidx.compose.runtime.InternalComposeApi public void endProviders();
     method @androidx.compose.runtime.ComposeCompilerApi public void endReplaceableGroup();
     method @androidx.compose.runtime.ComposeCompilerApi public androidx.compose.runtime.ScopeUpdateScope? endRestartGroup();
+    method @androidx.compose.runtime.ComposeCompilerApi public void endReusableGroup();
     method public androidx.compose.runtime.Applier<?> getApplier();
     method @org.jetbrains.annotations.TestOnly public kotlin.coroutines.CoroutineContext getApplyCoroutineContext();
     method @org.jetbrains.annotations.TestOnly public androidx.compose.runtime.ControlledComposition getComposition();
@@ -107,15 +114,17 @@
     method @androidx.compose.runtime.ComposeCompilerApi public Object? rememberedValue();
     method @androidx.compose.runtime.ComposeCompilerApi public void skipCurrentGroup();
     method @androidx.compose.runtime.ComposeCompilerApi public void skipToGroupEnd();
+    method public void sourceInformation(String sourceInformation);
+    method public void sourceInformationMarkerEnd();
+    method public void sourceInformationMarkerStart(int key, String sourceInformation);
     method @androidx.compose.runtime.ComposeCompilerApi public void startDefaults();
     method @androidx.compose.runtime.ComposeCompilerApi public void startMovableGroup(int key, Object? dataKey);
-    method @androidx.compose.runtime.ComposeCompilerApi public void startMovableGroup(int key, Object? dataKey, String? sourceInformation);
     method @androidx.compose.runtime.ComposeCompilerApi public void startNode();
     method @androidx.compose.runtime.InternalComposeApi public void startProviders(androidx.compose.runtime.ProvidedValue<?>![] values);
     method @androidx.compose.runtime.ComposeCompilerApi public void startReplaceableGroup(int key);
-    method @androidx.compose.runtime.ComposeCompilerApi public void startReplaceableGroup(int key, String? sourceInformation);
     method @androidx.compose.runtime.ComposeCompilerApi public androidx.compose.runtime.Composer startRestartGroup(int key);
-    method @androidx.compose.runtime.ComposeCompilerApi public androidx.compose.runtime.Composer startRestartGroup(int key, String? sourceInformation);
+    method @androidx.compose.runtime.ComposeCompilerApi public void startReusableGroup(int key, Object? dataKey);
+    method @androidx.compose.runtime.ComposeCompilerApi public void startReusableNode();
     method @androidx.compose.runtime.ComposeCompilerApi public void updateRememberedValue(Object? value);
     method @androidx.compose.runtime.ComposeCompilerApi public void useNode();
     property public abstract androidx.compose.runtime.Applier<?> applier;
@@ -137,6 +146,9 @@
 
   public final class ComposerKt {
     method @androidx.compose.runtime.ComposeCompilerApi public static inline <T> T! cache(androidx.compose.runtime.Composer, boolean invalid, kotlin.jvm.functions.Function0<? extends T> block);
+    method @androidx.compose.runtime.ComposeCompilerApi public static void sourceInformation(androidx.compose.runtime.Composer composer, String sourceInformation);
+    method @androidx.compose.runtime.ComposeCompilerApi public static void sourceInformationMarkerEnd(androidx.compose.runtime.Composer composer);
+    method @androidx.compose.runtime.ComposeCompilerApi public static void sourceInformationMarkerStart(androidx.compose.runtime.Composer composer, int key, String sourceInformation);
   }
 
   public interface Composition {
@@ -507,16 +519,16 @@
   }
 
   public final class ComposableLambdaKt {
-    method @androidx.compose.runtime.ComposeCompilerApi public static androidx.compose.runtime.internal.ComposableLambda composableLambda(androidx.compose.runtime.Composer composer, int key, boolean tracked, String? sourceInformation, Object block);
-    method @androidx.compose.runtime.ComposeCompilerApi public static androidx.compose.runtime.internal.ComposableLambda composableLambdaInstance(int key, boolean tracked, String? sourceInformation, Object block);
+    method @androidx.compose.runtime.ComposeCompilerApi public static androidx.compose.runtime.internal.ComposableLambda composableLambda(androidx.compose.runtime.Composer composer, int key, boolean tracked, Object block);
+    method @androidx.compose.runtime.ComposeCompilerApi public static androidx.compose.runtime.internal.ComposableLambda composableLambdaInstance(int key, boolean tracked, Object block);
   }
 
   @androidx.compose.runtime.ComposeCompilerApi @androidx.compose.runtime.Stable public interface ComposableLambdaN extends kotlin.jvm.functions.FunctionN<java.lang.Object> {
   }
 
   public final class ComposableLambdaN_jvmKt {
-    method @androidx.compose.runtime.ComposeCompilerApi public static androidx.compose.runtime.internal.ComposableLambdaN composableLambdaN(androidx.compose.runtime.Composer composer, int key, boolean tracked, String? sourceInformation, int arity, Object block);
-    method @androidx.compose.runtime.ComposeCompilerApi public static androidx.compose.runtime.internal.ComposableLambdaN composableLambdaNInstance(int key, boolean tracked, String? sourceInformation, int arity, Object block);
+    method @androidx.compose.runtime.ComposeCompilerApi public static androidx.compose.runtime.internal.ComposableLambdaN composableLambdaN(androidx.compose.runtime.Composer composer, int key, boolean tracked, int arity, Object block);
+    method @androidx.compose.runtime.ComposeCompilerApi public static androidx.compose.runtime.internal.ComposableLambdaN composableLambdaNInstance(int key, boolean tracked, int arity, Object block);
   }
 
   @androidx.compose.runtime.ExperimentalComposeApi @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget, kotlin.annotation.AnnotationTarget}) public @interface Decoy {
diff --git a/compose/runtime/runtime/api/restricted_1.0.0-beta07.txt b/compose/runtime/runtime/api/restricted_1.0.0-beta07.txt
index e216e95..2cd34c5 100644
--- a/compose/runtime/runtime/api/restricted_1.0.0-beta07.txt
+++ b/compose/runtime/runtime/api/restricted_1.0.0-beta07.txt
@@ -57,6 +57,10 @@
     method @androidx.compose.runtime.Composable public static inline <T extends java.lang.Object, reified E extends androidx.compose.runtime.Applier<?>> void ComposeNode(kotlin.jvm.functions.Function0<? extends T> factory, kotlin.jvm.functions.Function1<? super androidx.compose.runtime.Updater<T>,? extends kotlin.Unit> update);
     method @androidx.compose.runtime.Composable public static inline <T extends java.lang.Object, reified E extends androidx.compose.runtime.Applier<?>> void ComposeNode(kotlin.jvm.functions.Function0<? extends T> factory, kotlin.jvm.functions.Function1<? super androidx.compose.runtime.Updater<T>,? extends kotlin.Unit> update, kotlin.jvm.functions.Function0<? extends kotlin.Unit> content);
     method @androidx.compose.runtime.Composable @androidx.compose.runtime.ExplicitGroupsComposable public static inline <T, reified E extends androidx.compose.runtime.Applier<?>> void ComposeNode(kotlin.jvm.functions.Function0<? extends T> factory, kotlin.jvm.functions.Function1<? super androidx.compose.runtime.Updater<T>,? extends kotlin.Unit> update, kotlin.jvm.functions.Function1<? super androidx.compose.runtime.SkippableUpdater<T>,? extends kotlin.Unit> skippableUpdate, kotlin.jvm.functions.Function0<? extends kotlin.Unit> content);
+    method @androidx.compose.runtime.Composable public static inline <T extends java.lang.Object, reified E extends androidx.compose.runtime.Applier<?>> void ReusableComposeNode(kotlin.jvm.functions.Function0<? extends T> factory, kotlin.jvm.functions.Function1<? super androidx.compose.runtime.Updater<T>,? extends kotlin.Unit> update);
+    method @androidx.compose.runtime.Composable public static inline <T extends java.lang.Object, reified E extends androidx.compose.runtime.Applier<?>> void ReusableComposeNode(kotlin.jvm.functions.Function0<? extends T> factory, kotlin.jvm.functions.Function1<? super androidx.compose.runtime.Updater<T>,? extends kotlin.Unit> update, kotlin.jvm.functions.Function0<? extends kotlin.Unit> content);
+    method @androidx.compose.runtime.Composable @androidx.compose.runtime.ExplicitGroupsComposable public static inline <T, reified E extends androidx.compose.runtime.Applier<?>> void ReusableComposeNode(kotlin.jvm.functions.Function0<? extends T> factory, kotlin.jvm.functions.Function1<? super androidx.compose.runtime.Updater<T>,? extends kotlin.Unit> update, kotlin.jvm.functions.Function1<? super androidx.compose.runtime.SkippableUpdater<T>,? extends kotlin.Unit> skippableUpdate, kotlin.jvm.functions.Function0<? extends kotlin.Unit> content);
+    method @androidx.compose.runtime.Composable public static inline void ReusableContent(Object? key, kotlin.jvm.functions.Function0<kotlin.Unit> content);
     method @androidx.compose.runtime.Composable @androidx.compose.runtime.ReadOnlyComposable public static androidx.compose.runtime.Composer getCurrentComposer();
     method @androidx.compose.runtime.Composable public static int getCurrentCompositeKeyHash();
     method @androidx.compose.runtime.Composable @androidx.compose.runtime.ReadOnlyComposable public static androidx.compose.runtime.RecomposeScope getCurrentRecomposeScope();
@@ -85,11 +89,14 @@
     method @androidx.compose.runtime.ComposeCompilerApi public default boolean changed(long value);
     method @androidx.compose.runtime.ComposeCompilerApi public default boolean changed(double value);
     method @androidx.compose.runtime.ComposeCompilerApi public <T> void createNode(kotlin.jvm.functions.Function0<? extends T> factory);
+    method @androidx.compose.runtime.ComposeCompilerApi public void disableReusing();
+    method @androidx.compose.runtime.ComposeCompilerApi public void enableReusing();
     method @androidx.compose.runtime.ComposeCompilerApi public void endDefaults();
     method @androidx.compose.runtime.ComposeCompilerApi public void endMovableGroup();
     method @androidx.compose.runtime.ComposeCompilerApi public void endNode();
     method @androidx.compose.runtime.ComposeCompilerApi public void endReplaceableGroup();
     method @androidx.compose.runtime.ComposeCompilerApi public androidx.compose.runtime.ScopeUpdateScope? endRestartGroup();
+    method @androidx.compose.runtime.ComposeCompilerApi public void endReusableGroup();
     method public androidx.compose.runtime.Applier<?> getApplier();
     method @org.jetbrains.annotations.TestOnly public kotlin.coroutines.CoroutineContext getApplyCoroutineContext();
     method @org.jetbrains.annotations.TestOnly public androidx.compose.runtime.ControlledComposition getComposition();
@@ -103,14 +110,16 @@
     method @androidx.compose.runtime.ComposeCompilerApi public Object? rememberedValue();
     method @androidx.compose.runtime.ComposeCompilerApi public void skipCurrentGroup();
     method @androidx.compose.runtime.ComposeCompilerApi public void skipToGroupEnd();
+    method public void sourceInformation(String sourceInformation);
+    method public void sourceInformationMarkerEnd();
+    method public void sourceInformationMarkerStart(int key, String sourceInformation);
     method @androidx.compose.runtime.ComposeCompilerApi public void startDefaults();
     method @androidx.compose.runtime.ComposeCompilerApi public void startMovableGroup(int key, Object? dataKey);
-    method @androidx.compose.runtime.ComposeCompilerApi public void startMovableGroup(int key, Object? dataKey, String? sourceInformation);
     method @androidx.compose.runtime.ComposeCompilerApi public void startNode();
     method @androidx.compose.runtime.ComposeCompilerApi public void startReplaceableGroup(int key);
-    method @androidx.compose.runtime.ComposeCompilerApi public void startReplaceableGroup(int key, String? sourceInformation);
     method @androidx.compose.runtime.ComposeCompilerApi public androidx.compose.runtime.Composer startRestartGroup(int key);
-    method @androidx.compose.runtime.ComposeCompilerApi public androidx.compose.runtime.Composer startRestartGroup(int key, String? sourceInformation);
+    method @androidx.compose.runtime.ComposeCompilerApi public void startReusableGroup(int key, Object? dataKey);
+    method @androidx.compose.runtime.ComposeCompilerApi public void startReusableNode();
     method @androidx.compose.runtime.ComposeCompilerApi public void updateRememberedValue(Object? value);
     method @androidx.compose.runtime.ComposeCompilerApi public void useNode();
     property public abstract androidx.compose.runtime.Applier<?> applier;
@@ -132,6 +141,9 @@
 
   public final class ComposerKt {
     method @androidx.compose.runtime.ComposeCompilerApi public static inline <T> T! cache(androidx.compose.runtime.Composer, boolean invalid, kotlin.jvm.functions.Function0<? extends T> block);
+    method @androidx.compose.runtime.ComposeCompilerApi public static void sourceInformation(androidx.compose.runtime.Composer composer, String sourceInformation);
+    method @androidx.compose.runtime.ComposeCompilerApi public static void sourceInformationMarkerEnd(androidx.compose.runtime.Composer composer);
+    method @androidx.compose.runtime.ComposeCompilerApi public static void sourceInformationMarkerStart(androidx.compose.runtime.Composer composer, int key, String sourceInformation);
     field @kotlin.PublishedApi internal static final Object compositionLocalMap;
     field @kotlin.PublishedApi internal static final int compositionLocalMapKey = 202; // 0xca
     field @kotlin.PublishedApi internal static final Object invocation;
@@ -144,6 +156,7 @@
     field @kotlin.PublishedApi internal static final int providerValuesKey = 203; // 0xcb
     field @kotlin.PublishedApi internal static final Object reference;
     field @kotlin.PublishedApi internal static final int referenceKey = 206; // 0xce
+    field @kotlin.PublishedApi internal static final int reuseKey = 207; // 0xcf
   }
 
   public interface Composition {
@@ -516,16 +529,16 @@
   }
 
   public final class ComposableLambdaKt {
-    method @androidx.compose.runtime.ComposeCompilerApi public static androidx.compose.runtime.internal.ComposableLambda composableLambda(androidx.compose.runtime.Composer composer, int key, boolean tracked, String? sourceInformation, Object block);
-    method @androidx.compose.runtime.ComposeCompilerApi public static androidx.compose.runtime.internal.ComposableLambda composableLambdaInstance(int key, boolean tracked, String? sourceInformation, Object block);
+    method @androidx.compose.runtime.ComposeCompilerApi public static androidx.compose.runtime.internal.ComposableLambda composableLambda(androidx.compose.runtime.Composer composer, int key, boolean tracked, Object block);
+    method @androidx.compose.runtime.ComposeCompilerApi public static androidx.compose.runtime.internal.ComposableLambda composableLambdaInstance(int key, boolean tracked, Object block);
   }
 
   @androidx.compose.runtime.ComposeCompilerApi @androidx.compose.runtime.Stable public interface ComposableLambdaN extends kotlin.jvm.functions.FunctionN<java.lang.Object> {
   }
 
   public final class ComposableLambdaN_jvmKt {
-    method @androidx.compose.runtime.ComposeCompilerApi public static androidx.compose.runtime.internal.ComposableLambdaN composableLambdaN(androidx.compose.runtime.Composer composer, int key, boolean tracked, String? sourceInformation, int arity, Object block);
-    method @androidx.compose.runtime.ComposeCompilerApi public static androidx.compose.runtime.internal.ComposableLambdaN composableLambdaNInstance(int key, boolean tracked, String? sourceInformation, int arity, Object block);
+    method @androidx.compose.runtime.ComposeCompilerApi public static androidx.compose.runtime.internal.ComposableLambdaN composableLambdaN(androidx.compose.runtime.Composer composer, int key, boolean tracked, int arity, Object block);
+    method @androidx.compose.runtime.ComposeCompilerApi public static androidx.compose.runtime.internal.ComposableLambdaN composableLambdaNInstance(int key, boolean tracked, int arity, Object block);
   }
 
   public final class DecoyKt {
diff --git a/compose/runtime/runtime/api/restricted_current.ignore b/compose/runtime/runtime/api/restricted_current.ignore
index 23a3504..87dd33f 100644
--- a/compose/runtime/runtime/api/restricted_current.ignore
+++ b/compose/runtime/runtime/api/restricted_current.ignore
@@ -1,45 +1,33 @@
 // Baseline format: 1.0
-AddedAbstractMethod: androidx.compose.runtime.Composer#apply(V, kotlin.jvm.functions.Function2<? super T,? super V,kotlin.Unit>):
-    Added method androidx.compose.runtime.Composer.apply(V,kotlin.jvm.functions.Function2<? super T,? super V,kotlin.Unit>)
-AddedAbstractMethod: androidx.compose.runtime.Composer#changed(Object):
-    Added method androidx.compose.runtime.Composer.changed(Object)
-AddedAbstractMethod: androidx.compose.runtime.Composer#createNode(kotlin.jvm.functions.Function0<? extends T>):
-    Added method androidx.compose.runtime.Composer.createNode(kotlin.jvm.functions.Function0<? extends T>)
-AddedAbstractMethod: androidx.compose.runtime.Composer#endDefaults():
-    Added method androidx.compose.runtime.Composer.endDefaults()
-AddedAbstractMethod: androidx.compose.runtime.Composer#endMovableGroup():
-    Added method androidx.compose.runtime.Composer.endMovableGroup()
-AddedAbstractMethod: androidx.compose.runtime.Composer#endNode():
-    Added method androidx.compose.runtime.Composer.endNode()
-AddedAbstractMethod: androidx.compose.runtime.Composer#endReplaceableGroup():
-    Added method androidx.compose.runtime.Composer.endReplaceableGroup()
-AddedAbstractMethod: androidx.compose.runtime.Composer#endRestartGroup():
-    Added method androidx.compose.runtime.Composer.endRestartGroup()
-AddedAbstractMethod: androidx.compose.runtime.Composer#joinKey(Object, Object):
-    Added method androidx.compose.runtime.Composer.joinKey(Object,Object)
-AddedAbstractMethod: androidx.compose.runtime.Composer#rememberedValue():
-    Added method androidx.compose.runtime.Composer.rememberedValue()
-AddedAbstractMethod: androidx.compose.runtime.Composer#skipCurrentGroup():
-    Added method androidx.compose.runtime.Composer.skipCurrentGroup()
-AddedAbstractMethod: androidx.compose.runtime.Composer#skipToGroupEnd():
-    Added method androidx.compose.runtime.Composer.skipToGroupEnd()
-AddedAbstractMethod: androidx.compose.runtime.Composer#startDefaults():
-    Added method androidx.compose.runtime.Composer.startDefaults()
-AddedAbstractMethod: androidx.compose.runtime.Composer#startMovableGroup(int, Object):
-    Added method androidx.compose.runtime.Composer.startMovableGroup(int,Object)
-AddedAbstractMethod: androidx.compose.runtime.Composer#startMovableGroup(int, Object, String):
-    Added method androidx.compose.runtime.Composer.startMovableGroup(int,Object,String)
-AddedAbstractMethod: androidx.compose.runtime.Composer#startNode():
-    Added method androidx.compose.runtime.Composer.startNode()
-AddedAbstractMethod: androidx.compose.runtime.Composer#startReplaceableGroup(int):
-    Added method androidx.compose.runtime.Composer.startReplaceableGroup(int)
-AddedAbstractMethod: androidx.compose.runtime.Composer#startReplaceableGroup(int, String):
-    Added method androidx.compose.runtime.Composer.startReplaceableGroup(int,String)
-AddedAbstractMethod: androidx.compose.runtime.Composer#startRestartGroup(int):
-    Added method androidx.compose.runtime.Composer.startRestartGroup(int)
-AddedAbstractMethod: androidx.compose.runtime.Composer#startRestartGroup(int, String):
-    Added method androidx.compose.runtime.Composer.startRestartGroup(int,String)
-AddedAbstractMethod: androidx.compose.runtime.Composer#updateRememberedValue(Object):
-    Added method androidx.compose.runtime.Composer.updateRememberedValue(Object)
-AddedAbstractMethod: androidx.compose.runtime.Composer#useNode():
-    Added method androidx.compose.runtime.Composer.useNode()
+AddedAbstractMethod: androidx.compose.runtime.Composer#disableReusing():
+    Added method androidx.compose.runtime.Composer.disableReusing()
+AddedAbstractMethod: androidx.compose.runtime.Composer#enableReusing():
+    Added method androidx.compose.runtime.Composer.enableReusing()
+AddedAbstractMethod: androidx.compose.runtime.Composer#endReusableGroup():
+    Added method androidx.compose.runtime.Composer.endReusableGroup()
+AddedAbstractMethod: androidx.compose.runtime.Composer#sourceInformation(String):
+    Added method androidx.compose.runtime.Composer.sourceInformation(String)
+AddedAbstractMethod: androidx.compose.runtime.Composer#sourceInformationMarkerEnd():
+    Added method androidx.compose.runtime.Composer.sourceInformationMarkerEnd()
+AddedAbstractMethod: androidx.compose.runtime.Composer#sourceInformationMarkerStart(int, String):
+    Added method androidx.compose.runtime.Composer.sourceInformationMarkerStart(int,String)
+AddedAbstractMethod: androidx.compose.runtime.Composer#startReusableGroup(int, Object):
+    Added method androidx.compose.runtime.Composer.startReusableGroup(int,Object)
+AddedAbstractMethod: androidx.compose.runtime.Composer#startReusableNode():
+    Added method androidx.compose.runtime.Composer.startReusableNode()
+
+
+RemovedMethod: androidx.compose.runtime.Composer#startMovableGroup(int, Object, String):
+    Removed method androidx.compose.runtime.Composer.startMovableGroup(int,Object,String)
+RemovedMethod: androidx.compose.runtime.Composer#startReplaceableGroup(int, String):
+    Removed method androidx.compose.runtime.Composer.startReplaceableGroup(int,String)
+RemovedMethod: androidx.compose.runtime.Composer#startRestartGroup(int, String):
+    Removed method androidx.compose.runtime.Composer.startRestartGroup(int,String)
+RemovedMethod: androidx.compose.runtime.internal.ComposableLambdaKt#composableLambda(androidx.compose.runtime.Composer, int, boolean, String, Object):
+    Removed method androidx.compose.runtime.internal.ComposableLambdaKt.composableLambda(androidx.compose.runtime.Composer,int,boolean,String,Object)
+RemovedMethod: androidx.compose.runtime.internal.ComposableLambdaKt#composableLambdaInstance(int, boolean, String, Object):
+    Removed method androidx.compose.runtime.internal.ComposableLambdaKt.composableLambdaInstance(int,boolean,String,Object)
+RemovedMethod: androidx.compose.runtime.internal.ComposableLambdaN_jvmKt#composableLambdaN(androidx.compose.runtime.Composer, int, boolean, String, int, Object):
+    Removed method androidx.compose.runtime.internal.ComposableLambdaN_jvmKt.composableLambdaN(androidx.compose.runtime.Composer,int,boolean,String,int,Object)
+RemovedMethod: androidx.compose.runtime.internal.ComposableLambdaN_jvmKt#composableLambdaNInstance(int, boolean, String, int, Object):
+    Removed method androidx.compose.runtime.internal.ComposableLambdaN_jvmKt.composableLambdaNInstance(int,boolean,String,int,Object)
diff --git a/compose/runtime/runtime/api/restricted_current.txt b/compose/runtime/runtime/api/restricted_current.txt
index e216e95..2cd34c5 100644
--- a/compose/runtime/runtime/api/restricted_current.txt
+++ b/compose/runtime/runtime/api/restricted_current.txt
@@ -57,6 +57,10 @@
     method @androidx.compose.runtime.Composable public static inline <T extends java.lang.Object, reified E extends androidx.compose.runtime.Applier<?>> void ComposeNode(kotlin.jvm.functions.Function0<? extends T> factory, kotlin.jvm.functions.Function1<? super androidx.compose.runtime.Updater<T>,? extends kotlin.Unit> update);
     method @androidx.compose.runtime.Composable public static inline <T extends java.lang.Object, reified E extends androidx.compose.runtime.Applier<?>> void ComposeNode(kotlin.jvm.functions.Function0<? extends T> factory, kotlin.jvm.functions.Function1<? super androidx.compose.runtime.Updater<T>,? extends kotlin.Unit> update, kotlin.jvm.functions.Function0<? extends kotlin.Unit> content);
     method @androidx.compose.runtime.Composable @androidx.compose.runtime.ExplicitGroupsComposable public static inline <T, reified E extends androidx.compose.runtime.Applier<?>> void ComposeNode(kotlin.jvm.functions.Function0<? extends T> factory, kotlin.jvm.functions.Function1<? super androidx.compose.runtime.Updater<T>,? extends kotlin.Unit> update, kotlin.jvm.functions.Function1<? super androidx.compose.runtime.SkippableUpdater<T>,? extends kotlin.Unit> skippableUpdate, kotlin.jvm.functions.Function0<? extends kotlin.Unit> content);
+    method @androidx.compose.runtime.Composable public static inline <T extends java.lang.Object, reified E extends androidx.compose.runtime.Applier<?>> void ReusableComposeNode(kotlin.jvm.functions.Function0<? extends T> factory, kotlin.jvm.functions.Function1<? super androidx.compose.runtime.Updater<T>,? extends kotlin.Unit> update);
+    method @androidx.compose.runtime.Composable public static inline <T extends java.lang.Object, reified E extends androidx.compose.runtime.Applier<?>> void ReusableComposeNode(kotlin.jvm.functions.Function0<? extends T> factory, kotlin.jvm.functions.Function1<? super androidx.compose.runtime.Updater<T>,? extends kotlin.Unit> update, kotlin.jvm.functions.Function0<? extends kotlin.Unit> content);
+    method @androidx.compose.runtime.Composable @androidx.compose.runtime.ExplicitGroupsComposable public static inline <T, reified E extends androidx.compose.runtime.Applier<?>> void ReusableComposeNode(kotlin.jvm.functions.Function0<? extends T> factory, kotlin.jvm.functions.Function1<? super androidx.compose.runtime.Updater<T>,? extends kotlin.Unit> update, kotlin.jvm.functions.Function1<? super androidx.compose.runtime.SkippableUpdater<T>,? extends kotlin.Unit> skippableUpdate, kotlin.jvm.functions.Function0<? extends kotlin.Unit> content);
+    method @androidx.compose.runtime.Composable public static inline void ReusableContent(Object? key, kotlin.jvm.functions.Function0<kotlin.Unit> content);
     method @androidx.compose.runtime.Composable @androidx.compose.runtime.ReadOnlyComposable public static androidx.compose.runtime.Composer getCurrentComposer();
     method @androidx.compose.runtime.Composable public static int getCurrentCompositeKeyHash();
     method @androidx.compose.runtime.Composable @androidx.compose.runtime.ReadOnlyComposable public static androidx.compose.runtime.RecomposeScope getCurrentRecomposeScope();
@@ -85,11 +89,14 @@
     method @androidx.compose.runtime.ComposeCompilerApi public default boolean changed(long value);
     method @androidx.compose.runtime.ComposeCompilerApi public default boolean changed(double value);
     method @androidx.compose.runtime.ComposeCompilerApi public <T> void createNode(kotlin.jvm.functions.Function0<? extends T> factory);
+    method @androidx.compose.runtime.ComposeCompilerApi public void disableReusing();
+    method @androidx.compose.runtime.ComposeCompilerApi public void enableReusing();
     method @androidx.compose.runtime.ComposeCompilerApi public void endDefaults();
     method @androidx.compose.runtime.ComposeCompilerApi public void endMovableGroup();
     method @androidx.compose.runtime.ComposeCompilerApi public void endNode();
     method @androidx.compose.runtime.ComposeCompilerApi public void endReplaceableGroup();
     method @androidx.compose.runtime.ComposeCompilerApi public androidx.compose.runtime.ScopeUpdateScope? endRestartGroup();
+    method @androidx.compose.runtime.ComposeCompilerApi public void endReusableGroup();
     method public androidx.compose.runtime.Applier<?> getApplier();
     method @org.jetbrains.annotations.TestOnly public kotlin.coroutines.CoroutineContext getApplyCoroutineContext();
     method @org.jetbrains.annotations.TestOnly public androidx.compose.runtime.ControlledComposition getComposition();
@@ -103,14 +110,16 @@
     method @androidx.compose.runtime.ComposeCompilerApi public Object? rememberedValue();
     method @androidx.compose.runtime.ComposeCompilerApi public void skipCurrentGroup();
     method @androidx.compose.runtime.ComposeCompilerApi public void skipToGroupEnd();
+    method public void sourceInformation(String sourceInformation);
+    method public void sourceInformationMarkerEnd();
+    method public void sourceInformationMarkerStart(int key, String sourceInformation);
     method @androidx.compose.runtime.ComposeCompilerApi public void startDefaults();
     method @androidx.compose.runtime.ComposeCompilerApi public void startMovableGroup(int key, Object? dataKey);
-    method @androidx.compose.runtime.ComposeCompilerApi public void startMovableGroup(int key, Object? dataKey, String? sourceInformation);
     method @androidx.compose.runtime.ComposeCompilerApi public void startNode();
     method @androidx.compose.runtime.ComposeCompilerApi public void startReplaceableGroup(int key);
-    method @androidx.compose.runtime.ComposeCompilerApi public void startReplaceableGroup(int key, String? sourceInformation);
     method @androidx.compose.runtime.ComposeCompilerApi public androidx.compose.runtime.Composer startRestartGroup(int key);
-    method @androidx.compose.runtime.ComposeCompilerApi public androidx.compose.runtime.Composer startRestartGroup(int key, String? sourceInformation);
+    method @androidx.compose.runtime.ComposeCompilerApi public void startReusableGroup(int key, Object? dataKey);
+    method @androidx.compose.runtime.ComposeCompilerApi public void startReusableNode();
     method @androidx.compose.runtime.ComposeCompilerApi public void updateRememberedValue(Object? value);
     method @androidx.compose.runtime.ComposeCompilerApi public void useNode();
     property public abstract androidx.compose.runtime.Applier<?> applier;
@@ -132,6 +141,9 @@
 
   public final class ComposerKt {
     method @androidx.compose.runtime.ComposeCompilerApi public static inline <T> T! cache(androidx.compose.runtime.Composer, boolean invalid, kotlin.jvm.functions.Function0<? extends T> block);
+    method @androidx.compose.runtime.ComposeCompilerApi public static void sourceInformation(androidx.compose.runtime.Composer composer, String sourceInformation);
+    method @androidx.compose.runtime.ComposeCompilerApi public static void sourceInformationMarkerEnd(androidx.compose.runtime.Composer composer);
+    method @androidx.compose.runtime.ComposeCompilerApi public static void sourceInformationMarkerStart(androidx.compose.runtime.Composer composer, int key, String sourceInformation);
     field @kotlin.PublishedApi internal static final Object compositionLocalMap;
     field @kotlin.PublishedApi internal static final int compositionLocalMapKey = 202; // 0xca
     field @kotlin.PublishedApi internal static final Object invocation;
@@ -144,6 +156,7 @@
     field @kotlin.PublishedApi internal static final int providerValuesKey = 203; // 0xcb
     field @kotlin.PublishedApi internal static final Object reference;
     field @kotlin.PublishedApi internal static final int referenceKey = 206; // 0xce
+    field @kotlin.PublishedApi internal static final int reuseKey = 207; // 0xcf
   }
 
   public interface Composition {
@@ -516,16 +529,16 @@
   }
 
   public final class ComposableLambdaKt {
-    method @androidx.compose.runtime.ComposeCompilerApi public static androidx.compose.runtime.internal.ComposableLambda composableLambda(androidx.compose.runtime.Composer composer, int key, boolean tracked, String? sourceInformation, Object block);
-    method @androidx.compose.runtime.ComposeCompilerApi public static androidx.compose.runtime.internal.ComposableLambda composableLambdaInstance(int key, boolean tracked, String? sourceInformation, Object block);
+    method @androidx.compose.runtime.ComposeCompilerApi public static androidx.compose.runtime.internal.ComposableLambda composableLambda(androidx.compose.runtime.Composer composer, int key, boolean tracked, Object block);
+    method @androidx.compose.runtime.ComposeCompilerApi public static androidx.compose.runtime.internal.ComposableLambda composableLambdaInstance(int key, boolean tracked, Object block);
   }
 
   @androidx.compose.runtime.ComposeCompilerApi @androidx.compose.runtime.Stable public interface ComposableLambdaN extends kotlin.jvm.functions.FunctionN<java.lang.Object> {
   }
 
   public final class ComposableLambdaN_jvmKt {
-    method @androidx.compose.runtime.ComposeCompilerApi public static androidx.compose.runtime.internal.ComposableLambdaN composableLambdaN(androidx.compose.runtime.Composer composer, int key, boolean tracked, String? sourceInformation, int arity, Object block);
-    method @androidx.compose.runtime.ComposeCompilerApi public static androidx.compose.runtime.internal.ComposableLambdaN composableLambdaNInstance(int key, boolean tracked, String? sourceInformation, int arity, Object block);
+    method @androidx.compose.runtime.ComposeCompilerApi public static androidx.compose.runtime.internal.ComposableLambdaN composableLambdaN(androidx.compose.runtime.Composer composer, int key, boolean tracked, int arity, Object block);
+    method @androidx.compose.runtime.ComposeCompilerApi public static androidx.compose.runtime.internal.ComposableLambdaN composableLambdaNInstance(int key, boolean tracked, int arity, Object block);
   }
 
   public final class DecoyKt {
diff --git a/compose/runtime/runtime/build.gradle b/compose/runtime/runtime/build.gradle
index f6127c3..d8bae21 100644
--- a/compose/runtime/runtime/build.gradle
+++ b/compose/runtime/runtime/build.gradle
@@ -106,6 +106,9 @@
 }
 
 android {
+    defaultConfig {
+        consumerProguardFiles 'proguard-rules.pro'
+    }
     buildTypes {
         debug {
             testCoverageEnabled = false
diff --git a/compose/runtime/runtime/proguard-rules.pro b/compose/runtime/runtime/proguard-rules.pro
new file mode 100644
index 0000000..0343d92
--- /dev/null
+++ b/compose/runtime/runtime/proguard-rules.pro
@@ -0,0 +1,5 @@
+-assumenosideeffects public class androidx.compose.runtime.ComposerKt {
+    void sourceInformation(androidx.compose.runtime.Composer,java.lang.String);
+    void sourceInformationMarkerStart(androidx.compose.runtime.Composer,int,java.lang.String);
+    void sourceInformationMarkerEnd(androidx.compose.runtime.Composer);
+}
diff --git a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Composables.kt b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Composables.kt
index eb599eb..09fef48 100644
--- a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Composables.kt
+++ b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Composables.kt
@@ -129,6 +129,25 @@
 ) = block()
 
 /**
+ * A utility function to mark a composition as supporting recycling. If the [key] changes the
+ * composition is replaced by a new composition (as would happen for [key]) but reusable nodes
+ * that are emitted by [ReusableComposeNode] are reused.
+ *
+ * @param key the value that is used to trigger recycling. If recomposed with a different value
+ * the composer creates a new composition but tries to reuse reusable nodes.
+ * @param content the composable children that are recyclable.
+ */
+@Composable
+inline fun ReusableContent(
+    key: Any?,
+    content: @Composable () -> Unit
+) {
+    currentComposer.startReusableGroup(reuseKey, key)
+    content()
+    currentComposer.endReusableGroup()
+}
+
+/**
  * TODO(lmr): provide documentation
  */
 val currentComposer: Composer
@@ -198,6 +217,43 @@
 }
 
 /**
+ * Emits a recyclable node into the composition of type [T].
+ *
+ * This function will throw a runtime exception if [E] is not a subtype of the applier of the
+ * [currentComposer].
+ *
+ * @sample androidx.compose.runtime.samples.CustomTreeComposition
+ *
+ * @param factory A function which will create a new instance of [T]. This function is NOT
+ * guaranteed to be called in place.
+ * @param update A function to perform updates on the node. This will run every time emit is
+ * executed. This function is called in place and will be inlined.
+ *
+ * @see Updater
+ * @see Applier
+ * @see Composition
+ */
+// ComposeNode is a special case of readonly composable and handles creating its own groups, so
+// it is okay to use.
+@Suppress("NONREADONLY_CALL_IN_READONLY_COMPOSABLE", "UnnecessaryLambdaCreation")
+@Composable inline fun <T : Any, reified E : Applier<*>> ReusableComposeNode(
+    noinline factory: () -> T,
+    update: @DisallowComposableCalls Updater<T>.() -> Unit
+) {
+    if (currentComposer.applier !is E) invalidApplier()
+    currentComposer.startReusableNode()
+    if (currentComposer.inserting) {
+        currentComposer.createNode { factory() }
+    } else {
+        currentComposer.useNode()
+    }
+    currentComposer.disableReusing()
+    Updater<T>(currentComposer).update()
+    currentComposer.enableReusing()
+    currentComposer.endNode()
+}
+
+/**
  * Emits a node into the composition of type [T]. Nodes emitted inside of [content] will become
  * children of the emitted node.
  *
@@ -238,6 +294,48 @@
 }
 
 /**
+ * Emits a recyclable node into the composition of type [T]. Nodes emitted inside of [content] will
+ * become children of the emitted node.
+ *
+ * This function will throw a runtime exception if [E] is not a subtype of the applier of the
+ * [currentComposer].
+ *
+ * @sample androidx.compose.runtime.samples.CustomTreeComposition
+ *
+ * @param factory A function which will create a new instance of [T]. This function is NOT
+ * guaranteed to be called in place.
+ * @param update A function to perform updates on the node. This will run every time emit is
+ * executed. This function is called in place and will be inlined.
+ * @param content the composable content that will emit the "children" of this node.
+ *
+ * @see Updater
+ * @see Applier
+ * @see Composition
+ */
+// ComposeNode is a special case of readonly composable and handles creating its own groups, so
+// it is okay to use.
+@Suppress("NONREADONLY_CALL_IN_READONLY_COMPOSABLE")
+@Composable
+inline fun <T : Any?, reified E : Applier<*>> ReusableComposeNode(
+    noinline factory: () -> T,
+    update: @DisallowComposableCalls Updater<T>.() -> Unit,
+    content: @Composable () -> Unit
+) {
+    if (currentComposer.applier !is E) invalidApplier()
+    currentComposer.startReusableNode()
+    if (currentComposer.inserting) {
+        currentComposer.createNode(factory)
+    } else {
+        currentComposer.useNode()
+    }
+    currentComposer.disableReusing()
+    Updater<T>(currentComposer).update()
+    currentComposer.enableReusing()
+    content()
+    currentComposer.endNode()
+}
+
+/**
  * Emits a node into the composition of type [T]. Nodes emitted inside of [content] will become
  * children of the emitted node.
  *
@@ -284,6 +382,55 @@
     currentComposer.endNode()
 }
 
+/**
+ * Emits a recyclable node into the composition of type [T]. Nodes emitted inside of [content] will
+ * become children of the emitted node.
+ *
+ * This function will throw a runtime exception if [E] is not a subtype of the applier of the
+ * [currentComposer].
+ *
+ * @sample androidx.compose.runtime.samples.CustomTreeComposition
+ *
+ * @param factory A function which will create a new instance of [T]. This function is NOT
+ * guaranteed to be called in place.
+ * @param update A function to perform updates on the node. This will run every time emit is
+ * executed. This function is called in place and will be inlined.
+ * @param skippableUpdate A function to perform updates on the node. Unlike [update], this
+ * function is Composable and will therefore be skipped unless it has been invalidated by some
+ * other mechanism. This can be useful to perform expensive calculations for updating the node
+ * where the calculations are likely to have the same inputs over time, so the function's
+ * execution can be skipped.
+ * @param content the composable content that will emit the "children" of this node.
+ *
+ * @see Updater
+ * @see SkippableUpdater
+ * @see Applier
+ * @see Composition
+ */
+@Composable @ExplicitGroupsComposable
+inline fun <T, reified E : Applier<*>> ReusableComposeNode(
+    noinline factory: () -> T,
+    update: @DisallowComposableCalls Updater<T>.() -> Unit,
+    noinline skippableUpdate: @Composable SkippableUpdater<T>.() -> Unit,
+    content: @Composable () -> Unit
+) {
+    if (currentComposer.applier !is E) invalidApplier()
+    currentComposer.startReusableNode()
+    if (currentComposer.inserting) {
+        currentComposer.createNode(factory)
+    } else {
+        currentComposer.useNode()
+    }
+    currentComposer.disableReusing()
+    Updater<T>(currentComposer).update()
+    SkippableUpdater<T>(currentComposer).skippableUpdate()
+    currentComposer.enableReusing()
+    currentComposer.startReplaceableGroup(0x7ab4aae9)
+    content()
+    currentComposer.endReplaceableGroup()
+    currentComposer.endNode()
+}
+
 @PublishedApi
 internal fun invalidApplier(): Unit = error("Invalid applier")
 
diff --git a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Composer.kt b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Composer.kt
index 422c2e3..3f84fc1 100644
--- a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Composer.kt
+++ b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Composer.kt
@@ -378,7 +378,7 @@
      * execution and can only either inserted, removed, or replaced. For example, the group
      * created by most control flow constructs such as an `if` statement are replacable groups.
      *
-     * @param key An compiler generated key based on the source location of the call.
+     * @param key A compiler generated key based on the source location of the call.
      */
     @ComposeCompilerApi
     fun startReplaceableGroup(key: Int)
@@ -386,20 +386,6 @@
     /**
      * A Compose compiler plugin API. DO NOT call directly.
      *
-     * Start a replacable group. A replacable group is a group that cannot be moved during
-     * execution and can only either inserted, removed, or replaced. For example, the group
-     * created by most control flow constructs such as an `if` statement are replacable groups.
-     *
-     * @param key An compiler generated key based on the source location of the call.
-     * @param sourceInformation An optional string value to that provides the compose tools enough
-     * information to calculate the source location calls made in the group.
-     */
-    @ComposeCompilerApi
-    fun startReplaceableGroup(key: Int, sourceInformation: String?)
-
-    /**
-     * A Compose compiler plugin API. DO NOT call directly.
-     *
      * Called at the end of a replacable group.
      *
      * @see startRestartGroup
@@ -418,7 +404,7 @@
      * the state and nodes generated by a loop to move with the composition implied by the key
      * passed to [key][androidx.compose.runtime.key].
 
-     * @param key An compiler generated key based on the source location of the call.
+     * @param key A compiler generated key based on the source location of the call.
      */
     @ComposeCompilerApi
     fun startMovableGroup(key: Int, dataKey: Any?)
@@ -426,24 +412,6 @@
     /**
      * A Compose compiler plugin API. DO NOT call directly.
      *
-     * Start a movable group. A movable group is one that can be moved based on the value of
-     * [dataKey] which is typically supplied by the [key][androidx.compose.runtime.key] pseudo
-     * compiler function.
-     *
-     * A movable group implements the semantics of [key][androidx.compose.runtime.key] which allows
-     * the state and nodes generated by a loop to move with the composition implied by the key
-     * passed to [key][androidx.compose.runtime.key].
-
-     * @param key An compiler generated key based on the source location of the call.
-     * @param sourceInformation An optional string value to that provides the compose tools enough
-     * information to calculate the source location calls made in the group.
-     */
-    @ComposeCompilerApi
-    fun startMovableGroup(key: Int, dataKey: Any?, sourceInformation: String?)
-
-    /**
-     * A Compose compiler plugin API. DO NOT call directly.
-     *
      * Called at the end of a movable group.
      *
      * @see startMovableGroup
@@ -481,7 +449,7 @@
      * recomposed on demand based on the lambda passed to
      * [updateScope][ScopeUpdateScope.updateScope] when [endRestartGroup] is called
      *
-     * @param key An compiler generated key based on the source location of the call.
+     * @param key A compiler generated key based on the source location of the call.
      * @return the instance of the composer to use for the rest of the function.
      */
     @ComposeCompilerApi
@@ -490,21 +458,6 @@
     /**
      * A Compose compiler plugin API. DO NOT call directly.
      *
-     * Called to record a group for a [Composable] function and starts a group that can be
-     * recomposed on demand based on the lambda passed to
-     * [updateScope][ScopeUpdateScope.updateScope] when [endRestartGroup] is called
-     *
-     * @param key An compiler generated key based on the source location of the call.
-     * @param sourceInformation An optional string value to that provides the compose tools enough
-     * information to calculate the source location calls made in the group.
-     * @return the instance of the composer to use for the rest of the function.
-     */
-    @ComposeCompilerApi
-    fun startRestartGroup(key: Int, sourceInformation: String?): Composer
-
-    /**
-     * A Compose compiler plugin API. DO NOT call directly.
-     *
      * Called to end a restart group.
      */
     @ComposeCompilerApi
@@ -513,6 +466,38 @@
     /**
      * A Compose compiler plugin API. DO NOT call directly.
      *
+     * Record the source information string for a group. This must be immediately called after the
+     * start of a group.
+     *
+     * @param sourceInformation An string value to that provides the compose tools enough
+     * information to calculate the source location of calls to composable functions.
+     */
+    fun sourceInformation(sourceInformation: String)
+
+    /**
+     * A compose compiler plugin API. DO NOT call directly.
+     *
+     * Record a source information marker. This marker can be used in place of a group that would
+     * have contained the information but was elided as the compiler plugin determined the group
+     * was not necessary such as when a function is marked with [ReadOnlyComposable].
+     *
+     * @param key A compiler generated key based on the source location of the call.
+     * @param sourceInformation An string value to that provides the compose tools enough
+     * information to calculate the source location of calls to composable functions.
+     *
+     */
+    fun sourceInformationMarkerStart(key: Int, sourceInformation: String)
+
+    /**
+     * A compose compiler plugin API. DO NOT call directly.
+     *
+     * Record the end of the marked source information range.
+     */
+    fun sourceInformationMarkerEnd()
+
+    /**
+     * A Compose compiler plugin API. DO NOT call directly.
+     *
      * Skips the composer to the end of the current group. This generated by the compiler to when
      * the body of a [Composable] function can be skipped typically because the parameters to the
      * function are equal to the values passed to it in the previous composition.
@@ -544,6 +529,16 @@
     /**
      * A Compose compiler plugin API. DO NOT call directly.
      *
+     * Start a group that tracks a the code that will create or update a node that is generated
+     * as part of the tree implied by the composition. A reusable node can be reused in a
+     * reusable group even if the group key is changed.
+     */
+    @ComposeCompilerApi
+    fun startReusableNode()
+
+    /**
+     * A Compose compiler plugin API. DO NOT call directly.
+     *
      * Report the [factory] that will be used to create the node that will be generated into the
      * tree implied by the composition. This will only be called if [inserting] is is `true`.
      *
@@ -573,6 +568,48 @@
     /**
      * A Compose compiler plugin API. DO NOT call directly.
      *
+     * Start a reuse group. Unlike a movable group, in a reuse group if the [dataKey] changes
+     * the composition shifts into a reusing state cause the composer to act like it is
+     * inserting (e.g. [cache] acts as if all values are invalid, [changed] always returns
+     * true, etc.) even though it is recomposing until it encounters a reusable node. If the
+     * node is reusable it temporarily shifts into recomposition for the node and then shifts
+     * back to reusing for the children.  If a non-reusable node is generated the composer
+     * shifts to inserting for the node and all of its children.
+     *
+     * @param key An compiler generated key based on the source location of the call.
+     * @param dataKey A key provided by the [ReusableContent] composable function that is used to
+     * determine if the composition shifts into a reusing state for this group.
+     */
+    @ComposeCompilerApi
+    fun startReusableGroup(key: Int, dataKey: Any?)
+
+    /**
+     * A Compose compiler plugin API. DO NOT call directly.
+     *
+     * Called at the end of a reusable group.
+     */
+    @ComposeCompilerApi
+    fun endReusableGroup()
+
+    /**
+     * A Compose compiler plugin API. DO NOT call directly.
+     *
+     * Temporarily disable reusing if it is enabled.
+     */
+    @ComposeCompilerApi
+    fun disableReusing()
+
+    /**
+     * A Compose compiler plugin API. DO NOT call directly.
+     *
+     * Reenable reusing if it was previously enabled before the last call to [disableReusing].
+     */
+    @ComposeCompilerApi
+    fun enableReusing()
+
+    /**
+     * A Compose compiler plugin API. DO NOT call directly.
+     *
      * Schedule [block] to called with [value]. This is intended to update the node generated by
      * [createNode] to changes discovered by composition.
      *
@@ -907,6 +944,52 @@
 }
 
 /**
+ * A Compose internal function. DO NOT call directly.
+ *
+ * Records source information that can be used for tooling to determine the source location of
+ * the corresponding composable function. By default, this function is declared as having no
+ * side-effects. It is safe for code shrinking tools (such as R8 or ProGuard) to remove it.
+ */
+@ComposeCompilerApi
+fun sourceInformation(composer: Composer, sourceInformation: String) {
+    composer.sourceInformation(sourceInformation)
+}
+
+/**
+ * A Compose internal function. DO NOT call directly.
+ *
+ * Records the start of a source information marker that can be used for tooling to determine the
+ * source location of the corresponding composable function that otherwise don't require tracking
+ * information such as [ReadOnlyComposable] functions. By default, this function is declared as
+ * having no side-effects. It is safe for code shrinking tools (such as R8 or ProGuard) to remove
+ * it.
+ *
+ * Important that both [sourceInformationMarkerStart] and [sourceInformationMarkerEnd] are removed
+ * together or both kept. Removing only one will cause incorrect runtime behavior.
+ */
+@ComposeCompilerApi
+fun sourceInformationMarkerStart(composer: Composer, key: Int, sourceInformation: String) {
+    composer.sourceInformationMarkerStart(key, sourceInformation)
+}
+
+/**
+ * A Compose internal function. DO NOT call directly.
+ *
+ * Records the end of a source information marker that can be used for tooling to determine the
+ * source location of the corresponding composable function that otherwise don't require tracking
+ * information such as [ReadOnlyComposable] functions. By default, this function is declared as
+ * having no side-effects. It is safe for code shrinking tools (such as R8 or ProGuard) to remove
+ * it.
+ *
+ * Important that both [sourceInformationMarkerStart] and [sourceInformationMarkerEnd] are removed
+ * together or both kept. Removing only one will cause incorrect runtime behavior.
+ */
+@ComposeCompilerApi
+fun sourceInformationMarkerEnd(composer: Composer) {
+    composer.sourceInformationMarkerEnd()
+}
+
+/**
  * Implementation of a composer for a mutable tree.
  */
 internal class ComposerImpl(
@@ -951,6 +1034,8 @@
     private val providerUpdates = HashMap<Int, CompositionLocalMap>()
     private var providersInvalid = false
     private val providersInvalidStack = IntStack()
+    private var reusing = false
+    private var reusingGroup = -1
     private var childrenComposing: Int = 0
     private var snapshot = currentSnapshot()
 
@@ -999,10 +1084,6 @@
     @ComposeCompilerApi
     override fun startReplaceableGroup(key: Int) = start(key, null, false, null)
 
-    @ComposeCompilerApi
-    override fun startReplaceableGroup(key: Int, sourceInformation: String?) =
-        start(key, null, false, sourceInformation)
-
     /**
      * Indicates the end of a "Replaceable Group" at the current execution position. A
      * Replaceable Group is a group which cannot be moved between its siblings, but
@@ -1082,10 +1163,6 @@
     @ComposeCompilerApi
     override fun startMovableGroup(key: Int, dataKey: Any?) = start(key, dataKey, false, null)
 
-    @ComposeCompilerApi
-    override fun startMovableGroup(key: Int, dataKey: Any?, sourceInformation: String?) =
-        start(key, dataKey, false, sourceInformation)
-
     /**
      * Indicates the end of a "Movable Group" at the current execution position. A Movable Group is
      * a group which can be moved or reordered between its siblings and retain slot table state,
@@ -1173,7 +1250,7 @@
      */
     @ComposeCompilerApi
     override val skipping: Boolean get() {
-        return !inserting &&
+        return !inserting && !reusing &&
             !providersInvalid &&
             currentRecomposeScope?.requiresRecompose == false
     }
@@ -1195,92 +1272,6 @@
         collectParameterInformation = true
     }
 
-    /**
-     * Helper for collecting remember observers for later strictly ordered dispatch.
-     *
-     * This includes support for the deprecated [RememberObserver] which should be
-     * removed with it.
-     */
-    private class RememberEventDispatcher(
-        private val abandoning: MutableSet<RememberObserver>
-    ) : RememberManager {
-        private val remembering = mutableListOf<RememberObserver>()
-        private val forgetting = mutableListOf<RememberObserver>()
-        private val sideEffects = mutableListOf<() -> Unit>()
-
-        override fun remembering(instance: RememberObserver) {
-            forgetting.lastIndexOf(instance).let { index ->
-                if (index >= 0) {
-                    forgetting.removeAt(index)
-                    abandoning.remove(instance)
-                } else {
-                    remembering.add(instance)
-                }
-            }
-        }
-
-        override fun forgetting(instance: RememberObserver) {
-            remembering.lastIndexOf(instance).let { index ->
-                if (index >= 0) {
-                    remembering.removeAt(index)
-                    abandoning.remove(instance)
-                } else {
-                    forgetting.add(instance)
-                }
-            }
-        }
-
-        override fun sideEffect(effect: () -> Unit) {
-            sideEffects += effect
-        }
-
-        val hasEffects: Boolean
-            get() = sideEffects.isNotEmpty() ||
-                forgetting.isNotEmpty() ||
-                remembering.isNotEmpty()
-
-        fun dispatchRememberObservers() {
-            // Send forgets
-            if (forgetting.isNotEmpty()) {
-                for (index in forgetting.indices.reversed()) {
-                    val instance = forgetting[index]
-                    if (instance !in abandoning)
-                        instance.onForgotten()
-                }
-            }
-
-            // Send remembers
-            if (remembering.isNotEmpty()) {
-                remembering.fastForEach { instance ->
-                    abandoning.remove(instance)
-                    instance.onRemembered()
-                }
-            }
-        }
-
-        fun dispatchSideEffects() {
-            if (sideEffects.isNotEmpty()) {
-                sideEffects.fastForEach { sideEffect ->
-                    sideEffect()
-                }
-                sideEffects.clear()
-            }
-        }
-
-        fun dispatchAbandons() {
-            if (abandoning.isNotEmpty()) {
-                trace("Compose:dispatchAbandons") {
-                    val iterator = abandoning.iterator()
-                    while (iterator.hasNext()) {
-                        val instance = iterator.next()
-                        iterator.remove()
-                        instance.onAbandoned()
-                    }
-                }
-            }
-        }
-    }
-
     @OptIn(InternalComposeApi::class)
     internal fun dispose() {
         trace("Compose:Composer.dispose") {
@@ -1289,17 +1280,6 @@
             invalidations.clear()
             changes.clear()
             applier.clear()
-            if (slotTable.groupsSize > 0) {
-                val manager = RememberEventDispatcher(abandonSet)
-                slotTable.write { writer ->
-                    writer.removeCurrentGroup(manager)
-                }
-                providerUpdates.clear()
-                applier.clear()
-                manager.dispatchRememberObservers()
-            } else {
-                applier.clear()
-            }
             isDisposed = true
         }
     }
@@ -1336,6 +1316,16 @@
      * is scheduled to be inserted at the current location.
      */
     override fun startNode() {
+        val key = if (inserting) nodeKey
+        else if (reusing)
+            if (reader.groupKey == nodeKey) nodeKeyReplace else nodeKey
+        else if (reader.groupKey == nodeKeyReplace) nodeKeyReplace
+        else nodeKey
+        start(key, null, true, null)
+        nodeExpected = true
+    }
+
+    override fun startReusableNode() {
         start(nodeKey, null, true, null)
         nodeExpected = true
     }
@@ -1383,6 +1373,31 @@
      */
     override fun endNode() = end(isNode = true)
 
+    override fun startReusableGroup(key: Int, dataKey: Any?) {
+        if (reader.groupKey == key && reader.groupAux != dataKey && reusingGroup < 0) {
+            // Starting to reuse nodes
+            reusingGroup = reader.currentGroup
+            reusing = true
+        }
+        start(key, null, false, dataKey)
+    }
+
+    override fun endReusableGroup() {
+        if (reusing && reader.parent == reusingGroup) {
+            reusingGroup = -1
+            reusing = false
+        }
+        end(isNode = false)
+    }
+
+    override fun disableReusing() {
+        reusing = false
+    }
+
+    override fun enableReusing() {
+        reusing = reusingGroup >= 0
+    }
+
     /**
      * Schedule a change to be applied to a node's property. This change will be applied to the
      * node that is the current node in the tree which was either created by [createNode].
@@ -1413,7 +1428,7 @@
     internal fun nextSlot(): Any? = if (inserting) {
         validateNodeNotExpected()
         Composer.Empty
-    } else reader.next()
+    } else reader.next().let { if (reusing) Composer.Empty else it }
 
     /**
      * Determine if the current slot table value is equal to the given value, if true, the value
@@ -1785,7 +1800,7 @@
     private fun start(key: Int, objectKey: Any?, isNode: Boolean, data: Any?) {
         validateNodeNotExpected()
 
-        updateCompoundKeyWhenWeEnterGroup(key, objectKey)
+        updateCompoundKeyWhenWeEnterGroup(key, objectKey, data)
 
         // Check for the insert fast path. If we are already inserting (creating nodes) then
         // there is no need to track insert, deletes and moves with a pending changes object.
@@ -1923,10 +1938,18 @@
 
         if (inserting) {
             val parent = writer.parent
-            updateCompoundKeyWhenWeExitGroup(writer.groupKey(parent), writer.groupObjectKey(parent))
+            updateCompoundKeyWhenWeExitGroup(
+                writer.groupKey(parent),
+                writer.groupObjectKey(parent),
+                writer.groupAux(parent)
+            )
         } else {
             val parent = reader.parent
-            updateCompoundKeyWhenWeExitGroup(reader.groupKey(parent), reader.groupObjectKey(parent))
+            updateCompoundKeyWhenWeExitGroup(
+                reader.groupKey(parent),
+                reader.groupObjectKey(parent),
+                reader.groupAux(parent)
+            )
         }
         var expectedNodeCount = groupNodeCount
         val pending = pending
@@ -2310,13 +2333,17 @@
                 recomposeGroup,
                 recomposeKey
             ) rol 3
-            ) xor (
-            if (reader.hasObjectKey(group))
-                reader.groupObjectKey(group)?.hashCode() ?: 0
-            else reader.groupKey(group)
-            )
+            ) xor reader.groupCompoundKeyPart(group)
     }
 
+    private fun SlotReader.groupCompoundKeyPart(group: Int) =
+        if (hasObjectKey(group)) groupObjectKey(group)?.hashCode() ?: 0
+        else groupKey(group).let {
+            if (it == reusingGroup) groupAux(group)?.let { aux ->
+                if (aux == Composer.Empty) it else aux.hashCode()
+            } ?: it else it
+        }
+
     internal fun invalidateForResult(scope: RecomposeScopeImpl): InvalidationResult {
         if (scope.defaultsInScope) {
             scope.defaultsInvalid = true
@@ -2369,11 +2396,12 @@
             val reader = reader
             val key = reader.groupKey
             val dataKey = reader.groupObjectKey
-            updateCompoundKeyWhenWeEnterGroup(key, dataKey)
+            val aux = reader.groupAux
+            updateCompoundKeyWhenWeEnterGroup(key, dataKey, aux)
             startReaderGroup(reader.isNode, null)
             recomposeToGroupEnd()
             reader.endGroup()
-            updateCompoundKeyWhenWeExitGroup(key, dataKey)
+            updateCompoundKeyWhenWeExitGroup(key, dataKey, aux)
         }
     }
 
@@ -2409,13 +2437,6 @@
         return this
     }
 
-    @ComposeCompilerApi
-    override fun startRestartGroup(key: Int, sourceInformation: String?): Composer {
-        start(key, null, false, sourceInformation)
-        addRecomposeScope()
-        return this
-    }
-
     private fun addRecomposeScope() {
         if (inserting) {
             val scope = RecomposeScopeImpl(composition as CompositionImpl)
@@ -2464,6 +2485,23 @@
         return result
     }
 
+    @ComposeCompilerApi
+    override fun sourceInformation(sourceInformation: String) {
+        if (inserting) {
+            writer.insertAux(sourceInformation)
+        }
+    }
+
+    @ComposeCompilerApi
+    override fun sourceInformationMarkerStart(key: Int, sourceInformation: String) {
+        start(key, objectKey = null, isNode = false, data = sourceInformation)
+    }
+
+    @ComposeCompilerApi
+    override fun sourceInformationMarkerEnd() {
+        end(isNode = false)
+    }
+
     /**
      * Synchronously compose the initial composition of [content]. This collects all the changes
      * which must be applied by [ControlledComposition.applyChanges] to build the tree implied by
@@ -2962,9 +3000,12 @@
         }
     }
 
-    private fun updateCompoundKeyWhenWeEnterGroup(groupKey: Int, dataKey: Any?) {
+    private fun updateCompoundKeyWhenWeEnterGroup(groupKey: Int, dataKey: Any?, data: Any?) {
         if (dataKey == null)
-            updateCompoundKeyWhenWeEnterGroupKeyHash(groupKey)
+            if (data != null && groupKey == reuseKey && data != Composer.Empty)
+                updateCompoundKeyWhenWeEnterGroupKeyHash(data.hashCode())
+            else
+                updateCompoundKeyWhenWeEnterGroupKeyHash(groupKey)
         else
             updateCompoundKeyWhenWeEnterGroupKeyHash(dataKey.hashCode())
     }
@@ -2973,9 +3014,12 @@
         compoundKeyHash = (compoundKeyHash rol 3) xor keyHash
     }
 
-    private fun updateCompoundKeyWhenWeExitGroup(groupKey: Int, dataKey: Any?) {
+    private fun updateCompoundKeyWhenWeExitGroup(groupKey: Int, dataKey: Any?, data: Any?) {
         if (dataKey == null)
-            updateCompoundKeyWhenWeExitGroupKeyHash(groupKey)
+            if (data != null && groupKey == reuseKey && data != Composer.Empty)
+                updateCompoundKeyWhenWeExitGroupKeyHash(data.hashCode())
+            else
+                updateCompoundKeyWhenWeExitGroupKeyHash(groupKey)
         else
             updateCompoundKeyWhenWeExitGroupKeyHash(dataKey.hashCode())
     }
@@ -3110,7 +3154,6 @@
         composer.apply<Unit, T>(Unit, { this.block() })
     }
 }
-
 @Suppress("EXPERIMENTAL_FEATURE_WARNING")
 inline class SkippableUpdater<T> constructor(
     @PublishedApi internal val composer: Composer
@@ -3122,7 +3165,7 @@
     }
 }
 
-private fun SlotWriter.removeCurrentGroup(rememberManager: RememberManager) {
+internal fun SlotWriter.removeCurrentGroup(rememberManager: RememberManager) {
     // Notify the lifecycle manager of any observers leaving the slot table
     // The notification order should ensure that listeners are notified of leaving
     // in opposite order that they are notified of entering.
@@ -3300,9 +3343,12 @@
 // a unique key.
 private const val rootKey = 100
 
-// An arbitrary value paired with a boxed Int or a JoinKey data key.
+// An arbitrary key value for a node.
 private const val nodeKey = 125
 
+// An arbitrary key value for a node used to force the node to be replaced.
+private const val nodeKeyReplace = 126
+
 @PublishedApi
 internal const val invocationKey = 200
 
@@ -3338,3 +3384,6 @@
 
 @PublishedApi
 internal val reference: Any = OpaqueKey("reference")
+
+@PublishedApi
+internal const val reuseKey = 207
diff --git a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Composition.kt b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Composition.kt
index 153ba94..9303c6c 100644
--- a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Composition.kt
+++ b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Composition.kt
@@ -279,7 +279,7 @@
     private val parent: CompositionContext,
 
     /**
-     * The applier to use to update the tree managed by the compositon.
+     * The applier to use to update the tree managed by the composition.
      */
     private val applier: Applier<*>,
 
@@ -344,7 +344,7 @@
      * As [RecomposeScope]s are removed the corresponding entries in the observations set must be
      * removed as well. This process is expensive so should only be done if it is certain the
      * [observations] set contains [RecomposeScope] that is no longer needed. [pendingInvalidScopes]
-     * is set to true whenver a [RecomposeScope] is removed from the [slotTable].
+     * is set to true whenever a [RecomposeScope] is removed from the [slotTable].
      */
     internal var pendingInvalidScopes = false
 
@@ -459,8 +459,17 @@
             if (!disposed) {
                 disposed = true
                 composable = {}
+                if (slotTable.groupsSize > 0) {
+                    val manager = RememberEventDispatcher(abandonSet)
+                    slotTable.write { writer ->
+                        writer.removeCurrentGroup(manager)
+                    }
+                    applier.clear()
+                    manager.dispatchRememberObservers()
+                }
                 composer.dispose()
                 parent.unregisterComposition(this)
+                parent.unregisterComposition(this)
             }
         }
     }
diff --git a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/SlotTable.kt b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/SlotTable.kt
index 57cf35b..8e8a725 100644
--- a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/SlotTable.kt
+++ b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/SlotTable.kt
@@ -1127,6 +1127,37 @@
     }
 
     /**
+     * Insert aux data into the parent group.
+     *
+     * This must be done only after at most one value has been inserted into the slot table for
+     * the group.
+     */
+    fun insertAux(value: Any?) {
+        check(insertCount >= 0) { "Cannot insert auxiliary data when not inserting" }
+        val parent = parent
+        val parentGroupAddress = groupIndexToAddress(parent)
+        check(!groups.hasAux(parentGroupAddress)) { "Group already has auxiliary data" }
+        insertSlots(1, parent)
+        val auxIndex = groups.auxIndex(parentGroupAddress)
+        val auxAddress = dataIndexToDataAddress(auxIndex)
+        if (currentSlot > auxIndex) {
+            // One or more values were inserted into the slot table before the aux value, we need
+            // to move them. Currently we only will run into one or two slots (the recompose
+            // scope inserted by a restart group and the lambda value in a composableLambda
+            // instance) so this is the only case currently supported.
+            val slotsToMove = currentSlot - auxIndex
+            check(slotsToMove < 3) { "Moving more than two slot not supported" }
+            if (slotsToMove > 1) {
+                slots[auxAddress + 2] = slots[auxAddress + 1]
+            }
+            slots[auxAddress + 1] = slots[auxAddress]
+        }
+        groups.addAux(parentGroupAddress)
+        slots[auxAddress] = value
+        currentSlot++
+    }
+
+    /**
      * Updates the node for the current node group to [value].
      */
     fun updateNode(value: Any?) = updateNodeOfGroup(currentGroup, value)
@@ -1765,7 +1796,7 @@
             check(!anchorsRemoved) { "Unexpectedly removed anchors" }
 
             // Update the node count.
-            nodeCount += groups.nodeCount(currentGroup)
+            nodeCount += if (groups.isNode(currentGroup)) 1 else groups.nodeCount(currentGroup)
 
             // Move current passed the insert
             this.currentGroup = currentGroup + groupsToMove
@@ -2463,7 +2494,7 @@
 // 31 30 29 28_27 26 25 24_23 22 21 20_19 18 17 16__15 14 13 12_11 10 09 08_07 06 05 04_03 02 01 00
 // 0  n  ks ds r |                                node count                                       |
 // where n is set when the group represents a node
-// where ks is whether the group has a data key slot
+// where ks is whether the group has a object key slot
 // where ds is whether the group has a group data slot
 // where r is always 0 (future use)
 
@@ -2508,6 +2539,11 @@
 }
 private fun IntArray.hasAux(address: Int) =
     this[address * Group_Fields_Size + GroupInfo_Offset] and Aux_Mask != 0
+private fun IntArray.addAux(address: Int) {
+    val arrayIndex = address * Group_Fields_Size + GroupInfo_Offset
+    this[arrayIndex] = this[arrayIndex] or Aux_Mask
+}
+
 private fun IntArray.auxIndex(address: Int) = (address * Group_Fields_Size).let { slot ->
     if (slot >= size) size
     else this[slot + DataAnchor_Offset] +
diff --git a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/internal/ComposableLambda.kt b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/internal/ComposableLambda.kt
index f2cdb63..368da08 100644
--- a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/internal/ComposableLambda.kt
+++ b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/internal/ComposableLambda.kt
@@ -47,8 +47,7 @@
 /* ktlint-disable parameter-list-wrapping */ // TODO(https://github.com/pinterest/ktlint/issues/921): reenable
 internal expect class ComposableLambdaImpl(
     key: Int,
-    tracked: Boolean,
-    sourceInformation: String?
+    tracked: Boolean
 ) : ComposableLambda {
     fun update(block: Any)
 }
@@ -327,13 +326,12 @@
     composer: Composer,
     key: Int,
     tracked: Boolean,
-    sourceInformation: String?,
     block: Any
 ): ComposableLambda {
     composer.startReplaceableGroup(key)
     val slot = composer.rememberedValue()
     val result = if (slot === Composer.Empty) {
-        val value = ComposableLambdaImpl(key, tracked, sourceInformation)
+        val value = ComposableLambdaImpl(key, tracked)
         composer.updateRememberedValue(value)
         value
     } else {
@@ -349,7 +347,6 @@
 fun composableLambdaInstance(
     key: Int,
     tracked: Boolean,
-    sourceInformation: String?,
     block: Any
 ): ComposableLambda =
-    ComposableLambdaImpl(key, tracked, sourceInformation).apply { update(block) }
+    ComposableLambdaImpl(key, tracked).apply { update(block) }
diff --git a/compose/runtime/runtime/src/jvmMain/kotlin/androidx/compose/runtime/internal/ComposableLambda.jvm.kt b/compose/runtime/runtime/src/jvmMain/kotlin/androidx/compose/runtime/internal/ComposableLambda.jvm.kt
index 572c146..172a413 100644
--- a/compose/runtime/runtime/src/jvmMain/kotlin/androidx/compose/runtime/internal/ComposableLambda.jvm.kt
+++ b/compose/runtime/runtime/src/jvmMain/kotlin/androidx/compose/runtime/internal/ComposableLambda.jvm.kt
@@ -36,8 +36,7 @@
 /* ktlint-disable parameter-list-wrapping */ // TODO(https://github.com/pinterest/ktlint/issues/921): reenable
 internal actual class ComposableLambdaImpl actual constructor(
     val key: Int,
-    private val tracked: Boolean,
-    private val sourceInformation: String?
+    private val tracked: Boolean
 ) : ComposableLambda {
     private var _block: Any? = null
     private var scope: RecomposeScope? = null
@@ -102,7 +101,7 @@
     }
 
     override operator fun invoke(c: Composer, changed: Int): Any? {
-        val c = c.startRestartGroup(key, sourceInformation)
+        val c = c.startRestartGroup(key)
         trackRead(c)
         val dirty = changed or if (c.changed(this)) differentBits(0) else sameBits(0)
         val result = (_block as (c: Composer, changed: Int) -> Any?)(c, dirty)
@@ -111,7 +110,7 @@
     }
 
     override operator fun invoke(p1: Any?, c: Composer, changed: Int): Any? {
-        val c = c.startRestartGroup(key, sourceInformation)
+        val c = c.startRestartGroup(key)
         trackRead(c)
         val dirty = changed or if (c.changed(this)) differentBits(1) else sameBits(1)
         val result = (
@@ -130,7 +129,7 @@
     }
 
     override operator fun invoke(p1: Any?, p2: Any?, c: Composer, changed: Int): Any? {
-        val c = c.startRestartGroup(key, sourceInformation)
+        val c = c.startRestartGroup(key)
         trackRead(c)
         val dirty = changed or if (c.changed(this)) differentBits(2) else sameBits(2)
         val result = (_block as (p1: Any?, p2: Any?, c: Composer, changed: Int) -> Any?)(
@@ -144,7 +143,7 @@
     }
 
     override operator fun invoke(p1: Any?, p2: Any?, p3: Any?, c: Composer, changed: Int): Any? {
-        val c = c.startRestartGroup(key, sourceInformation)
+        val c = c.startRestartGroup(key)
         trackRead(c)
         val dirty = changed or if (c.changed(this)) differentBits(3) else sameBits(3)
         val result = (
@@ -174,7 +173,7 @@
         c: Composer,
         changed: Int
     ): Any? {
-        val c = c.startRestartGroup(key, sourceInformation)
+        val c = c.startRestartGroup(key)
         trackRead(c)
         val dirty = changed or if (c.changed(this)) differentBits(4) else sameBits(4)
         val result = (
@@ -209,7 +208,7 @@
         c: Composer,
         changed: Int
     ): Any? {
-        val c = c.startRestartGroup(key, sourceInformation)
+        val c = c.startRestartGroup(key)
         trackRead(c)
         val dirty = changed or if (c.changed(this)) differentBits(5) else sameBits(5)
         val result = (
@@ -247,7 +246,7 @@
         c: Composer,
         changed: Int
     ): Any? {
-        val c = c.startRestartGroup(key, sourceInformation)
+        val c = c.startRestartGroup(key)
         trackRead(c)
         val dirty = changed or if (c.changed(this)) differentBits(6) else sameBits(6)
         val result = (
@@ -288,7 +287,7 @@
         c: Composer,
         changed: Int
     ): Any? {
-        val c = c.startRestartGroup(key, sourceInformation)
+        val c = c.startRestartGroup(key)
         trackRead(c)
         val dirty = changed or if (c.changed(this)) differentBits(7) else sameBits(7)
         val result = (
@@ -332,7 +331,7 @@
         c: Composer,
         changed: Int
     ): Any? {
-        val c = c.startRestartGroup(key, sourceInformation)
+        val c = c.startRestartGroup(key)
         trackRead(c)
         val dirty = changed or if (c.changed(this)) differentBits(8) else sameBits(8)
         val result = (
@@ -379,7 +378,7 @@
         c: Composer,
         changed: Int
     ): Any? {
-        val c = c.startRestartGroup(key, sourceInformation)
+        val c = c.startRestartGroup(key)
         trackRead(c)
         val dirty = changed or if (c.changed(this)) differentBits(9) else sameBits(9)
         val result = (
@@ -430,7 +429,7 @@
         changed: Int,
         changed1: Int
     ): Any? {
-        val c = c.startRestartGroup(key, sourceInformation)
+        val c = c.startRestartGroup(key)
         trackRead(c)
         val dirty = changed1 or if (c.changed(this)) differentBits(10) else sameBits(10)
         val result = (
@@ -486,7 +485,7 @@
         changed: Int,
         changed1: Int
     ): Any? {
-        val c = c.startRestartGroup(key, sourceInformation)
+        val c = c.startRestartGroup(key)
         trackRead(c)
         val dirty = changed1 or if (c.changed(this)) differentBits(11) else sameBits(11)
         val result = (
@@ -545,7 +544,7 @@
         changed: Int,
         changed1: Int
     ): Any? {
-        val c = c.startRestartGroup(key, sourceInformation)
+        val c = c.startRestartGroup(key)
         trackRead(c)
         val dirty = changed1 or if (c.changed(this)) differentBits(12) else sameBits(12)
         val result = (
@@ -607,7 +606,7 @@
         changed: Int,
         changed1: Int
     ): Any? {
-        val c = c.startRestartGroup(key, sourceInformation)
+        val c = c.startRestartGroup(key)
         trackRead(c)
         val dirty = changed1 or if (c.changed(this)) differentBits(13) else sameBits(13)
         val result = (
@@ -689,7 +688,7 @@
         changed: Int,
         changed1: Int
     ): Any? {
-        val c = c.startRestartGroup(key, sourceInformation)
+        val c = c.startRestartGroup(key)
         trackRead(c)
         val dirty = changed1 or if (c.changed(this)) differentBits(14) else sameBits(14)
         val result = (
@@ -775,7 +774,7 @@
         changed: Int,
         changed1: Int
     ): Any? {
-        val c = c.startRestartGroup(key, sourceInformation)
+        val c = c.startRestartGroup(key)
         trackRead(c)
         val dirty = changed1 or if (c.changed(this)) differentBits(15) else sameBits(15)
         val result = (
@@ -865,7 +864,7 @@
         changed: Int,
         changed1: Int
     ): Any? {
-        val c = c.startRestartGroup(key, sourceInformation)
+        val c = c.startRestartGroup(key)
         trackRead(c)
         val dirty = changed1 or if (c.changed(this)) differentBits(16) else sameBits(16)
         val result = (
@@ -959,7 +958,7 @@
         changed: Int,
         changed1: Int
     ): Any? {
-        val c = c.startRestartGroup(key, sourceInformation)
+        val c = c.startRestartGroup(key)
         trackRead(c)
         val dirty = changed1 or if (c.changed(this)) differentBits(17) else sameBits(17)
         val result = (
@@ -1057,7 +1056,7 @@
         changed: Int,
         changed1: Int
     ): Any? {
-        val c = c.startRestartGroup(key, sourceInformation)
+        val c = c.startRestartGroup(key)
         trackRead(c)
         val dirty = changed1 or if (c.changed(this)) differentBits(18) else sameBits(18)
         val result = (
diff --git a/compose/runtime/runtime/src/jvmMain/kotlin/androidx/compose/runtime/internal/ComposableLambdaN.jvm.kt b/compose/runtime/runtime/src/jvmMain/kotlin/androidx/compose/runtime/internal/ComposableLambdaN.jvm.kt
index 08e88f1..3193f395 100644
--- a/compose/runtime/runtime/src/jvmMain/kotlin/androidx/compose/runtime/internal/ComposableLambdaN.jvm.kt
+++ b/compose/runtime/runtime/src/jvmMain/kotlin/androidx/compose/runtime/internal/ComposableLambdaN.jvm.kt
@@ -30,7 +30,6 @@
 internal class ComposableLambdaNImpl(
     val key: Int,
     private val tracked: Boolean,
-    private val sourceInformation: String?,
     override val arity: Int
 ) : ComposableLambdaN {
     private var _block: Any? = null
@@ -112,7 +111,7 @@
         var c = args[realParams] as Composer
         val allArgsButLast = args.slice(0 until args.size - 1).toTypedArray()
         val lastChanged = args[args.size - 1] as Int
-        c = c.startRestartGroup(key, sourceInformation)
+        c = c.startRestartGroup(key)
         trackRead(c)
         val dirty = lastChanged or if (c.changed(this))
             differentBits(realParams)
@@ -146,14 +145,13 @@
     composer: Composer,
     key: Int,
     tracked: Boolean,
-    sourceInformation: String?,
     arity: Int,
     block: Any
 ): ComposableLambdaN {
     composer.startReplaceableGroup(key)
     val slot = composer.rememberedValue()
     val result = if (slot === Composer.Empty) {
-        val value = ComposableLambdaNImpl(key, tracked, sourceInformation, arity)
+        val value = ComposableLambdaNImpl(key, tracked, arity)
         composer.updateRememberedValue(value)
         value
     } else {
@@ -170,12 +168,10 @@
 fun composableLambdaNInstance(
     key: Int,
     tracked: Boolean,
-    sourceInformation: String?,
     arity: Int,
     block: Any
 ): ComposableLambdaN = ComposableLambdaNImpl(
     key,
     tracked,
-    sourceInformation,
     arity
 ).apply { update(block) }
diff --git a/compose/runtime/runtime/src/test/kotlin/androidx/compose/runtime/CompositionReusingTests.kt b/compose/runtime/runtime/src/test/kotlin/androidx/compose/runtime/CompositionReusingTests.kt
new file mode 100644
index 0000000..b698eae
--- /dev/null
+++ b/compose/runtime/runtime/src/test/kotlin/androidx/compose/runtime/CompositionReusingTests.kt
@@ -0,0 +1,195 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.runtime
+
+import androidx.compose.runtime.mock.CompositionTestScope
+import androidx.compose.runtime.mock.Linear
+import androidx.compose.runtime.mock.NonReusableLinear
+import androidx.compose.runtime.mock.NonReusableText
+import androidx.compose.runtime.mock.Text
+import androidx.compose.runtime.mock.View
+import androidx.compose.runtime.mock.compositionTest
+import androidx.compose.runtime.mock.expectChanges
+import androidx.compose.runtime.mock.flatten
+import androidx.compose.runtime.mock.revalidate
+import androidx.compose.runtime.mock.validate
+import kotlin.test.Test
+import kotlin.test.assertEquals
+import kotlin.test.assertNotEquals
+
+@Stable
+class CompositionReusingTests {
+
+    @Test
+    fun canReuse() = compositionTest {
+        var lastData: State<Int>? = null
+        var key by mutableStateOf(0)
+
+        compose {
+            ReusableContent(key) {
+                Linear {
+                    val data = remember { mutableStateOf(1) }
+                    lastData = data
+                    Text("Key = $key")
+                }
+            }
+        }
+
+        validate {
+            Linear {
+                Text("Key = $key")
+            }
+        }
+
+        val firstData = lastData
+        val nodes = root.flatten()
+
+        key++
+        expectChanges()
+        revalidate()
+
+        val nodesAfterChange = root.flatten()
+
+        // Ensure nodes are reused
+        assertArrayEquals(nodes, nodesAfterChange) { "${it.hashCode()}" }
+
+        // Ensure remembers are not reused
+        assertNotEquals(
+            firstData, lastData,
+            "Should not remember values when recycling"
+        )
+    }
+
+    @Test
+    fun canRecycleAroundNonReusable() = compositionTest {
+        var key by mutableStateOf(0)
+
+        compose {
+            ReusableContent(key) {
+                Linear {
+                    Text("Key = $key")
+                    NonReusableText("Non-recyclable key = $key")
+                }
+            }
+        }
+
+        validate {
+            Linear {
+                Text("Key = $key")
+                Text("Non-recyclable key = $key")
+            }
+        }
+
+        val recycleText = findTextWith("Key")
+        val nonRecycledText = findTextWith("Non-recyclable key")
+        key++
+        expectChanges()
+        revalidate()
+
+        assertEquals(recycleText, findTextWith("Key"), "Expected text to be recycled")
+        assertNotEquals(
+            nonRecycledText,
+            findTextWith("Non-recyclable key"),
+            "Expected non-recyclable text to be replaced"
+        )
+    }
+
+    @Test
+    fun recyclableNodesInNonReusableContainerNotRecycled() = compositionTest {
+        var key by mutableStateOf(0)
+
+        compose {
+            ReusableContent(key) {
+                Linear {
+                    Linear {
+                        Text("Key = $key")
+                    }
+                    NonReusableLinear {
+                        Text("Non-recyclable key = $key")
+                    }
+                    NonReusableLinear { }
+                }
+            }
+        }
+
+        validate {
+            Linear {
+                Linear {
+                    Text("Key = $key")
+                }
+                Linear {
+                    Text("Non-recyclable key = $key")
+                }
+                Linear { }
+            }
+        }
+
+        val recycleText = findTextWith("Key")
+        val nonRecycledText = findTextWith("Non-recyclable key")
+        key++
+        expectChanges()
+        revalidate()
+        verifyConsistent()
+
+        assertEquals(recycleText, findTextWith("Key"), "Expected text to be recycled")
+        assertNotEquals(
+            nonRecycledText,
+            findTextWith("Non-recyclable key"),
+            "Expected non-recyclable text to be replaced"
+        )
+    }
+
+    @Test
+    fun compositeHashCodeReflectsReusableChanges() = compositionTest {
+        var key by mutableStateOf(0)
+        var lastCompositeHash = 0
+
+        compose {
+            ReusableContent(key) {
+                Linear {
+                    Text("Key = $key")
+                    lastCompositeHash = currentCompositeKeyHash
+                }
+            }
+        }
+
+        validate {
+            Linear {
+                Text("Key = $key")
+            }
+        }
+
+        val firstCompositeHash = lastCompositeHash
+        key++
+        expectChanges()
+        revalidate()
+        assertNotEquals(firstCompositeHash, lastCompositeHash)
+    }
+}
+
+private fun View.findTextWith(contains: String) =
+    find { it.name == "text" && it.text?.contains(contains) == true }
+private fun CompositionTestScope.findTextWith(contains: String) = root.findTextWith(contains)
+
+private fun View.find(predicate: (view: View) -> Boolean): View? {
+    if (predicate(this)) return this
+    for (child in children) {
+        val found = child.find(predicate)
+        if (found != null) return found
+    }
+    return null
+}
\ No newline at end of file
diff --git a/compose/runtime/runtime/src/test/kotlin/androidx/compose/runtime/SlotTableTests.kt b/compose/runtime/runtime/src/test/kotlin/androidx/compose/runtime/SlotTableTests.kt
index 820b9764..f5498d2 100644
--- a/compose/runtime/runtime/src/test/kotlin/androidx/compose/runtime/SlotTableTests.kt
+++ b/compose/runtime/runtime/src/test/kotlin/androidx/compose/runtime/SlotTableTests.kt
@@ -1231,6 +1231,52 @@
         sourceTable.verifyWellFormed()
     }
 
+    @Test
+    fun testMovingANodeGroup() {
+        val sourceTable = SlotTable()
+        val anchors = mutableListOf<Anchor>()
+        sourceTable.write { writer ->
+            writer.beginInsert()
+            anchors.add(writer.anchor())
+            writer.startNode(10, 10)
+            writer.update(100)
+            writer.update(200)
+            writer.endGroup()
+            writer.endInsert()
+        }
+        sourceTable.verifyWellFormed()
+
+        val destinationTable = SlotTable()
+        destinationTable.write { writer ->
+            writer.beginInsert()
+            writer.startGroup(treeRoot)
+            writer.startGroup(1000)
+            writer.endGroup()
+            writer.endGroup()
+            writer.endInsert()
+        }
+        destinationTable.verifyWellFormed()
+
+        destinationTable.write { writer ->
+            writer.startGroup()
+            writer.startGroup()
+            writer.beginInsert()
+            writer.moveFrom(sourceTable, anchors.first().toIndexFor(sourceTable))
+            writer.endInsert()
+            writer.skipToGroupEnd()
+            writer.endGroup()
+            writer.skipToGroupEnd()
+            writer.endGroup()
+        }
+        destinationTable.verifyWellFormed()
+        destinationTable.read { reader ->
+            val anchor = anchors.first()
+            assertEquals(125, reader.groupKey(anchor))
+            assertEquals(10, reader.groupObjectKey(anchor.toIndexFor(destinationTable)))
+        }
+        sourceTable.verifyWellFormed()
+    }
+
     @org.junit.Test
     fun testMovingMultipleRootGroups() {
         val sourceTable = SlotTable()
@@ -3093,6 +3139,95 @@
         }
         slots.verifyWellFormed()
     }
+
+    @Test
+    fun canInsertAuxData() {
+        val slots = SlotTable().also {
+            it.write { writer ->
+                writer.insert {
+                    // Insert a normal aux data.
+                    writer.startData(10, 10, "10")
+                    writer.endGroup()
+
+                    // Insert using insertAux
+                    writer.startGroup(20)
+                    writer.insertAux("20")
+                    writer.endGroup()
+
+                    // Insert using insertAux after a slot value was added.
+                    writer.startGroup(30)
+                    writer.update(300)
+                    writer.insertAux("30")
+                    writer.endGroup()
+
+                    // Insert using insertAux after a group with an object key
+                    writer.startGroup(40, 40)
+                    writer.insertAux("40")
+                    writer.endGroup()
+
+                    // Insert aux into an object key with a value slot and then add another value.
+                    writer.startGroup(50, 50)
+                    writer.update(500)
+                    writer.insertAux("50")
+                    writer.update(501)
+                    writer.endGroup()
+
+                    // Insert aux after two slot values and then add another value.
+                    writer.startGroup(60)
+                    writer.update(600)
+                    writer.update(601)
+                    writer.insertAux("60")
+                    writer.update(602)
+                    writer.endGroup()
+
+                    // Write a trail group to ensure that the slot table is valid after the
+                    // insertAux
+                    writer.startGroup(1000)
+                    writer.update(10000)
+                    writer.update(10001)
+                    writer.endGroup()
+                }
+            }
+        }
+        slots.verifyWellFormed()
+        slots.read { reader ->
+            assertEquals(10, reader.groupKey)
+            assertEquals(10, reader.groupObjectKey)
+            assertEquals("10", reader.groupAux)
+            reader.skipGroup()
+            assertEquals(20, reader.groupKey)
+            assertEquals("20", reader.groupAux)
+            reader.skipGroup()
+            assertEquals(30, reader.groupKey)
+            assertEquals("30", reader.groupAux)
+            reader.startGroup()
+            assertEquals(300, reader.next())
+            reader.endGroup()
+            assertEquals(40, reader.groupKey)
+            assertEquals(40, reader.groupObjectKey)
+            assertEquals("40", reader.groupAux)
+            reader.skipGroup()
+            assertEquals(50, reader.groupKey)
+            assertEquals(50, reader.groupObjectKey)
+            assertEquals("50", reader.groupAux)
+            reader.startGroup()
+            assertEquals(500, reader.next())
+            assertEquals(501, reader.next())
+            reader.endGroup()
+            assertEquals(60, reader.groupKey)
+            assertEquals("60", reader.groupAux)
+            reader.startGroup()
+            assertEquals(600, reader.next())
+            assertEquals(601, reader.next())
+            assertEquals(602, reader.next())
+            reader.endGroup()
+            assertEquals(1000, reader.groupKey)
+            reader.startGroup()
+            assertEquals(10000, reader.next())
+            assertEquals(10001, reader.next())
+            reader.endGroup()
+        }
+    }
 }
 
 @OptIn(InternalComposeApi::class)
diff --git a/compose/runtime/runtime/src/test/kotlin/androidx/compose/runtime/mock/CompositionTest.kt b/compose/runtime/runtime/src/test/kotlin/androidx/compose/runtime/mock/CompositionTest.kt
index f2933d3..63abe7b 100644
--- a/compose/runtime/runtime/src/test/kotlin/androidx/compose/runtime/mock/CompositionTest.kt
+++ b/compose/runtime/runtime/src/test/kotlin/androidx/compose/runtime/mock/CompositionTest.kt
@@ -18,6 +18,7 @@
 
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.Composition
+import androidx.compose.runtime.ControlledComposition
 import androidx.compose.runtime.InternalComposeApi
 import androidx.compose.runtime.Recomposer
 import androidx.compose.runtime.snapshots.Snapshot
@@ -63,6 +64,12 @@
                 }
                 return recomposer.changeCount != changeCount
             }
+
+            override fun verifyConsistent() {
+                (composition as? ControlledComposition)?.verifyConsistent()
+            }
+
+            override var validator: (MockViewValidator.() -> Unit)? = null
         }
         scope.block()
         scope.composition?.dispose()
@@ -88,16 +95,32 @@
     fun advance(ignorePendingWork: Boolean = false): Boolean
 
     /**
+     * Verify the composition is well-formed.
+     */
+    fun verifyConsistent()
+
+    /**
      * The root mock view of the mock views being composed.
      */
     val root: View
+
+    /**
+     * The last validator used.
+     */
+    var validator: (MockViewValidator.() -> Unit)?
 }
 
 /**
  * Create a mock view validator and validate the view.
  */
 fun CompositionTestScope.validate(block: MockViewValidator.() -> Unit) =
-    MockViewListValidator(root.children).validate(block)
+    MockViewListValidator(root.children).validate(block).also { validator = block }
+
+/**
+ * Revalidate using the last validator
+ */
+fun CompositionTestScope.revalidate() =
+    validate(validator ?: error("validate was not called"))
 
 /**
  * Advance and expect changes
diff --git a/compose/runtime/runtime/src/test/kotlin/androidx/compose/runtime/mock/View.kt b/compose/runtime/runtime/src/test/kotlin/androidx/compose/runtime/mock/View.kt
index 4babb99..2f41caf 100644
--- a/compose/runtime/runtime/src/test/kotlin/androidx/compose/runtime/mock/View.kt
+++ b/compose/runtime/runtime/src/test/kotlin/androidx/compose/runtime/mock/View.kt
@@ -100,3 +100,5 @@
         it.toString()
     }
 }
+
+fun View.flatten(): List<View> = listOf(this) + children.flatMap { it.flatten() }
\ No newline at end of file
diff --git a/compose/runtime/runtime/src/test/kotlin/androidx/compose/runtime/mock/Views.kt b/compose/runtime/runtime/src/test/kotlin/androidx/compose/runtime/mock/Views.kt
index f4f16a0..5f90678 100644
--- a/compose/runtime/runtime/src/test/kotlin/androidx/compose/runtime/mock/Views.kt
+++ b/compose/runtime/runtime/src/test/kotlin/androidx/compose/runtime/mock/Views.kt
@@ -18,6 +18,7 @@
 
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.ComposeNode
+import androidx.compose.runtime.ReusableComposeNode
 import androidx.compose.runtime.NonRestartableComposable
 import androidx.compose.runtime.key
 
@@ -35,6 +36,16 @@
 
 @Composable
 fun Linear(content: @Composable () -> Unit) {
+    ReusableComposeNode<View, ViewApplier>(
+        factory = { View().also { it.name = "linear" } },
+        update = { }
+    ) {
+        content()
+    }
+}
+
+@Composable
+fun NonReusableLinear(content: @Composable () -> Unit) {
     ComposeNode<View, ViewApplier>(
         factory = { View().also { it.name = "linear" } },
         update = { }
@@ -45,6 +56,14 @@
 
 @Composable @NonRestartableComposable
 fun Text(value: String) {
+    ReusableComposeNode<View, ViewApplier>(
+        factory = { View().also { it.name = "text" } },
+        update = { set(value) { text = it } }
+    )
+}
+
+@Composable
+fun NonReusableText(value: String) {
     ComposeNode<View, ViewApplier>(
         factory = { View().also { it.name = "text" } },
         update = { set(value) { text = it } }
@@ -53,7 +72,7 @@
 
 @Composable
 fun Edit(value: String) {
-    ComposeNode<View, ViewApplier>(
+    ReusableComposeNode<View, ViewApplier>(
         factory = { View().also { it.name = "edit" } },
         update = { set(value) { this.value = it } }
     )
@@ -65,7 +84,7 @@
     content: @Composable () -> Unit
 ) {
     if (selected) {
-        ComposeNode<View, ViewApplier>(
+        ReusableComposeNode<View, ViewApplier>(
             factory = { View().also { it.name = "box" } },
             update = { },
             content = { content() }
diff --git a/compose/test-utils/src/commonMain/kotlin/androidx/compose/testutils/TestViewConfiguration.kt b/compose/test-utils/src/commonMain/kotlin/androidx/compose/testutils/TestViewConfiguration.kt
new file mode 100644
index 0000000..64f01f6
--- /dev/null
+++ b/compose/test-utils/src/commonMain/kotlin/androidx/compose/testutils/TestViewConfiguration.kt
@@ -0,0 +1,82 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.testutils
+
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.ui.platform.LocalViewConfiguration
+import androidx.compose.ui.platform.ViewConfiguration
+
+/**
+ * A [ViewConfiguration] that can be used for testing. The default values are representative for
+ * Android devices, but can be set to any value desired for a test. See the `With*` functions for
+ * shorthands that create a [TestViewConfiguration] and provide it as a [LocalViewConfiguration].
+ *
+ * @see WithLongPressTimeoutMillis
+ * @see WithDoubleTapTimeoutMillis
+ * @see WithDoubleTapMinTimeMillis
+ * @see WithTouchSlop
+ */
+class TestViewConfiguration(
+    override val longPressTimeoutMillis: Long = 500L,
+    override val doubleTapTimeoutMillis: Long = 300L,
+    override val doubleTapMinTimeMillis: Long = 40L,
+    override val touchSlop: Float = 18f
+) : ViewConfiguration
+
+@Composable
+fun WithLongPressTimeoutMillis(longPressTimeoutMillis: Long, content: @Composable () -> Unit) {
+    WithViewConfiguration(
+        TestViewConfiguration(longPressTimeoutMillis = longPressTimeoutMillis),
+        content = content
+    )
+}
+
+@Composable
+fun WithDoubleTapTimeoutMillis(doubleTapTimeoutMillis: Long, content: @Composable () -> Unit) {
+    WithViewConfiguration(
+        TestViewConfiguration(doubleTapTimeoutMillis = doubleTapTimeoutMillis),
+        content = content
+    )
+}
+
+@Composable
+fun WithDoubleTapMinTimeMillis(doubleTapMinTimeMillis: Long, content: @Composable () -> Unit) {
+    WithViewConfiguration(
+        TestViewConfiguration(doubleTapMinTimeMillis = doubleTapMinTimeMillis),
+        content = content
+    )
+}
+
+@Composable
+fun WithTouchSlop(touchSlop: Float, content: @Composable () -> Unit) {
+    WithViewConfiguration(
+        TestViewConfiguration(touchSlop = touchSlop),
+        content = content
+    )
+}
+
+@Composable
+fun WithViewConfiguration(
+    testViewConfiguration: TestViewConfiguration,
+    content: @Composable () -> Unit
+) {
+    CompositionLocalProvider(
+        LocalViewConfiguration provides testViewConfiguration,
+        content = content
+    )
+}
diff --git a/compose/ui/ui-test-junit4/build.gradle b/compose/ui/ui-test-junit4/build.gradle
index 1923226..9a89811 100644
--- a/compose/ui/ui-test-junit4/build.gradle
+++ b/compose/ui/ui-test-junit4/build.gradle
@@ -85,6 +85,7 @@
 
                 implementation("androidx.annotation:annotation:1.1.0")
                 implementation(KOTLIN_COROUTINES_CORE)
+                implementation(KOTLIN_COROUTINES_TEST)
             }
 
             androidMain.dependencies {
@@ -99,7 +100,6 @@
                 implementation(ANDROIDX_TEST_MONITOR)
                 implementation(ESPRESSO_CORE)
                 implementation(ESPRESSO_IDLING_RESOURCE)
-                implementation(KOTLIN_COROUTINES_TEST)
             }
 
             androidAndroidTest.dependencies {
diff --git a/compose/ui/ui-test-junit4/src/androidAndroidTest/kotlin/androidx/compose/ui/test/junit4/UncaughtExceptionsInCoroutinesTest.kt b/compose/ui/ui-test-junit4/src/androidAndroidTest/kotlin/androidx/compose/ui/test/junit4/UncaughtExceptionsInCoroutinesTest.kt
index 0c5823c..01d4151 100644
--- a/compose/ui/ui-test-junit4/src/androidAndroidTest/kotlin/androidx/compose/ui/test/junit4/UncaughtExceptionsInCoroutinesTest.kt
+++ b/compose/ui/ui-test-junit4/src/androidAndroidTest/kotlin/androidx/compose/ui/test/junit4/UncaughtExceptionsInCoroutinesTest.kt
@@ -23,10 +23,7 @@
 import androidx.test.filters.LargeTest
 import org.junit.Rule
 import org.junit.Test
-import org.junit.rules.RuleChain
-import org.junit.rules.TestRule
 import org.junit.runner.RunWith
-import org.junit.runners.model.Statement
 
 @LargeTest
 @RunWith(AndroidJUnit4::class)
@@ -34,23 +31,8 @@
 
     private class TestException : Exception()
 
-    // Need to expect the error in a test rule around the AndroidComposeTestRule, because the
-    // exception can surface after the test is completed, when the AndroidComposeTestRule cleans
-    // up test scoped variables.
-    private val testExceptionRule = TestRule { base, _ ->
-        object : Statement() {
-            override fun evaluate() {
-                expectError<TestException> {
-                    base.evaluate()
-                }
-            }
-        }
-    }
-
-    private val rule = createComposeRule()
-
     @get:Rule
-    val testRule: TestRule = RuleChain.outerRule(testExceptionRule).around(rule)
+    val rule = createComposeRule()
 
     // Run the test twice so we can verify if a failed test took down the test suite:
     // - Results have 1 failed test:
@@ -60,12 +42,16 @@
 
     @Test
     fun test1() {
-        throwInLaunchedEffect()
+        expectError<TestException> {
+            throwInLaunchedEffect()
+        }
     }
 
     @Test
     fun test2() {
-        throwInLaunchedEffect()
+        expectError<TestException> {
+            throwInLaunchedEffect()
+        }
     }
 
     private fun throwInLaunchedEffect() {
diff --git a/compose/ui/ui-test-junit4/src/androidMain/kotlin/androidx/compose/ui/test/junit4/AndroidComposeTestRule.android.kt b/compose/ui/ui-test-junit4/src/androidMain/kotlin/androidx/compose/ui/test/junit4/AndroidComposeTestRule.android.kt
index 75569f8..b5d0310 100644
--- a/compose/ui/ui-test-junit4/src/androidMain/kotlin/androidx/compose/ui/test/junit4/AndroidComposeTestRule.android.kt
+++ b/compose/ui/ui-test-junit4/src/androidMain/kotlin/androidx/compose/ui/test/junit4/AndroidComposeTestRule.android.kt
@@ -61,7 +61,6 @@
 import kotlinx.coroutines.cancel
 import kotlinx.coroutines.launch
 import kotlinx.coroutines.test.TestCoroutineDispatcher
-import kotlinx.coroutines.test.TestCoroutineExceptionHandler
 import kotlinx.coroutines.withContext
 import org.junit.rules.RuleChain
 import org.junit.rules.TestRule
@@ -185,8 +184,7 @@
     private val testCoroutineDispatcher: TestCoroutineDispatcher
     private val recomposerApplyCoroutineScope: CoroutineScope
     private val frameCoroutineScope: CoroutineScope
-    @OptIn(ExperimentalCoroutinesApi::class)
-    private val coroutineExceptionHandler: TestCoroutineExceptionHandler
+    private val coroutineExceptionHandler = UncaughtExceptionHandler()
 
     override val mainClock: MainTestClock
         get() = mainClockImpl
@@ -207,8 +205,6 @@
             }
         }
         @OptIn(ExperimentalCoroutinesApi::class)
-        coroutineExceptionHandler = TestCoroutineExceptionHandler()
-        @OptIn(ExperimentalCoroutinesApi::class)
         recomposerApplyCoroutineScope = CoroutineScope(
             testCoroutineDispatcher + frameClock + infiniteAnimationPolicy +
                 coroutineExceptionHandler + Job()
@@ -287,7 +283,7 @@
             // Then await composition(s)
             runEspressoOnIdle()
         }
-        checkUncaughtCoroutineExceptions()
+        coroutineExceptionHandler.throwUncaught()
     }
 
     override fun <T> runOnUiThread(action: () -> T): T {
@@ -326,18 +322,6 @@
         idlingResourceRegistry.unregisterIdlingResource(idlingResource)
     }
 
-    /**
-     * Checks if the [coroutineExceptionHandler] has caught uncaught exceptions. If so, will
-     * rethrow the first to fail the test. Rather than only calling this only at the end of the
-     * test, as recommended by [cleanupTestCoroutines][kotlinx.coroutines.test
-     * .UncaughtExceptionCaptor.cleanupTestCoroutines], try calling this at a few strategic
-     * points to fail the test asap after the exception was caught.
-     */
-    private fun checkUncaughtCoroutineExceptions() {
-        @OptIn(ExperimentalCoroutinesApi::class)
-        coroutineExceptionHandler.cleanupTestCoroutines()
-    }
-
     inner class AndroidComposeStatement(
         private val base: Statement
     ) : Statement() {
@@ -358,15 +342,15 @@
                 }
                 base.evaluate()
             } finally {
+                textInputServiceFactory = oldTextInputFactory
                 recomposer.cancel()
                 // FYI: Not canceling these scope below would end up cleanupTestCoroutines
                 // throwing errors on active coroutines
                 recomposerApplyCoroutineScope.cancel()
                 frameCoroutineScope.cancel()
-                checkUncaughtCoroutineExceptions()
+                coroutineExceptionHandler.throwUncaught()
                 @OptIn(ExperimentalCoroutinesApi::class)
                 testCoroutineDispatcher.cleanupTestCoroutines()
-                textInputServiceFactory = oldTextInputFactory
                 // Dispose the content
                 if (disposeContentHook != null) {
                     runOnUiThread {
@@ -456,7 +440,7 @@
             //  between now and when the new Activity has created its compose root, even though
             //  waitForComposeRoots() suggests that we are now guaranteed one.
 
-            checkUncaughtCoroutineExceptions()
+            coroutineExceptionHandler.throwUncaught()
         }
 
         override fun getRoots(atLeastOneRootExpected: Boolean): Set<RootForTest> {
diff --git a/compose/ui/ui-test-junit4/src/desktopMain/kotlin/androidx/compose/ui/test/junit4/DesktopComposeTestRule.desktop.kt b/compose/ui/ui-test-junit4/src/desktopMain/kotlin/androidx/compose/ui/test/junit4/DesktopComposeTestRule.desktop.kt
index 7038f13..e055101 100644
--- a/compose/ui/ui-test-junit4/src/desktopMain/kotlin/androidx/compose/ui/test/junit4/DesktopComposeTestRule.desktop.kt
+++ b/compose/ui/ui-test-junit4/src/desktopMain/kotlin/androidx/compose/ui/test/junit4/DesktopComposeTestRule.desktop.kt
@@ -49,10 +49,6 @@
 @OptIn(InternalTestApi::class)
 class DesktopComposeTestRule : ComposeContentTestRule {
 
-    companion object {
-        var current: DesktopComposeTestRule? = null
-    }
-
     override val density: Density
         get() = Density(1f, 1f)
 
@@ -61,13 +57,13 @@
 
     internal val testDisplaySize: IntSize get() = IntSize(1024, 768)
 
+    private var uncaughtExceptionHandler = UncaughtExceptionHandler()
     lateinit var window: TestComposeWindow
 
     private val testOwner = DesktopTestOwner(this)
     private val testContext = createTestContext(testOwner)
 
     override fun apply(base: Statement, description: Description?): Statement {
-        current = this
         return object : Statement() {
             override fun evaluate() {
                 window = runOnUiThread(::createWindow)
@@ -77,6 +73,8 @@
                 } finally {
                     runOnUiThread(window::dispose)
                 }
+
+                uncaughtExceptionHandler.throwUncaught()
             }
         }
     }
@@ -86,7 +84,7 @@
         height = testDisplaySize.height,
         density = density,
         nanoTime = System::nanoTime, // TODO(demin): use mainClock?
-        coroutineContext = Dispatchers.Swing
+        coroutineContext = Dispatchers.Swing + uncaughtExceptionHandler
     )
 
     private fun isIdle() =
@@ -96,6 +94,7 @@
     override fun waitForIdle() {
         while (!isIdle()) {
             Thread.sleep(10)
+            uncaughtExceptionHandler.throwUncaught()
         }
     }
 
@@ -103,6 +102,7 @@
     override suspend fun awaitIdle() {
         while (!isIdle()) {
             delay(10)
+            uncaughtExceptionHandler.throwUncaught()
         }
     }
 
diff --git a/compose/ui/ui-test-junit4/src/jvmMain/kotlin/androidx/compose/ui/test/junit4/UncaughtExceptionHandler.jvm.kt b/compose/ui/ui-test-junit4/src/jvmMain/kotlin/androidx/compose/ui/test/junit4/UncaughtExceptionHandler.jvm.kt
new file mode 100644
index 0000000..b25d2ba
--- /dev/null
+++ b/compose/ui/ui-test-junit4/src/jvmMain/kotlin/androidx/compose/ui/test/junit4/UncaughtExceptionHandler.jvm.kt
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.ui.test.junit4
+
+import kotlinx.coroutines.CoroutineExceptionHandler
+import kotlin.coroutines.AbstractCoroutineContextElement
+import kotlin.coroutines.CoroutineContext
+
+/**
+ * Similar to [TestCoroutineExceptionHandler], but with clearing all thrown exceptions
+ *
+ * If we don't clear exceptions they will be thrown twice in this example
+ * (the exception will be thrown inside awaitIdle and after the test):
+ * ```
+ * @Test
+ * fun test() {
+ *    runBlocking(Dispatchers.Main) {
+ *        try {
+ *            rule.awaitIdle()
+ *        } catch (e: SomeException) {
+ *            // ignore
+ *        }
+ *    }
+ * }
+ * ```
+ */
+internal class UncaughtExceptionHandler :
+    AbstractCoroutineContextElement(CoroutineExceptionHandler),
+    CoroutineExceptionHandler {
+    private var exception: Throwable? = null
+
+    override fun handleException(context: CoroutineContext, exception: Throwable) {
+        synchronized(this) {
+            if (this.exception == null) {
+                this.exception = exception
+            } else {
+                this.exception!!.addSuppressed(exception)
+            }
+        }
+    }
+
+    /**
+     * Checks if the [UncaughtExceptionHandler] has caught uncaught exceptions. If so, will
+     * rethrow the first to fail the test. The rest exceptions will be added to the first and
+     * marked as `suppressed`.
+     *
+     * The next call of this method will not throw already thrown exception.
+     *
+     * Rather than only calling this only at the end of the test, as recommended by
+     * [UncaughtExceptionCaptor.cleanupTestCoroutines],
+     * try calling this at a few strategic
+     * points to fail the test asap after the exception was caught.
+     */
+    fun throwUncaught() {
+        synchronized(this) {
+            val exception = exception
+            if (exception != null) {
+                this.exception = null
+                throw exception
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/compose/ui/ui-test/src/androidAndroidTest/kotlin/androidx/compose/ui/test/gesturescope/SendSwipeTest.kt b/compose/ui/ui-test/src/androidAndroidTest/kotlin/androidx/compose/ui/test/gesturescope/SendSwipeTest.kt
index e01eadf..2fa7c14 100644
--- a/compose/ui/ui-test/src/androidAndroidTest/kotlin/androidx/compose/ui/test/gesturescope/SendSwipeTest.kt
+++ b/compose/ui/ui-test/src/androidAndroidTest/kotlin/androidx/compose/ui/test/gesturescope/SendSwipeTest.kt
@@ -24,14 +24,12 @@
 import androidx.compose.foundation.layout.wrapContentSize
 import androidx.compose.foundation.verticalScroll
 import androidx.compose.runtime.Composable
-import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.testutils.WithTouchSlop
 import androidx.compose.testutils.expectError
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.platform.LocalDensity
-import androidx.compose.ui.platform.LocalViewConfiguration
-import androidx.compose.ui.platform.ViewConfiguration
 import androidx.compose.ui.platform.testTag
 import androidx.compose.ui.test.ExperimentalTestApi
 import androidx.compose.ui.test.bottomCenter
@@ -266,10 +264,10 @@
 
     @Test
     fun swipeScrollable() {
-        val touchSlop = TestTouchSlop
+        val touchSlop = 18f
         val scrollState = ScrollState(initial = 0)
         rule.setContent {
-            CompositionLocalProvider(LocalViewConfiguration provides FakeViewConfiguration) {
+            WithTouchSlop(touchSlop) {
                 with(LocalDensity.current) {
                     // Scrollable with a viewport the size of 10 boxes
                     Column(
@@ -345,17 +343,4 @@
         events.map { it.position.x }.assertIncreasing()
         events.map { it.position.y }.assertSame(tolerance = 0.001f)
     }
-
-    internal val TestTouchSlop = 18f
-
-    private val FakeViewConfiguration = object : ViewConfiguration {
-        override val longPressTimeoutMillis: Long
-            get() = 500L
-        override val doubleTapTimeoutMillis: Long
-            get() = 300L
-        override val doubleTapMinTimeMillis: Long
-            get() = 40L
-        override val touchSlop: Float
-            get() = TestTouchSlop
-    }
 }
diff --git a/compose/ui/ui-text/src/androidAndroidTest/kotlin/androidx/compose/ui/text/platform/AndroidParagraphTest.kt b/compose/ui/ui-text/src/androidAndroidTest/kotlin/androidx/compose/ui/text/platform/AndroidParagraphTest.kt
index fd62ae0..32d6216 100644
--- a/compose/ui/ui-text/src/androidAndroidTest/kotlin/androidx/compose/ui/text/platform/AndroidParagraphTest.kt
+++ b/compose/ui/ui-text/src/androidAndroidTest/kotlin/androidx/compose/ui/text/platform/AndroidParagraphTest.kt
@@ -9,8 +9,6 @@
 import android.text.style.LocaleSpan
 import android.text.style.RelativeSizeSpan
 import android.text.style.ScaleXSpan
-import android.text.style.StrikethroughSpan
-import android.text.style.UnderlineSpan
 import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.graphics.Canvas
 import androidx.compose.ui.graphics.Color
@@ -29,6 +27,7 @@
 import androidx.compose.ui.text.android.style.LetterSpacingSpanPx
 import androidx.compose.ui.text.android.style.ShadowSpan
 import androidx.compose.ui.text.android.style.SkewXSpan
+import androidx.compose.ui.text.android.style.TextDecorationSpan
 import androidx.compose.ui.text.font.FontFamily
 import androidx.compose.ui.text.font.FontStyle
 import androidx.compose.ui.text.font.FontSynthesis
@@ -165,7 +164,13 @@
         )
 
         assertThat(paragraph.charSequence.toString()).isEqualTo(text)
-        assertThat(paragraph.charSequence).hasSpan(StrikethroughSpan::class, 0, text.length)
+        assertThat(paragraph.charSequence).hasSpan(
+            spanClazz = TextDecorationSpan::class,
+            start = 0,
+            end = text.length
+        ) {
+            !it.isUnderlineText && it.isStrikethroughText
+        }
     }
 
     @Test
@@ -180,7 +185,13 @@
         )
 
         assertThat(paragraph.charSequence.toString()).isEqualTo(text)
-        assertThat(paragraph.charSequence).hasSpan(UnderlineSpan::class, 0, text.length)
+        assertThat(paragraph.charSequence).hasSpan(
+            spanClazz = TextDecorationSpan::class,
+            start = 0,
+            end = text.length
+        ) {
+            it.isUnderlineText && !it.isStrikethroughText
+        }
     }
 
     @Test
@@ -195,7 +206,13 @@
         )
 
         assertThat(paragraph.charSequence.toString()).isEqualTo(text)
-        assertThat(paragraph.charSequence).hasSpan(StrikethroughSpan::class, 0, "abc".length)
+        assertThat(paragraph.charSequence).hasSpan(
+            spanClazz = TextDecorationSpan::class,
+            start = 0,
+            end = "abc".length
+        ) {
+            !it.isUnderlineText && it.isStrikethroughText
+        }
     }
 
     @Test
@@ -210,7 +227,13 @@
         )
 
         assertThat(paragraph.charSequence.toString()).isEqualTo(text)
-        assertThat(paragraph.charSequence).hasSpan(UnderlineSpan::class, 0, "abc".length)
+        assertThat(paragraph.charSequence).hasSpan(
+            spanClazz = TextDecorationSpan::class,
+            start = 0,
+            end = "abc".length
+        ) {
+            it.isUnderlineText && !it.isStrikethroughText
+        }
     }
 
     @Test
@@ -227,8 +250,13 @@
         )
 
         assertThat(paragraph.charSequence.toString()).isEqualTo(text)
-        assertThat(paragraph.charSequence).hasSpan(UnderlineSpan::class, 0, "abc".length)
-        assertThat(paragraph.charSequence).hasSpan(StrikethroughSpan::class, 0, "abc".length)
+        assertThat(paragraph.charSequence).hasSpan(
+            spanClazz = TextDecorationSpan::class,
+            start = 0,
+            end = "abc".length
+        ) {
+            it.isUnderlineText && it.isStrikethroughText
+        }
     }
 
     @Test
@@ -551,7 +579,7 @@
     }
 
     @Test
-    fun testAnnotatedString_setTextGeometricTransformWithNull_noSpanSet() {
+    fun testAnnotatedString_setDefaultTextGeometricTransform() {
         val text = "abcde"
         val spanStyle = SpanStyle(textGeometricTransform = TextGeometricTransform())
 
@@ -561,8 +589,16 @@
             width = 100.0f // width is not important
         )
 
-        assertThat(paragraph.charSequence).spans(ScaleXSpan::class).isEmpty()
-        assertThat(paragraph.charSequence).spans(SkewXSpan::class).isEmpty()
+        assertThat(paragraph.charSequence).hasSpan(ScaleXSpan::class, 0, text.length) {
+            it.scaleX == 1.0f
+        }
+        assertThat(paragraph.charSequence).hasSpan(
+            spanClazz = SkewXSpan::class,
+            start = 0,
+            end = text.length
+        ) {
+            it.skewX == 0.0f
+        }
     }
 
     @Test
@@ -584,7 +620,9 @@
         assertThat(paragraph.charSequence).hasSpan(ScaleXSpan::class, 0, text.length) {
             it.scaleX == scaleX
         }
-        assertThat(paragraph.charSequence).spans(SkewXSpan::class).isEmpty()
+        assertThat(paragraph.charSequence).hasSpan(SkewXSpan::class, 0, text.length) {
+            it.skewX == 0.0f
+        }
     }
 
     @Test
@@ -602,7 +640,9 @@
         assertThat(paragraph.charSequence).hasSpan(SkewXSpan::class, 0, text.length) {
             it.skewX == skewX
         }
-        assertThat(paragraph.charSequence).spans(ScaleXSpan::class).isEmpty()
+        assertThat(paragraph.charSequence).hasSpan(ScaleXSpan::class, 0, text.length) {
+            it.scaleX == 1.0f
+        }
     }
 
     @Test
diff --git a/compose/ui/ui-text/src/androidMain/kotlin/androidx/compose/ui/text/platform/AndroidAccessibilitySpannableString.android.kt b/compose/ui/ui-text/src/androidMain/kotlin/androidx/compose/ui/text/platform/AndroidAccessibilitySpannableString.android.kt
index fb3c45d5..8bc21b2 100644
--- a/compose/ui/ui-text/src/androidMain/kotlin/androidx/compose/ui/text/platform/AndroidAccessibilitySpannableString.android.kt
+++ b/compose/ui/ui-text/src/androidMain/kotlin/androidx/compose/ui/text/platform/AndroidAccessibilitySpannableString.android.kt
@@ -21,8 +21,10 @@
 import android.text.SpannableString
 import android.text.Spanned
 import android.text.style.ScaleXSpan
+import android.text.style.StrikethroughSpan
 import android.text.style.StyleSpan
 import android.text.style.TypefaceSpan
+import android.text.style.UnderlineSpan
 import androidx.annotation.DoNotInline
 import androidx.annotation.RequiresApi
 import androidx.annotation.RestrictTo
@@ -39,8 +41,8 @@
 import androidx.compose.ui.text.platform.extensions.setColor
 import androidx.compose.ui.text.platform.extensions.setFontSize
 import androidx.compose.ui.text.platform.extensions.setLocaleList
-import androidx.compose.ui.text.platform.extensions.setTextDecoration
 import androidx.compose.ui.text.platform.extensions.toSpan
+import androidx.compose.ui.text.style.TextDecoration
 import androidx.compose.ui.unit.Density
 import androidx.compose.ui.util.fastForEach
 
@@ -126,12 +128,29 @@
         }
     }
 
-    setTextDecoration(spanStyle.textDecoration, start, end)
+    if (spanStyle.textDecoration != null) {
+        // This doesn't match how we rendering the styles. When TextDecoration.None is set, it
+        // should remove the underline and lineThrough effect on the given range. Here we didn't
+        // remove any previously applied spans yet.
+        if (TextDecoration.Underline in spanStyle.textDecoration) {
+            setSpan(
+                UnderlineSpan(),
+                start,
+                end,
+                Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
+            )
+        }
+        if (TextDecoration.LineThrough in spanStyle.textDecoration) {
+            setSpan(
+                StrikethroughSpan(),
+                start,
+                end,
+                Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
+            )
+        }
+    }
 
-    if (
-        spanStyle.textGeometricTransform != null &&
-        spanStyle.textGeometricTransform.scaleX != 1f
-    ) {
+    if (spanStyle.textGeometricTransform != null) {
         setSpan(
             ScaleXSpan(spanStyle.textGeometricTransform.scaleX),
             start,
diff --git a/compose/ui/ui-text/src/androidMain/kotlin/androidx/compose/ui/text/platform/TypefaceAdapter.android.kt b/compose/ui/ui-text/src/androidMain/kotlin/androidx/compose/ui/text/platform/TypefaceAdapter.android.kt
index 188825d..85d8bcf 100644
--- a/compose/ui/ui-text/src/androidMain/kotlin/androidx/compose/ui/text/platform/TypefaceAdapter.android.kt
+++ b/compose/ui/ui-text/src/androidMain/kotlin/androidx/compose/ui/text/platform/TypefaceAdapter.android.kt
@@ -248,7 +248,7 @@
                 else -> throw IllegalStateException("Unknown font type: $font")
             }
         } catch (e: Exception) {
-            throw IllegalStateException("Cannot create Typeface from $font")
+            throw IllegalStateException("Cannot create Typeface from $font", e)
         }
 
         val loadedFontIsSameAsRequest = fontWeight == font.weight && fontStyle == font.style
diff --git a/compose/ui/ui-text/src/androidMain/kotlin/androidx/compose/ui/text/platform/extensions/SpannableExtensions.android.kt b/compose/ui/ui-text/src/androidMain/kotlin/androidx/compose/ui/text/platform/extensions/SpannableExtensions.android.kt
index 17f672c..f109647 100644
--- a/compose/ui/ui-text/src/androidMain/kotlin/androidx/compose/ui/text/platform/extensions/SpannableExtensions.android.kt
+++ b/compose/ui/ui-text/src/androidMain/kotlin/androidx/compose/ui/text/platform/extensions/SpannableExtensions.android.kt
@@ -27,8 +27,6 @@
 import android.text.style.MetricAffectingSpan
 import android.text.style.RelativeSizeSpan
 import android.text.style.ScaleXSpan
-import android.text.style.StrikethroughSpan
-import android.text.style.UnderlineSpan
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.graphics.Shadow
 import androidx.compose.ui.graphics.isSpecified
@@ -44,6 +42,7 @@
 import androidx.compose.ui.text.android.style.LineHeightSpan
 import androidx.compose.ui.text.android.style.ShadowSpan
 import androidx.compose.ui.text.android.style.SkewXSpan
+import androidx.compose.ui.text.android.style.TextDecorationSpan
 import androidx.compose.ui.text.android.style.TypefaceSpan
 import androidx.compose.ui.text.fastFilter
 import androidx.compose.ui.text.font.FontStyle
@@ -393,12 +392,8 @@
     end: Int
 ) {
     textGeometricTransform?.let {
-        if (it.scaleX != 1.0f) {
-            setSpan(ScaleXSpan(it.scaleX), start, end)
-        }
-        if (it.skewX != 0f) {
-            setSpan(SkewXSpan(it.skewX), start, end)
-        }
+        setSpan(ScaleXSpan(it.scaleX), start, end)
+        setSpan(SkewXSpan(it.skewX), start, end)
     }
 }
 
@@ -427,14 +422,14 @@
     }
 }
 
+@OptIn(InternalPlatformTextApi::class)
 internal fun Spannable.setTextDecoration(textDecoration: TextDecoration?, start: Int, end: Int) {
     textDecoration?.let {
-        if (TextDecoration.Underline in it) {
-            setSpan(UnderlineSpan(), start, end)
-        }
-        if (TextDecoration.LineThrough in it) {
-            setSpan(StrikethroughSpan(), start, end)
-        }
+        val textDecorationSpan = TextDecorationSpan(
+            isUnderlineText = TextDecoration.Underline in it,
+            isStrikethroughText = TextDecoration.LineThrough in it
+        )
+        setSpan(textDecorationSpan, start, end)
     }
 }
 
diff --git a/compose/ui/ui-text/src/desktopTest/kotlin/androidx/compose/ui/text/DesktopFontTest.kt b/compose/ui/ui-text/src/desktopTest/kotlin/androidx/compose/ui/text/DesktopFontTest.kt
index 5d89cf1..440782a 100644
--- a/compose/ui/ui-text/src/desktopTest/kotlin/androidx/compose/ui/text/DesktopFontTest.kt
+++ b/compose/ui/ui-text/src/desktopTest/kotlin/androidx/compose/ui/text/DesktopFontTest.kt
@@ -19,13 +19,14 @@
 import androidx.compose.ui.test.junit4.createComposeRule
 import androidx.compose.ui.text.font.FontFamily
 import androidx.compose.ui.text.font.FontStyle
+import androidx.compose.ui.text.platform.Font
 import androidx.compose.ui.text.platform.FontLoader
 import androidx.compose.ui.text.platform.GenericFontFamiliesMapping
-import androidx.compose.ui.text.platform.Font
 import androidx.compose.ui.text.platform.Typeface
 import com.google.common.truth.Truth
 import org.jetbrains.skija.Data
 import org.jetbrains.skija.Typeface
+import org.junit.Assume.assumeTrue
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -80,8 +81,11 @@
             .isEqualTo(listOf("Sample Font"))
     }
 
+    // TODO(demin): can we fix findTypeface for Windows and Linux?
     @Test
     fun findTypeface() {
+        assumeTrue(isMacOs)
+
         Truth.assertThat(fontLoader.findTypeface(fontListFontFamily)!!.isItalic)
             .isEqualTo(false)
 
diff --git a/compose/ui/ui-text/src/desktopTest/kotlin/androidx/compose/ui/text/DesktopParagraphTest.kt b/compose/ui/ui-text/src/desktopTest/kotlin/androidx/compose/ui/text/DesktopParagraphTest.kt
index 13af274..10b9265 100644
--- a/compose/ui/ui-text/src/desktopTest/kotlin/androidx/compose/ui/text/DesktopParagraphTest.kt
+++ b/compose/ui/ui-text/src/desktopTest/kotlin/androidx/compose/ui/text/DesktopParagraphTest.kt
@@ -18,11 +18,11 @@
 
 import androidx.compose.ui.geometry.Rect
 import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.text.font.FontFamily
 import androidx.compose.ui.text.font.FontStyle
 import androidx.compose.ui.text.font.FontWeight
-import androidx.compose.ui.text.font.FontFamily
-import androidx.compose.ui.text.platform.FontLoader
 import androidx.compose.ui.text.platform.Font
+import androidx.compose.ui.text.platform.FontLoader
 import androidx.compose.ui.text.style.TextDirection
 import androidx.compose.ui.unit.Density
 import androidx.compose.ui.unit.sp
@@ -72,7 +72,7 @@
 
     @Test
     fun getBoundingBox_multicodepoints() {
-        assumeLinux()
+        assumeTrue(isLinux)
         with(defaultDensity) {
             val text = "h\uD83E\uDDD1\uD83C\uDFFF\u200D\uD83E\uDDB0"
             val fontSize = 50.sp
@@ -240,8 +240,4 @@
             resourceLoader = fontLoader
         )
     }
-
-    private fun assumeLinux() {
-        assumeTrue(System.getProperty("os.name").startsWith("Linux"))
-    }
 }
\ No newline at end of file
diff --git a/compose/ui/ui-text/src/desktopTest/kotlin/androidx/compose/ui/text/TestUtils.kt b/compose/ui/ui-text/src/desktopTest/kotlin/androidx/compose/ui/text/TestUtils.kt
new file mode 100644
index 0000000..3f0df87
--- /dev/null
+++ b/compose/ui/ui-text/src/desktopTest/kotlin/androidx/compose/ui/text/TestUtils.kt
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.ui.text
+
+import java.util.Locale
+
+private val os = System.getProperty("os.name").toLowerCase(Locale.US)
+internal val isLinux = os.startsWith("linux")
+internal val isWindows = os.startsWith("win")
+internal val isMacOs = os.startsWith("mac")
\ No newline at end of file
diff --git a/compose/ui/ui-tooling/api/1.0.0-beta07.txt b/compose/ui/ui-tooling/api/1.0.0-beta07.txt
index e392e95..1789ce2 100644
--- a/compose/ui/ui-tooling/api/1.0.0-beta07.txt
+++ b/compose/ui/ui-tooling/api/1.0.0-beta07.txt
@@ -7,95 +7,6 @@
 
 }
 
-package androidx.compose.ui.tooling.inspector {
-
-  public final class InspectorNode {
-    method public int[] getBounds();
-    method public java.util.List<androidx.compose.ui.tooling.inspector.InspectorNode> getChildren();
-    method public String getFileName();
-    method public int getHeight();
-    method public long getId();
-    method public int getLeft();
-    method public int getLength();
-    method public int getLineNumber();
-    method public String getName();
-    method public int getOffset();
-    method public int getPackageHash();
-    method public java.util.List<androidx.compose.ui.tooling.inspector.RawParameter> getParameters();
-    method public int getTop();
-    method public int getWidth();
-    property public final int[] bounds;
-    property public final java.util.List<androidx.compose.ui.tooling.inspector.InspectorNode> children;
-    property public final String fileName;
-    property public final int height;
-    property public final long id;
-    property public final int left;
-    property public final int length;
-    property public final int lineNumber;
-    property public final String name;
-    property public final int offset;
-    property public final int packageHash;
-    property public final java.util.List<androidx.compose.ui.tooling.inspector.RawParameter> parameters;
-    property public final int top;
-    property public final int width;
-  }
-
-  public final class InspectorNodeKt {
-  }
-
-  public final class LayoutInspectorTree {
-    ctor public LayoutInspectorTree();
-    method public java.util.List<androidx.compose.ui.tooling.inspector.InspectorNode> convert(android.view.View view);
-    method public java.util.List<androidx.compose.ui.tooling.inspector.NodeParameter> convertParameters(androidx.compose.ui.tooling.inspector.InspectorNode node);
-    method public boolean getHideSystemNodes();
-    method public void resetGeneratedId();
-    method public void setHideSystemNodes(boolean p);
-    property public final boolean hideSystemNodes;
-  }
-
-  public final class LayoutInspectorTreeKt {
-  }
-
-  public final class NodeParameter {
-    method public java.util.List<androidx.compose.ui.tooling.inspector.NodeParameter> getElements();
-    method public String getName();
-    method public androidx.compose.ui.tooling.inspector.ParameterType getType();
-    method public Object? getValue();
-    property public final java.util.List<androidx.compose.ui.tooling.inspector.NodeParameter> elements;
-    property public final String name;
-    property public final androidx.compose.ui.tooling.inspector.ParameterType type;
-    property public final Object? value;
-  }
-
-  public final class ParameterFactoryKt {
-  }
-
-  public enum ParameterType {
-    enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType Boolean;
-    enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType Color;
-    enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType DimensionDp;
-    enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType DimensionEm;
-    enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType DimensionSp;
-    enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType Double;
-    enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType Float;
-    enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType FunctionReference;
-    enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType Int32;
-    enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType Int64;
-    enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType Lambda;
-    enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType Resource;
-    enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType String;
-  }
-
-  public final class RawParameter {
-    ctor public RawParameter(String name, Object? value);
-    method public String getName();
-    method public Object? getValue();
-    property public final String name;
-    property public final Object? value;
-  }
-
-}
-
 package androidx.compose.ui.tooling.preview {
 
   public final class ComposeViewAdapterKt {
diff --git a/compose/ui/ui-tooling/api/current.ignore b/compose/ui/ui-tooling/api/current.ignore
new file mode 100644
index 0000000..050747e
--- /dev/null
+++ b/compose/ui/ui-tooling/api/current.ignore
@@ -0,0 +1,3 @@
+// Baseline format: 1.0
+RemovedPackage: androidx.compose.ui.tooling.inspector:
+    Removed package androidx.compose.ui.tooling.inspector
diff --git a/compose/ui/ui-tooling/api/current.txt b/compose/ui/ui-tooling/api/current.txt
index e392e95..1789ce2 100644
--- a/compose/ui/ui-tooling/api/current.txt
+++ b/compose/ui/ui-tooling/api/current.txt
@@ -7,95 +7,6 @@
 
 }
 
-package androidx.compose.ui.tooling.inspector {
-
-  public final class InspectorNode {
-    method public int[] getBounds();
-    method public java.util.List<androidx.compose.ui.tooling.inspector.InspectorNode> getChildren();
-    method public String getFileName();
-    method public int getHeight();
-    method public long getId();
-    method public int getLeft();
-    method public int getLength();
-    method public int getLineNumber();
-    method public String getName();
-    method public int getOffset();
-    method public int getPackageHash();
-    method public java.util.List<androidx.compose.ui.tooling.inspector.RawParameter> getParameters();
-    method public int getTop();
-    method public int getWidth();
-    property public final int[] bounds;
-    property public final java.util.List<androidx.compose.ui.tooling.inspector.InspectorNode> children;
-    property public final String fileName;
-    property public final int height;
-    property public final long id;
-    property public final int left;
-    property public final int length;
-    property public final int lineNumber;
-    property public final String name;
-    property public final int offset;
-    property public final int packageHash;
-    property public final java.util.List<androidx.compose.ui.tooling.inspector.RawParameter> parameters;
-    property public final int top;
-    property public final int width;
-  }
-
-  public final class InspectorNodeKt {
-  }
-
-  public final class LayoutInspectorTree {
-    ctor public LayoutInspectorTree();
-    method public java.util.List<androidx.compose.ui.tooling.inspector.InspectorNode> convert(android.view.View view);
-    method public java.util.List<androidx.compose.ui.tooling.inspector.NodeParameter> convertParameters(androidx.compose.ui.tooling.inspector.InspectorNode node);
-    method public boolean getHideSystemNodes();
-    method public void resetGeneratedId();
-    method public void setHideSystemNodes(boolean p);
-    property public final boolean hideSystemNodes;
-  }
-
-  public final class LayoutInspectorTreeKt {
-  }
-
-  public final class NodeParameter {
-    method public java.util.List<androidx.compose.ui.tooling.inspector.NodeParameter> getElements();
-    method public String getName();
-    method public androidx.compose.ui.tooling.inspector.ParameterType getType();
-    method public Object? getValue();
-    property public final java.util.List<androidx.compose.ui.tooling.inspector.NodeParameter> elements;
-    property public final String name;
-    property public final androidx.compose.ui.tooling.inspector.ParameterType type;
-    property public final Object? value;
-  }
-
-  public final class ParameterFactoryKt {
-  }
-
-  public enum ParameterType {
-    enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType Boolean;
-    enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType Color;
-    enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType DimensionDp;
-    enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType DimensionEm;
-    enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType DimensionSp;
-    enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType Double;
-    enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType Float;
-    enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType FunctionReference;
-    enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType Int32;
-    enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType Int64;
-    enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType Lambda;
-    enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType Resource;
-    enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType String;
-  }
-
-  public final class RawParameter {
-    ctor public RawParameter(String name, Object? value);
-    method public String getName();
-    method public Object? getValue();
-    property public final String name;
-    property public final Object? value;
-  }
-
-}
-
 package androidx.compose.ui.tooling.preview {
 
   public final class ComposeViewAdapterKt {
diff --git a/compose/ui/ui-tooling/api/public_plus_experimental_1.0.0-beta07.txt b/compose/ui/ui-tooling/api/public_plus_experimental_1.0.0-beta07.txt
index e392e95..1789ce2 100644
--- a/compose/ui/ui-tooling/api/public_plus_experimental_1.0.0-beta07.txt
+++ b/compose/ui/ui-tooling/api/public_plus_experimental_1.0.0-beta07.txt
@@ -7,95 +7,6 @@
 
 }
 
-package androidx.compose.ui.tooling.inspector {
-
-  public final class InspectorNode {
-    method public int[] getBounds();
-    method public java.util.List<androidx.compose.ui.tooling.inspector.InspectorNode> getChildren();
-    method public String getFileName();
-    method public int getHeight();
-    method public long getId();
-    method public int getLeft();
-    method public int getLength();
-    method public int getLineNumber();
-    method public String getName();
-    method public int getOffset();
-    method public int getPackageHash();
-    method public java.util.List<androidx.compose.ui.tooling.inspector.RawParameter> getParameters();
-    method public int getTop();
-    method public int getWidth();
-    property public final int[] bounds;
-    property public final java.util.List<androidx.compose.ui.tooling.inspector.InspectorNode> children;
-    property public final String fileName;
-    property public final int height;
-    property public final long id;
-    property public final int left;
-    property public final int length;
-    property public final int lineNumber;
-    property public final String name;
-    property public final int offset;
-    property public final int packageHash;
-    property public final java.util.List<androidx.compose.ui.tooling.inspector.RawParameter> parameters;
-    property public final int top;
-    property public final int width;
-  }
-
-  public final class InspectorNodeKt {
-  }
-
-  public final class LayoutInspectorTree {
-    ctor public LayoutInspectorTree();
-    method public java.util.List<androidx.compose.ui.tooling.inspector.InspectorNode> convert(android.view.View view);
-    method public java.util.List<androidx.compose.ui.tooling.inspector.NodeParameter> convertParameters(androidx.compose.ui.tooling.inspector.InspectorNode node);
-    method public boolean getHideSystemNodes();
-    method public void resetGeneratedId();
-    method public void setHideSystemNodes(boolean p);
-    property public final boolean hideSystemNodes;
-  }
-
-  public final class LayoutInspectorTreeKt {
-  }
-
-  public final class NodeParameter {
-    method public java.util.List<androidx.compose.ui.tooling.inspector.NodeParameter> getElements();
-    method public String getName();
-    method public androidx.compose.ui.tooling.inspector.ParameterType getType();
-    method public Object? getValue();
-    property public final java.util.List<androidx.compose.ui.tooling.inspector.NodeParameter> elements;
-    property public final String name;
-    property public final androidx.compose.ui.tooling.inspector.ParameterType type;
-    property public final Object? value;
-  }
-
-  public final class ParameterFactoryKt {
-  }
-
-  public enum ParameterType {
-    enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType Boolean;
-    enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType Color;
-    enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType DimensionDp;
-    enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType DimensionEm;
-    enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType DimensionSp;
-    enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType Double;
-    enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType Float;
-    enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType FunctionReference;
-    enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType Int32;
-    enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType Int64;
-    enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType Lambda;
-    enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType Resource;
-    enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType String;
-  }
-
-  public final class RawParameter {
-    ctor public RawParameter(String name, Object? value);
-    method public String getName();
-    method public Object? getValue();
-    property public final String name;
-    property public final Object? value;
-  }
-
-}
-
 package androidx.compose.ui.tooling.preview {
 
   public final class ComposeViewAdapterKt {
diff --git a/compose/ui/ui-tooling/api/public_plus_experimental_current.txt b/compose/ui/ui-tooling/api/public_plus_experimental_current.txt
index e392e95..1789ce2 100644
--- a/compose/ui/ui-tooling/api/public_plus_experimental_current.txt
+++ b/compose/ui/ui-tooling/api/public_plus_experimental_current.txt
@@ -7,95 +7,6 @@
 
 }
 
-package androidx.compose.ui.tooling.inspector {
-
-  public final class InspectorNode {
-    method public int[] getBounds();
-    method public java.util.List<androidx.compose.ui.tooling.inspector.InspectorNode> getChildren();
-    method public String getFileName();
-    method public int getHeight();
-    method public long getId();
-    method public int getLeft();
-    method public int getLength();
-    method public int getLineNumber();
-    method public String getName();
-    method public int getOffset();
-    method public int getPackageHash();
-    method public java.util.List<androidx.compose.ui.tooling.inspector.RawParameter> getParameters();
-    method public int getTop();
-    method public int getWidth();
-    property public final int[] bounds;
-    property public final java.util.List<androidx.compose.ui.tooling.inspector.InspectorNode> children;
-    property public final String fileName;
-    property public final int height;
-    property public final long id;
-    property public final int left;
-    property public final int length;
-    property public final int lineNumber;
-    property public final String name;
-    property public final int offset;
-    property public final int packageHash;
-    property public final java.util.List<androidx.compose.ui.tooling.inspector.RawParameter> parameters;
-    property public final int top;
-    property public final int width;
-  }
-
-  public final class InspectorNodeKt {
-  }
-
-  public final class LayoutInspectorTree {
-    ctor public LayoutInspectorTree();
-    method public java.util.List<androidx.compose.ui.tooling.inspector.InspectorNode> convert(android.view.View view);
-    method public java.util.List<androidx.compose.ui.tooling.inspector.NodeParameter> convertParameters(androidx.compose.ui.tooling.inspector.InspectorNode node);
-    method public boolean getHideSystemNodes();
-    method public void resetGeneratedId();
-    method public void setHideSystemNodes(boolean p);
-    property public final boolean hideSystemNodes;
-  }
-
-  public final class LayoutInspectorTreeKt {
-  }
-
-  public final class NodeParameter {
-    method public java.util.List<androidx.compose.ui.tooling.inspector.NodeParameter> getElements();
-    method public String getName();
-    method public androidx.compose.ui.tooling.inspector.ParameterType getType();
-    method public Object? getValue();
-    property public final java.util.List<androidx.compose.ui.tooling.inspector.NodeParameter> elements;
-    property public final String name;
-    property public final androidx.compose.ui.tooling.inspector.ParameterType type;
-    property public final Object? value;
-  }
-
-  public final class ParameterFactoryKt {
-  }
-
-  public enum ParameterType {
-    enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType Boolean;
-    enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType Color;
-    enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType DimensionDp;
-    enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType DimensionEm;
-    enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType DimensionSp;
-    enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType Double;
-    enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType Float;
-    enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType FunctionReference;
-    enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType Int32;
-    enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType Int64;
-    enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType Lambda;
-    enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType Resource;
-    enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType String;
-  }
-
-  public final class RawParameter {
-    ctor public RawParameter(String name, Object? value);
-    method public String getName();
-    method public Object? getValue();
-    property public final String name;
-    property public final Object? value;
-  }
-
-}
-
 package androidx.compose.ui.tooling.preview {
 
   public final class ComposeViewAdapterKt {
diff --git a/compose/ui/ui-tooling/api/restricted_1.0.0-beta07.txt b/compose/ui/ui-tooling/api/restricted_1.0.0-beta07.txt
index e392e95..1789ce2 100644
--- a/compose/ui/ui-tooling/api/restricted_1.0.0-beta07.txt
+++ b/compose/ui/ui-tooling/api/restricted_1.0.0-beta07.txt
@@ -7,95 +7,6 @@
 
 }
 
-package androidx.compose.ui.tooling.inspector {
-
-  public final class InspectorNode {
-    method public int[] getBounds();
-    method public java.util.List<androidx.compose.ui.tooling.inspector.InspectorNode> getChildren();
-    method public String getFileName();
-    method public int getHeight();
-    method public long getId();
-    method public int getLeft();
-    method public int getLength();
-    method public int getLineNumber();
-    method public String getName();
-    method public int getOffset();
-    method public int getPackageHash();
-    method public java.util.List<androidx.compose.ui.tooling.inspector.RawParameter> getParameters();
-    method public int getTop();
-    method public int getWidth();
-    property public final int[] bounds;
-    property public final java.util.List<androidx.compose.ui.tooling.inspector.InspectorNode> children;
-    property public final String fileName;
-    property public final int height;
-    property public final long id;
-    property public final int left;
-    property public final int length;
-    property public final int lineNumber;
-    property public final String name;
-    property public final int offset;
-    property public final int packageHash;
-    property public final java.util.List<androidx.compose.ui.tooling.inspector.RawParameter> parameters;
-    property public final int top;
-    property public final int width;
-  }
-
-  public final class InspectorNodeKt {
-  }
-
-  public final class LayoutInspectorTree {
-    ctor public LayoutInspectorTree();
-    method public java.util.List<androidx.compose.ui.tooling.inspector.InspectorNode> convert(android.view.View view);
-    method public java.util.List<androidx.compose.ui.tooling.inspector.NodeParameter> convertParameters(androidx.compose.ui.tooling.inspector.InspectorNode node);
-    method public boolean getHideSystemNodes();
-    method public void resetGeneratedId();
-    method public void setHideSystemNodes(boolean p);
-    property public final boolean hideSystemNodes;
-  }
-
-  public final class LayoutInspectorTreeKt {
-  }
-
-  public final class NodeParameter {
-    method public java.util.List<androidx.compose.ui.tooling.inspector.NodeParameter> getElements();
-    method public String getName();
-    method public androidx.compose.ui.tooling.inspector.ParameterType getType();
-    method public Object? getValue();
-    property public final java.util.List<androidx.compose.ui.tooling.inspector.NodeParameter> elements;
-    property public final String name;
-    property public final androidx.compose.ui.tooling.inspector.ParameterType type;
-    property public final Object? value;
-  }
-
-  public final class ParameterFactoryKt {
-  }
-
-  public enum ParameterType {
-    enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType Boolean;
-    enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType Color;
-    enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType DimensionDp;
-    enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType DimensionEm;
-    enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType DimensionSp;
-    enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType Double;
-    enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType Float;
-    enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType FunctionReference;
-    enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType Int32;
-    enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType Int64;
-    enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType Lambda;
-    enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType Resource;
-    enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType String;
-  }
-
-  public final class RawParameter {
-    ctor public RawParameter(String name, Object? value);
-    method public String getName();
-    method public Object? getValue();
-    property public final String name;
-    property public final Object? value;
-  }
-
-}
-
 package androidx.compose.ui.tooling.preview {
 
   public final class ComposeViewAdapterKt {
diff --git a/compose/ui/ui-tooling/api/restricted_current.ignore b/compose/ui/ui-tooling/api/restricted_current.ignore
new file mode 100644
index 0000000..050747e
--- /dev/null
+++ b/compose/ui/ui-tooling/api/restricted_current.ignore
@@ -0,0 +1,3 @@
+// Baseline format: 1.0
+RemovedPackage: androidx.compose.ui.tooling.inspector:
+    Removed package androidx.compose.ui.tooling.inspector
diff --git a/compose/ui/ui-tooling/api/restricted_current.txt b/compose/ui/ui-tooling/api/restricted_current.txt
index e392e95..1789ce2 100644
--- a/compose/ui/ui-tooling/api/restricted_current.txt
+++ b/compose/ui/ui-tooling/api/restricted_current.txt
@@ -7,95 +7,6 @@
 
 }
 
-package androidx.compose.ui.tooling.inspector {
-
-  public final class InspectorNode {
-    method public int[] getBounds();
-    method public java.util.List<androidx.compose.ui.tooling.inspector.InspectorNode> getChildren();
-    method public String getFileName();
-    method public int getHeight();
-    method public long getId();
-    method public int getLeft();
-    method public int getLength();
-    method public int getLineNumber();
-    method public String getName();
-    method public int getOffset();
-    method public int getPackageHash();
-    method public java.util.List<androidx.compose.ui.tooling.inspector.RawParameter> getParameters();
-    method public int getTop();
-    method public int getWidth();
-    property public final int[] bounds;
-    property public final java.util.List<androidx.compose.ui.tooling.inspector.InspectorNode> children;
-    property public final String fileName;
-    property public final int height;
-    property public final long id;
-    property public final int left;
-    property public final int length;
-    property public final int lineNumber;
-    property public final String name;
-    property public final int offset;
-    property public final int packageHash;
-    property public final java.util.List<androidx.compose.ui.tooling.inspector.RawParameter> parameters;
-    property public final int top;
-    property public final int width;
-  }
-
-  public final class InspectorNodeKt {
-  }
-
-  public final class LayoutInspectorTree {
-    ctor public LayoutInspectorTree();
-    method public java.util.List<androidx.compose.ui.tooling.inspector.InspectorNode> convert(android.view.View view);
-    method public java.util.List<androidx.compose.ui.tooling.inspector.NodeParameter> convertParameters(androidx.compose.ui.tooling.inspector.InspectorNode node);
-    method public boolean getHideSystemNodes();
-    method public void resetGeneratedId();
-    method public void setHideSystemNodes(boolean p);
-    property public final boolean hideSystemNodes;
-  }
-
-  public final class LayoutInspectorTreeKt {
-  }
-
-  public final class NodeParameter {
-    method public java.util.List<androidx.compose.ui.tooling.inspector.NodeParameter> getElements();
-    method public String getName();
-    method public androidx.compose.ui.tooling.inspector.ParameterType getType();
-    method public Object? getValue();
-    property public final java.util.List<androidx.compose.ui.tooling.inspector.NodeParameter> elements;
-    property public final String name;
-    property public final androidx.compose.ui.tooling.inspector.ParameterType type;
-    property public final Object? value;
-  }
-
-  public final class ParameterFactoryKt {
-  }
-
-  public enum ParameterType {
-    enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType Boolean;
-    enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType Color;
-    enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType DimensionDp;
-    enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType DimensionEm;
-    enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType DimensionSp;
-    enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType Double;
-    enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType Float;
-    enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType FunctionReference;
-    enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType Int32;
-    enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType Int64;
-    enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType Lambda;
-    enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType Resource;
-    enum_constant public static final androidx.compose.ui.tooling.inspector.ParameterType String;
-  }
-
-  public final class RawParameter {
-    ctor public RawParameter(String name, Object? value);
-    method public String getName();
-    method public Object? getValue();
-    property public final String name;
-    property public final Object? value;
-  }
-
-}
-
 package androidx.compose.ui.tooling.preview {
 
   public final class ComposeViewAdapterKt {
diff --git a/compose/ui/ui-tooling/src/androidTest/java/androidx/compose/ui/tooling/inspector/InlineClassConverterTest.kt b/compose/ui/ui-tooling/src/androidTest/java/androidx/compose/ui/tooling/inspector/InlineClassConverterTest.kt
deleted file mode 100644
index 57ae98f..0000000
--- a/compose/ui/ui-tooling/src/androidTest/java/androidx/compose/ui/tooling/inspector/InlineClassConverterTest.kt
+++ /dev/null
@@ -1,79 +0,0 @@
-/*
- * Copyright 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.compose.ui.tooling.inspector
-
-import androidx.compose.material.Surface
-import androidx.compose.material.Text
-import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.tooling.CompositionDataRecord
-import androidx.compose.ui.tooling.Inspectable
-import androidx.compose.ui.tooling.ToolingTest
-import androidx.compose.ui.tooling.data.Group
-import androidx.compose.ui.tooling.data.UiToolingDataApi
-import androidx.compose.ui.tooling.data.asTree
-import androidx.compose.ui.unit.Dp
-import androidx.compose.ui.unit.TextUnit
-import androidx.compose.ui.unit.sp
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.MediumTest
-import com.google.common.truth.Truth.assertThat
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@MediumTest
-@RunWith(AndroidJUnit4::class)
-@OptIn(UiToolingDataApi::class)
-class InlineClassConverterTest : ToolingTest() {
-
-    @Test
-    fun parameterValueTest() {
-        val slotTableRecord = CompositionDataRecord.create()
-        show {
-            Inspectable(slotTableRecord) {
-                Surface {
-                    Text(text = "OK", fontSize = 12.sp)
-                }
-            }
-        }
-
-        val tree = slotTableRecord.store.first().asTree()
-        val groups = flatten(tree)
-        val surface = find(groups, "Surface")
-        val text = find(groups, "Text")
-
-        val mapper = InlineClassConverter()
-
-        fun validate(caller: Group, parameterName: String, valueType: Class<*>) {
-            val parameter = caller.parameters.single { it.name == parameterName }
-            val value = mapper.castParameterValue(parameter.inlineClass, parameter.value)
-            assertThat(value).isInstanceOf(valueType)
-        }
-
-        validate(surface, "color", Color::class.java)
-        validate(surface, "elevation", Dp::class.java)
-        validate(text, "color", Color::class.java)
-        validate(text, "fontSize", TextUnit::class.java)
-    }
-
-    private fun flatten(group: Group): Sequence<Group> =
-        sequenceOf(group).plus(group.children.asSequence().flatMap { flatten(it) })
-
-    private fun find(groups: Sequence<Group>, calleeName: String) =
-        groups.first {
-            it.parameters.isNotEmpty() && it.name == calleeName
-        }
-}
diff --git a/compose/ui/ui-tooling/src/androidTest/java/androidx/compose/ui/tooling/inspector/LayoutInspectorTreeTest.kt b/compose/ui/ui-tooling/src/androidTest/java/androidx/compose/ui/tooling/inspector/LayoutInspectorTreeTest.kt
deleted file mode 100644
index 7a375f3..0000000
--- a/compose/ui/ui-tooling/src/androidTest/java/androidx/compose/ui/tooling/inspector/LayoutInspectorTreeTest.kt
+++ /dev/null
@@ -1,570 +0,0 @@
-/*
- * Copyright 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.compose.ui.tooling.inspector
-
-import android.view.View
-import android.view.ViewGroup
-import androidx.compose.foundation.Image
-import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.Spacer
-import androidx.compose.foundation.layout.height
-import androidx.compose.foundation.text.BasicText
-import androidx.compose.material.Button
-import androidx.compose.material.Icon
-import androidx.compose.material.MaterialTheme
-import androidx.compose.material.ModalDrawer
-import androidx.compose.material.Surface
-import androidx.compose.material.Text
-import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.filled.Call
-import androidx.compose.material.icons.filled.FavoriteBorder
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.graphics.graphicsLayer
-import androidx.compose.ui.layout.GraphicLayerInfo
-import androidx.compose.ui.platform.isDebugInspectorInfoEnabled
-import androidx.compose.ui.text.TextStyle
-import androidx.compose.ui.text.style.TextDecoration
-import androidx.compose.ui.tooling.CompositionDataRecord
-import androidx.compose.ui.tooling.Inspectable
-import androidx.compose.ui.tooling.R
-import androidx.compose.ui.tooling.ToolingTest
-import androidx.compose.ui.tooling.data.Group
-import androidx.compose.ui.tooling.data.UiToolingDataApi
-import androidx.compose.ui.tooling.data.asTree
-import androidx.compose.ui.tooling.data.position
-import androidx.compose.ui.unit.Density
-import androidx.compose.ui.unit.Dp
-import androidx.compose.ui.unit.dp
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.LargeTest
-import androidx.test.filters.MediumTest
-import androidx.test.filters.SdkSuppress
-import com.google.common.truth.Truth.assertThat
-import com.google.common.truth.Truth.assertWithMessage
-import org.junit.After
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import kotlin.math.roundToInt
-
-private const val DEBUG = false
-
-@MediumTest
-@RunWith(AndroidJUnit4::class)
-@SdkSuppress(minSdkVersion = 29) // Render id is not returned for api < 29
-@OptIn(UiToolingDataApi::class)
-class LayoutInspectorTreeTest : ToolingTest() {
-    private lateinit var density: Density
-    private lateinit var view: View
-
-    @Before
-    fun before() {
-        density = Density(activity)
-        view = activityTestRule.activity.findViewById<ViewGroup>(android.R.id.content)
-        isDebugInspectorInfoEnabled = true
-    }
-
-    @After
-    fun after() {
-        isDebugInspectorInfoEnabled = false
-    }
-
-    @Test
-    fun buildTree() {
-        val slotTableRecord = CompositionDataRecord.create()
-
-        show {
-            Inspectable(slotTableRecord) {
-                Column {
-                    Text(text = "Hello World", color = Color.Green)
-                    Icon(Icons.Filled.FavoriteBorder, null)
-                    Surface {
-                        Button(onClick = {}) { Text(text = "OK") }
-                    }
-                }
-            }
-        }
-
-        // TODO: Find out if we can set "settings put global debug_view_attributes 1" in tests
-        view.setTag(R.id.inspection_slot_table_set, slotTableRecord.store)
-        val builder = LayoutInspectorTree()
-        val nodes = builder.convert(view)
-        dumpNodes(nodes, builder)
-
-        validate(nodes, builder, checkParameters = false) {
-            node(
-                name = "Inspectable",
-                fileName = "LayoutInspectorTreeTest.kt",
-                left = 0.0.dp, top = 0.0.dp, width = 72.0.dp, height = 78.9.dp,
-                children = listOf("Column")
-            )
-            node(
-                name = "Column",
-                fileName = "LayoutInspectorTreeTest.kt",
-                left = 0.0.dp, top = 0.0.dp, width = 72.0.dp, height = 78.9.dp,
-                children = listOf("Text", "Icon", "Surface")
-            )
-            node(
-                name = "Text",
-                isRenderNode = true,
-                fileName = "LayoutInspectorTreeTest.kt",
-                left = 0.0.dp, top = 0.0.dp, width = 72.0.dp, height = 18.9.dp,
-            )
-            node(
-                name = "Icon",
-                isRenderNode = true,
-                fileName = "LayoutInspectorTreeTest.kt",
-                left = 0.0.dp, top = 18.9.dp, width = 24.0.dp, height = 24.0.dp,
-            )
-            node(
-                name = "Surface",
-                fileName = "LayoutInspectorTreeTest.kt",
-                isRenderNode = true,
-                left = 0.0.dp,
-                top = 42.9.dp, width = 64.0.dp, height = 36.0.dp,
-                children = listOf("Button")
-            )
-            node(
-                name = "Button",
-                fileName = "LayoutInspectorTreeTest.kt",
-                isRenderNode = true,
-                left = 0.0.dp,
-                top = 42.9.dp, width = 64.0.dp, height = 36.0.dp,
-                children = listOf("Text")
-            )
-            node(
-                name = "Text",
-                isRenderNode = true,
-                fileName = "LayoutInspectorTreeTest.kt",
-                left = 21.7.dp, top = 51.6.dp, width = 20.9.dp, height = 18.9.dp,
-            )
-        }
-    }
-
-    @Test
-    fun buildTreeWithTransformedText() {
-        val slotTableRecord = CompositionDataRecord.create()
-
-        show {
-            Inspectable(slotTableRecord) {
-                MaterialTheme {
-                    Text(
-                        text = "Hello World",
-                        modifier = Modifier.graphicsLayer(rotationZ = 225f)
-                    )
-                }
-            }
-        }
-
-        // TODO: Find out if we can set "settings put global debug_view_attributes 1" in tests
-        view.setTag(R.id.inspection_slot_table_set, slotTableRecord.store)
-        val builder = LayoutInspectorTree()
-        val nodes = builder.convert(view)
-        dumpNodes(nodes, builder)
-
-        validate(nodes, builder, checkParameters = false) {
-            node(
-                name = "Inspectable",
-                hasTransformations = true,
-                fileName = "LayoutInspectorTreeTest.kt",
-                children = listOf("MaterialTheme")
-            )
-            node(
-                name = "MaterialTheme",
-                hasTransformations = true,
-                fileName = "LayoutInspectorTreeTest.kt",
-                left = 65.0.dp, top = 49.7.dp, width = 86.dp, height = 21.7.dp,
-                children = listOf("Text")
-            )
-            node(
-                name = "Text",
-                isRenderNode = true,
-                hasTransformations = true,
-                fileName = "LayoutInspectorTreeTest.kt",
-                left = 65.0.dp, top = 49.7.dp, width = 86.dp, height = 21.7.dp,
-            )
-        }
-    }
-
-    @Test
-    fun testStitchTreeFromModelDrawerLayout() {
-        val slotTableRecord = CompositionDataRecord.create()
-
-        show {
-            Inspectable(slotTableRecord) {
-                ModalDrawer(
-                    drawerContent = { Text("Something") },
-                    content = {
-                        Column {
-                            Text(text = "Hello World", color = Color.Green)
-                            Button(onClick = {}) { Text(text = "OK") }
-                        }
-                    }
-                )
-            }
-        }
-        view.setTag(R.id.inspection_slot_table_set, slotTableRecord.store)
-        dumpSlotTableSet(slotTableRecord)
-        val builder = LayoutInspectorTree()
-        val nodes = builder.convert(view)
-        dumpNodes(nodes, builder)
-
-        if (DEBUG) {
-            validate(nodes, builder, checkParameters = false) {
-                node("Inspectable", children = listOf("Box"))
-                node("Box", children = listOf("ModalDrawer"))
-                node("ModalDrawer", children = listOf("Column", "Text"))
-                node("Column", children = listOf("Text", "Button"))
-                node("Text")
-                node("Button", children = listOf("Text"))
-                node("Text")
-                node("Text")
-            }
-        }
-        assertThat(nodes.size).isEqualTo(1)
-    }
-
-    @LargeTest
-    @Test
-    fun testStitchTreeFromModelDrawerLayoutWithSystemNodes() {
-        val slotTableRecord = CompositionDataRecord.create()
-
-        show {
-            Inspectable(slotTableRecord) {
-                ModalDrawer(
-                    drawerContent = { Text("Something") },
-                    content = {
-                        Column {
-                            Text(text = "Hello World", color = Color.Green)
-                            Button(onClick = {}) { Text(text = "OK") }
-                        }
-                    }
-                )
-            }
-        }
-        view.setTag(R.id.inspection_slot_table_set, slotTableRecord.store)
-        dumpSlotTableSet(slotTableRecord)
-        val builder = LayoutInspectorTree()
-        builder.hideSystemNodes = false
-        val nodes = builder.convert(view)
-        dumpNodes(nodes, builder)
-
-        if (DEBUG) {
-            validate(nodes, builder, checkParameters = false) {
-                node("Inspectable", children = listOf("Box"))
-                node("Box", children = listOf("ModalDrawer"))
-                node("ModalDrawer", children = listOf("WithConstraints"))
-                node("WithConstraints", children = listOf("SubcomposeLayout"))
-                node("SubcomposeLayout", children = listOf("Box"))
-                node("Box", children = listOf("Box", "Canvas", "Surface"))
-                node("Box", children = listOf("Column"))
-                node("Column", children = listOf("Text", "Button"))
-                node("Text", children = listOf("Text"))
-                node("Text", children = listOf("CoreText"))
-                node("CoreText", children = listOf())
-                node("Button", children = listOf("Surface"))
-                node("Surface", children = listOf("ProvideTextStyle"))
-                node("ProvideTextStyle", children = listOf("Row"))
-                node("Row", children = listOf("Text"))
-                node("Text", children = listOf("Text"))
-                node("Text", children = listOf("CoreText"))
-                node("CoreText", children = listOf())
-                node("Canvas", children = listOf("Spacer"))
-                node("Spacer", children = listOf())
-                node("Surface", children = listOf("Column"))
-                node("Column", children = listOf("Text"))
-                node("Text", children = listOf("Text"))
-                node("Text", children = listOf("CoreText"))
-                node("CoreText", children = listOf())
-            }
-        }
-        assertThat(nodes.size).isEqualTo(1)
-    }
-
-    @Test
-    fun testSpacer() {
-        val slotTableRecord = CompositionDataRecord.create()
-
-        show {
-            Inspectable(slotTableRecord) {
-                Column {
-                    Text(text = "Hello World", color = Color.Green)
-                    Spacer(Modifier.height(16.dp))
-                    Image(Icons.Filled.Call, null)
-                }
-            }
-        }
-
-        view.setTag(R.id.inspection_slot_table_set, slotTableRecord.store)
-        val builder = LayoutInspectorTree()
-        val node = builder.convert(view)
-            .flatMap { flatten(it) }
-            .firstOrNull { it.name == "Spacer" }
-
-        // Spacer should show up in the Compose tree:
-        assertThat(node).isNotNull()
-    }
-
-    @Test // regression test b/174855322
-    fun testBasicText() {
-        val slotTableRecord = CompositionDataRecord.create()
-
-        view.setTag(R.id.inspection_slot_table_set, slotTableRecord.store)
-        show {
-            Inspectable(slotTableRecord) {
-                Column {
-                    BasicText(
-                        text = "Some text",
-                        style = TextStyle(textDecoration = TextDecoration.Underline)
-                    )
-                }
-            }
-        }
-
-        val builder = LayoutInspectorTree()
-        val node = builder.convert(view)
-            .flatMap { flatten(it) }
-            .firstOrNull { it.name == "BasicText" }
-
-        assertThat(node).isNotNull()
-
-        assertThat(node?.parameters).isNotEmpty()
-    }
-
-    @Test
-    fun testTextId() {
-        val slotTableRecord = CompositionDataRecord.create()
-
-        show {
-            Inspectable(slotTableRecord) {
-                Text(text = "Hello World")
-            }
-        }
-
-        view.setTag(R.id.inspection_slot_table_set, slotTableRecord.store)
-        val builder = LayoutInspectorTree()
-        val node = builder.convert(view)
-            .flatMap { flatten(it) }
-            .firstOrNull { it.name == "Text" }
-
-        // LayoutNode id should be captured by the Text node:
-        assertThat(node?.id).isGreaterThan(0)
-    }
-
-    @Suppress("SameParameterValue")
-    private fun validate(
-        result: List<InspectorNode>,
-        builder: LayoutInspectorTree,
-        checkParameters: Boolean,
-        block: TreeValidationReceiver.() -> Unit = {}
-    ) {
-        val nodes = result.flatMap { flatten(it) }.iterator()
-        val tree = TreeValidationReceiver(nodes, density, checkParameters, builder)
-        tree.block()
-    }
-
-    private class TreeValidationReceiver(
-        val nodeIterator: Iterator<InspectorNode>,
-        val density: Density,
-        val checkParameters: Boolean,
-        val builder: LayoutInspectorTree
-    ) {
-        fun node(
-            name: String,
-            fileName: String? = null,
-            lineNumber: Int = -1,
-            isRenderNode: Boolean = false,
-            hasTransformations: Boolean = false,
-
-            left: Dp = Dp.Unspecified,
-            top: Dp = Dp.Unspecified,
-            width: Dp = Dp.Unspecified,
-            height: Dp = Dp.Unspecified,
-            children: List<String> = listOf(),
-            block: ParameterValidationReceiver.() -> Unit = {}
-        ) {
-            assertWithMessage("No such node found: $name").that(nodeIterator.hasNext()).isTrue()
-            val node = nodeIterator.next()
-            assertThat(node.name).isEqualTo(name)
-            val message = "Node: $name"
-            assertWithMessage(message).that(node.children.map { it.name })
-                .containsExactlyElementsIn(children).inOrder()
-            fileName?.let { assertWithMessage(message).that(node.fileName).isEqualTo(fileName) }
-            if (lineNumber != -1) {
-                assertWithMessage(message).that(node.lineNumber).isEqualTo(lineNumber)
-            }
-            if (isRenderNode) {
-                assertWithMessage(message).that(node.id).isGreaterThan(0L)
-            } else {
-                assertWithMessage(message).that(node.id).isLessThan(0L)
-            }
-            if (hasTransformations) {
-                assertWithMessage(message).that(node.bounds).isNotEmpty()
-            } else {
-                assertWithMessage(message).that(node.bounds).isEmpty()
-            }
-            if (left != Dp.Unspecified) {
-                val tolerance = 5.0f
-                with(density) {
-                    assertWithMessage(message).that(node.left.toDp().value)
-                        .isWithin(tolerance).of(left.value)
-                    assertWithMessage(message).that(node.top.toDp().value)
-                        .isWithin(tolerance).of(top.value)
-                    assertWithMessage(message).that(node.width.toDp().value)
-                        .isWithin(tolerance).of(width.value)
-                    assertWithMessage(message).that(node.height.toDp().value)
-                        .isWithin(tolerance).of(height.value)
-                }
-            }
-
-            if (checkParameters) {
-                val params = builder.convertParameters(node)
-                val receiver = ParameterValidationReceiver(params.listIterator())
-                receiver.block()
-                if (receiver.parameterIterator.hasNext()) {
-                    val elementNames = mutableListOf<String>()
-                    receiver.parameterIterator.forEachRemaining { elementNames.add(it.name) }
-                    error("$name: has more parameters like: ${elementNames.joinToString()}")
-                }
-            }
-        }
-    }
-
-    private fun flatten(node: InspectorNode): List<InspectorNode> =
-        listOf(node).plus(node.children.flatMap { flatten(it) })
-
-    // region DEBUG print methods
-    private fun dumpNodes(nodes: List<InspectorNode>, builder: LayoutInspectorTree) {
-        @Suppress("ConstantConditionIf")
-        if (!DEBUG) {
-            return
-        }
-        println()
-        println("=================== Nodes ==========================")
-        nodes.forEach { dumpNode(it, indent = 0) }
-        println()
-        println("=================== validate statements ==========================")
-        nodes.forEach { generateValidate(it, builder) }
-    }
-
-    private fun dumpNode(node: InspectorNode, indent: Int) {
-        println(
-            "\"${"  ".repeat(indent * 2)}\", \"${node.name}\", \"${node.fileName}\", " +
-                "${node.lineNumber}, ${node.left}, ${node.top}, " +
-                "${node.width}, ${node.height}"
-        )
-        node.children.forEach { dumpNode(it, indent + 1) }
-    }
-
-    private fun generateValidate(
-        node: InspectorNode,
-        builder: LayoutInspectorTree,
-        generateParameters: Boolean = false
-    ) {
-        with(density) {
-            val left = round(node.left.toDp())
-            val top = round(node.top.toDp())
-            val width = if (node.width == view.width) "viewWidth" else round(node.width.toDp())
-            val height = if (node.height == view.height) "viewHeight" else round(node.height.toDp())
-
-            print(
-                """
-                  validate(
-                      name = "${node.name}",
-                      fileName = "${node.fileName}",
-                      left = $left, top = $top, width = $width, height = $height
-                """.trimIndent()
-            )
-        }
-        if (node.id > 0L) {
-            println(",")
-            print("    isRenderNode = true")
-        }
-        if (node.children.isNotEmpty()) {
-            println(",")
-            val children = node.children.joinToString { "\"${it.name}\"" }
-            print("    children = listOf($children)")
-        }
-        println()
-        print(")")
-        if (generateParameters && node.parameters.isNotEmpty()) {
-            generateParameters(builder.convertParameters(node), 0)
-        }
-        println()
-        node.children.forEach { generateValidate(it, builder) }
-    }
-
-    private fun generateParameters(parameters: List<NodeParameter>, indent: Int) {
-        val indentation = " ".repeat(indent * 2)
-        println(" {")
-        for (param in parameters) {
-            val name = param.name
-            val type = param.type
-            val value = toDisplayValue(type, param.value)
-            print("$indentation  parameter(name = \"$name\", type = $type, value = $value)")
-            if (param.elements.isNotEmpty()) {
-                generateParameters(param.elements, indent + 1)
-            }
-            println()
-        }
-        print("$indentation}")
-    }
-
-    private fun toDisplayValue(type: ParameterType, value: Any?): String =
-        when (type) {
-            ParameterType.Boolean -> value.toString()
-            ParameterType.Color ->
-                "0x${Integer.toHexString(value as Int)}${if (value < 0) ".toInt()" else ""}"
-            ParameterType.DimensionSp,
-            ParameterType.DimensionDp -> "${value}f"
-            ParameterType.Int32 -> value.toString()
-            ParameterType.String -> "\"$value\""
-            else -> value?.toString() ?: "null"
-        }
-
-    private fun dumpSlotTableSet(slotTableRecord: CompositionDataRecord) {
-        @Suppress("ConstantConditionIf")
-        if (!DEBUG) {
-            return
-        }
-        println()
-        println("=================== Groups ==========================")
-        slotTableRecord.store.forEach { dumpGroup(it.asTree(), indent = 0) }
-    }
-
-    private fun dumpGroup(group: Group, indent: Int) {
-        val position = group.position?.let { "\"$it\"" } ?: "null"
-        val box = group.box
-        val id = group.modifierInfo.mapNotNull { (it.extra as? GraphicLayerInfo)?.layerId }
-            .singleOrNull() ?: 0
-        println(
-            "\"${"  ".repeat(indent)}\", ${group.javaClass.simpleName}, \"${group.name}\", " +
-                "params: ${group.parameters.size}, children: ${group.children.size}, " +
-                "$id, $position, " +
-                "${box.left}, ${box.right}, ${box.right - box.left}, ${box.bottom - box.top}"
-        )
-        for (parameter in group.parameters) {
-            println("\"${"  ".repeat(indent + 4)}\"- ${parameter.name}")
-        }
-        group.children.forEach { dumpGroup(it, indent + 1) }
-    }
-
-    private fun round(dp: Dp): Dp = Dp((dp.value * 10.0f).roundToInt() / 10.0f)
-
-    //endregion
-}
diff --git a/compose/ui/ui-tooling/src/androidTest/java/androidx/compose/ui/tooling/inspector/ParameterFactoryTest.kt b/compose/ui/ui-tooling/src/androidTest/java/androidx/compose/ui/tooling/inspector/ParameterFactoryTest.kt
deleted file mode 100644
index 83459eb..0000000
--- a/compose/ui/ui-tooling/src/androidTest/java/androidx/compose/ui/tooling/inspector/ParameterFactoryTest.kt
+++ /dev/null
@@ -1,737 +0,0 @@
-/*
- * Copyright 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.compose.ui.tooling.inspector
-
-import androidx.compose.foundation.BorderStroke
-import androidx.compose.foundation.background
-import androidx.compose.foundation.border
-import androidx.compose.foundation.layout.Arrangement
-import androidx.compose.foundation.layout.PaddingValues
-import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.layout.width
-import androidx.compose.foundation.layout.wrapContentHeight
-import androidx.compose.foundation.shape.CornerSize
-import androidx.compose.foundation.shape.CutCornerShape
-import androidx.compose.foundation.shape.RoundedCornerShape
-import androidx.compose.foundation.shape.ZeroCornerSize
-import androidx.compose.material.Text
-import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.filled.Call
-import androidx.compose.material.icons.rounded.Add
-import androidx.compose.runtime.Composable
-import androidx.compose.ui.AbsoluteAlignment
-import androidx.compose.ui.Alignment
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.draw.paint
-import androidx.compose.ui.geometry.Offset
-import androidx.compose.ui.geometry.Size
-import androidx.compose.ui.graphics.Brush
-import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.graphics.RectangleShape
-import androidx.compose.ui.graphics.Shadow
-import androidx.compose.ui.graphics.SolidColor
-import androidx.compose.ui.graphics.colorspace.ColorModel
-import androidx.compose.ui.graphics.drawscope.DrawScope
-import androidx.compose.ui.graphics.painter.Painter
-import androidx.compose.ui.graphics.toArgb
-import androidx.compose.ui.platform.isDebugInspectorInfoEnabled
-import androidx.compose.ui.text.AnnotatedString
-import androidx.compose.ui.text.TextStyle
-import androidx.compose.ui.text.font.Font
-import androidx.compose.ui.text.font.FontFamily
-import androidx.compose.ui.text.font.FontStyle
-import androidx.compose.ui.text.font.FontWeight
-import androidx.compose.ui.text.intl.Locale
-import androidx.compose.ui.text.intl.LocaleList
-import androidx.compose.ui.text.style.BaselineShift
-import androidx.compose.ui.text.style.TextDecoration
-import androidx.compose.ui.text.style.TextGeometricTransform
-import androidx.compose.ui.text.style.TextIndent
-import androidx.compose.ui.unit.Density
-import androidx.compose.ui.unit.Dp
-import androidx.compose.ui.unit.LayoutDirection
-import androidx.compose.ui.unit.TextUnit
-import androidx.compose.ui.unit.dp
-import androidx.compose.ui.unit.em
-import androidx.compose.ui.unit.sp
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.MediumTest
-import com.google.common.truth.Truth.assertThat
-import com.google.common.truth.Truth.assertWithMessage
-import kotlinx.coroutines.runBlocking
-import org.junit.After
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@Suppress("unused")
-private fun topLevelFunction() {}
-
-@MediumTest
-@RunWith(AndroidJUnit4::class)
-class ParameterFactoryTest {
-    private val factory = ParameterFactory(InlineClassConverter())
-    private val node = MutableInspectorNode().apply {
-        width = 1000
-        height = 500
-    }.build()
-
-    @Before
-    fun before() {
-        factory.density = Density(2.0f)
-        isDebugInspectorInfoEnabled = true
-    }
-
-    @After
-    fun after() {
-        isDebugInspectorInfoEnabled = false
-    }
-
-    @Test
-    fun testAbsoluteAlignment() {
-        assertThat(lookup(AbsoluteAlignment.TopLeft))
-            .isEqualTo(ParameterType.String to "TopLeft")
-        assertThat(lookup(AbsoluteAlignment.TopRight))
-            .isEqualTo(ParameterType.String to "TopRight")
-        assertThat(lookup(AbsoluteAlignment.CenterLeft))
-            .isEqualTo(ParameterType.String to "CenterLeft")
-        assertThat(lookup(AbsoluteAlignment.CenterRight))
-            .isEqualTo(ParameterType.String to "CenterRight")
-        assertThat(lookup(AbsoluteAlignment.BottomLeft))
-            .isEqualTo(ParameterType.String to "BottomLeft")
-        assertThat(lookup(AbsoluteAlignment.BottomRight))
-            .isEqualTo(ParameterType.String to "BottomRight")
-        assertThat(lookup(AbsoluteAlignment.Left))
-            .isEqualTo(ParameterType.String to "Left")
-        assertThat(lookup(AbsoluteAlignment.Right))
-            .isEqualTo(ParameterType.String to "Right")
-    }
-
-    @Test
-    fun testAlignment() {
-        assertThat(lookup(Alignment.Top)).isEqualTo(ParameterType.String to "Top")
-        assertThat(lookup(Alignment.Bottom)).isEqualTo(ParameterType.String to "Bottom")
-        assertThat(lookup(Alignment.CenterVertically))
-            .isEqualTo(ParameterType.String to "CenterVertically")
-
-        assertThat(lookup(Alignment.Start)).isEqualTo(ParameterType.String to "Start")
-        assertThat(lookup(Alignment.End)).isEqualTo(ParameterType.String to "End")
-        assertThat(lookup(Alignment.CenterHorizontally))
-            .isEqualTo(ParameterType.String to "CenterHorizontally")
-
-        assertThat(lookup(Alignment.TopStart)).isEqualTo(ParameterType.String to "TopStart")
-        assertThat(lookup(Alignment.TopCenter)).isEqualTo(ParameterType.String to "TopCenter")
-        assertThat(lookup(Alignment.TopEnd)).isEqualTo(ParameterType.String to "TopEnd")
-        assertThat(lookup(Alignment.CenterStart)).isEqualTo(ParameterType.String to "CenterStart")
-        assertThat(lookup(Alignment.Center)).isEqualTo(ParameterType.String to "Center")
-        assertThat(lookup(Alignment.CenterEnd)).isEqualTo(ParameterType.String to "CenterEnd")
-        assertThat(lookup(Alignment.BottomStart)).isEqualTo(ParameterType.String to "BottomStart")
-        assertThat(lookup(Alignment.BottomCenter)).isEqualTo(ParameterType.String to "BottomCenter")
-        assertThat(lookup(Alignment.BottomEnd)).isEqualTo(ParameterType.String to "BottomEnd")
-    }
-
-    @Test
-    fun testAnnotatedString() {
-        assertThat(lookup(AnnotatedString("Hello"))).isEqualTo(ParameterType.String to "Hello")
-    }
-
-    @Test
-    fun testArrangement() {
-        assertThat(lookup(Arrangement.Top)).isEqualTo(ParameterType.String to "Top")
-        assertThat(lookup(Arrangement.Bottom)).isEqualTo(ParameterType.String to "Bottom")
-        assertThat(lookup(Arrangement.Center)).isEqualTo(ParameterType.String to "Center")
-        assertThat(lookup(Arrangement.Start)).isEqualTo(ParameterType.String to "Start")
-        assertThat(lookup(Arrangement.End)).isEqualTo(ParameterType.String to "End")
-        assertThat(lookup(Arrangement.SpaceEvenly)).isEqualTo(ParameterType.String to "SpaceEvenly")
-        assertThat(lookup(Arrangement.SpaceBetween))
-            .isEqualTo(ParameterType.String to "SpaceBetween")
-        assertThat(lookup(Arrangement.SpaceAround)).isEqualTo(ParameterType.String to "SpaceAround")
-    }
-
-    @Test
-    fun testBaselineShift() {
-        assertThat(lookup(BaselineShift.None)).isEqualTo(ParameterType.String to "None")
-        assertThat(lookup(BaselineShift.Subscript)).isEqualTo(ParameterType.String to "Subscript")
-        assertThat(lookup(BaselineShift.Superscript))
-            .isEqualTo(ParameterType.String to "Superscript")
-        assertThat(lookup(BaselineShift(0.0f))).isEqualTo(ParameterType.String to "None")
-        assertThat(lookup(BaselineShift(-0.5f))).isEqualTo(ParameterType.String to "Subscript")
-        assertThat(lookup(BaselineShift(0.5f))).isEqualTo(ParameterType.String to "Superscript")
-        assertThat(lookup(BaselineShift(0.1f))).isEqualTo(ParameterType.Float to 0.1f)
-        assertThat(lookup(BaselineShift(0.75f))).isEqualTo(ParameterType.Float to 0.75f)
-    }
-
-    @Test
-    fun testBoolean() {
-        assertThat(lookup(true)).isEqualTo(ParameterType.Boolean to true)
-        assertThat(lookup(false)).isEqualTo(ParameterType.Boolean to false)
-    }
-
-    @Test
-    fun testBorder() {
-        validate(factory.create(node, "borderstroke", BorderStroke(2.0.dp, Color.Magenta))!!) {
-            parameter("borderstroke", ParameterType.String, "BorderStroke") {
-                parameter("brush", ParameterType.Color, Color.Magenta.toArgb())
-                parameter("width", ParameterType.DimensionDp, 2.0f)
-            }
-        }
-    }
-
-    @Test
-    fun testBrush() {
-        assertThat(lookup(SolidColor(Color.Red)))
-            .isEqualTo(ParameterType.Color to Color.Red.toArgb())
-        validate(
-            factory.create(
-                node,
-                "brush",
-                Brush.linearGradient(
-                    colors = listOf(Color.Red, Color.Blue),
-                    start = Offset(0.0f, 0.5f),
-                    end = Offset(5.0f, 10.0f)
-                )
-            )!!
-        ) {
-            parameter("brush", ParameterType.String, "LinearGradient") {
-                parameter("colors", ParameterType.String, "") {
-                    parameter("0", ParameterType.Color, Color.Red.toArgb())
-                    parameter("1", ParameterType.Color, Color.Blue.toArgb())
-                }
-                // Parameters are traversed in alphabetical order through reflection queries.
-                // Validate createdSize exists before validating end parameter
-                parameter("createdSize", ParameterType.String, "Unspecified")
-                parameter("end", ParameterType.String, Offset::class.java.simpleName) {
-                    parameter("x", ParameterType.DimensionDp, 2.5f)
-                    parameter("y", ParameterType.DimensionDp, 5.0f)
-                }
-                parameter("start", ParameterType.String, Offset::class.java.simpleName) {
-                    parameter("x", ParameterType.DimensionDp, 0.0f)
-                    parameter("y", ParameterType.DimensionDp, 0.25f)
-                }
-                parameter("tileMode", ParameterType.String, "Clamp")
-            }
-        }
-        // TODO: add tests for RadialGradient & ShaderBrush
-    }
-
-    @Test
-    fun testColor() {
-        assertThat(lookup(Color.Blue)).isEqualTo(ParameterType.Color to 0xff0000ff.toInt())
-        assertThat(lookup(Color.Red)).isEqualTo(ParameterType.Color to 0xffff0000.toInt())
-        assertThat(lookup(Color.Transparent)).isEqualTo(ParameterType.Color to 0x00000000)
-        assertThat(lookup(Color.Unspecified)).isEqualTo(ParameterType.String to "Unspecified")
-    }
-
-    @Test
-    fun testComposableLambda() = runBlocking {
-        // capture here to force the lambda to not be created as a singleton.
-        val capture = "Hello World"
-        val c: @Composable () -> Unit = { Text(text = capture) }
-        val result = lookup(c as Any) ?: error("Lookup of ComposableLambda failed")
-        val array = result.second as Array<*>
-        assertThat(result.first).isEqualTo(ParameterType.Lambda)
-        assertThat(array).hasLength(1)
-        assertThat(array[0]?.javaClass?.name).isEqualTo(
-            "${ParameterFactoryTest::class.java.name}\$testComposableLambda\$1\$c\$1"
-        )
-    }
-
-    @Test
-    fun testCornerBasedShape() {
-        validate(
-            factory.create(
-                node, "corner",
-                RoundedCornerShape(2.0.dp, 0.5.dp, 2.5.dp, 0.7.dp)
-            )!!
-        ) {
-            parameter("corner", ParameterType.String, RoundedCornerShape::class.java.simpleName) {
-                parameter("bottomEnd", ParameterType.DimensionDp, 2.5f)
-                parameter("bottomStart", ParameterType.DimensionDp, 0.7f)
-                parameter("topEnd", ParameterType.DimensionDp, 0.5f)
-                parameter("topStart", ParameterType.DimensionDp, 2.0f)
-            }
-        }
-        validate(factory.create(node, "corner", CutCornerShape(2))!!) {
-            parameter("corner", ParameterType.String, CutCornerShape::class.java.simpleName) {
-                parameter("bottomEnd", ParameterType.DimensionDp, 5.0f)
-                parameter("bottomStart", ParameterType.DimensionDp, 5.0f)
-                parameter("topEnd", ParameterType.DimensionDp, 5.0f)
-                parameter("topStart", ParameterType.DimensionDp, 5.0f)
-            }
-        }
-        validate(factory.create(node, "corner", RoundedCornerShape(1.0f, 10.0f, 2.0f, 3.5f))!!) {
-            parameter("corner", ParameterType.String, RoundedCornerShape::class.java.simpleName) {
-                parameter("bottomEnd", ParameterType.DimensionDp, 1.0f)
-                parameter("bottomStart", ParameterType.DimensionDp, 1.75f)
-                parameter("topEnd", ParameterType.DimensionDp, 5.0f)
-                parameter("topStart", ParameterType.DimensionDp, 0.5f)
-            }
-        }
-    }
-
-    @Test
-    fun testCornerSize() {
-        assertThat(lookup(ZeroCornerSize)).isEqualTo(ParameterType.String to "ZeroCornerSize")
-        assertThat(lookup(CornerSize(2.4.dp))).isEqualTo(ParameterType.DimensionDp to 2.4f)
-        assertThat(lookup(CornerSize(2.4f))).isEqualTo(ParameterType.DimensionDp to 1.2f)
-        assertThat(lookup(CornerSize(3))).isEqualTo(ParameterType.DimensionDp to 7.5f)
-    }
-
-    @Test
-    fun testDouble() {
-        assertThat(lookup(3.1428)).isEqualTo(ParameterType.Double to 3.1428)
-    }
-
-    @Test
-    fun testDp() {
-        assertThat(lookup(2.0.dp)).isEqualTo(ParameterType.DimensionDp to 2.0f)
-        assertThat(lookup(Dp.Hairline)).isEqualTo(ParameterType.DimensionDp to 0.0f)
-        assertThat(lookup(Dp.Unspecified)).isEqualTo(ParameterType.DimensionDp to Float.NaN)
-        assertThat(lookup(Dp.Infinity))
-            .isEqualTo(ParameterType.DimensionDp to Float.POSITIVE_INFINITY)
-    }
-
-    @Test
-    fun testEnum() {
-        assertThat(lookup(ColorModel.Lab)).isEqualTo(ParameterType.String to "Lab")
-        assertThat(lookup(ColorModel.Rgb)).isEqualTo(ParameterType.String to "Rgb")
-        assertThat(lookup(ColorModel.Cmyk)).isEqualTo(ParameterType.String to "Cmyk")
-    }
-
-    @Test
-    fun testFloat() {
-        assertThat(lookup(3.1428f)).isEqualTo(ParameterType.Float to 3.1428f)
-    }
-
-    @Test
-    fun testFontFamily() {
-        assertThat(lookup(FontFamily.Cursive)).isEqualTo(ParameterType.String to "Cursive")
-        assertThat(lookup(FontFamily.Default)).isEqualTo(ParameterType.String to "Default")
-        assertThat(lookup(FontFamily.SansSerif)).isEqualTo(ParameterType.String to "SansSerif")
-        assertThat(lookup(FontFamily.Serif)).isEqualTo(ParameterType.String to "Serif")
-        assertThat(lookup(FontFamily.Monospace)).isEqualTo(ParameterType.String to "Monospace")
-    }
-
-    @Test
-    fun testFontListFontFamily() {
-        val family = FontFamily(
-            listOf(
-                Font(1234, FontWeight.Normal, FontStyle.Italic),
-                Font(1235, FontWeight.Normal, FontStyle.Normal),
-                Font(1236, FontWeight.Bold, FontStyle.Italic),
-                Font(1237, FontWeight.Bold, FontStyle.Normal)
-            )
-        )
-        assertThat(lookup(family)).isEqualTo(ParameterType.Resource to 1235)
-    }
-
-    @Test
-    fun testFontWeight() {
-        assertThat(lookup(FontWeight.Thin)).isEqualTo(ParameterType.String to "W100")
-        assertThat(lookup(FontWeight.ExtraLight)).isEqualTo(ParameterType.String to "W200")
-        assertThat(lookup(FontWeight.Light)).isEqualTo(ParameterType.String to "W300")
-        assertThat(lookup(FontWeight.Normal)).isEqualTo(ParameterType.String to "W400")
-        assertThat(lookup(FontWeight.Medium)).isEqualTo(ParameterType.String to "W500")
-        assertThat(lookup(FontWeight.SemiBold)).isEqualTo(ParameterType.String to "W600")
-        assertThat(lookup(FontWeight.Bold)).isEqualTo(ParameterType.String to "W700")
-        assertThat(lookup(FontWeight.ExtraBold)).isEqualTo(ParameterType.String to "W800")
-        assertThat(lookup(FontWeight.Black)).isEqualTo(ParameterType.String to "W900")
-        assertThat(lookup(FontWeight.W100)).isEqualTo(ParameterType.String to "W100")
-        assertThat(lookup(FontWeight.W200)).isEqualTo(ParameterType.String to "W200")
-        assertThat(lookup(FontWeight.W300)).isEqualTo(ParameterType.String to "W300")
-        assertThat(lookup(FontWeight.W400)).isEqualTo(ParameterType.String to "W400")
-        assertThat(lookup(FontWeight.W500)).isEqualTo(ParameterType.String to "W500")
-        assertThat(lookup(FontWeight.W600)).isEqualTo(ParameterType.String to "W600")
-        assertThat(lookup(FontWeight.W700)).isEqualTo(ParameterType.String to "W700")
-        assertThat(lookup(FontWeight.W800)).isEqualTo(ParameterType.String to "W800")
-        assertThat(lookup(FontWeight.W900)).isEqualTo(ParameterType.String to "W900")
-    }
-
-    @Test
-    fun testFunctionReference() {
-        val ref1 = ::testInt
-        val map1 = lookup(ref1)!!
-        val array1 = map1.second as Array<*>
-        assertThat(map1.first).isEqualTo(ParameterType.FunctionReference)
-        assertThat(array1.contentEquals(arrayOf(ref1, "testInt"))).isTrue()
-        val ref2 = ::topLevelFunction
-        val map2 = lookup(ref2)!!
-        val array2 = map2.second as Array<*>
-        assertThat(map2.first).isEqualTo(ParameterType.FunctionReference)
-        assertThat(array2.contentEquals(arrayOf(ref2, "topLevelFunction"))).isTrue()
-    }
-
-    @Test
-    fun testPaddingValues() {
-        validate(factory.create(node, "padding", PaddingValues(2.0.dp, 0.5.dp, 2.5.dp, 0.7.dp))!!) {
-            parameter(
-                "padding",
-                ParameterType.String,
-                "PaddingValuesImpl"
-            ) {
-                parameter("bottom", ParameterType.DimensionDp, 0.7f)
-                parameter("end", ParameterType.DimensionDp, 2.5f)
-                parameter("start", ParameterType.DimensionDp, 2.0f)
-                parameter("top", ParameterType.DimensionDp, 0.5f)
-            }
-        }
-    }
-
-    @Test
-    fun testInt() {
-        assertThat(lookup(12345)).isEqualTo(ParameterType.Int32 to 12345)
-    }
-
-    @Test
-    fun testLambda() {
-        val a: (Int) -> Int = { it }
-        val map = lookup(a)!!
-        val array = map.second as Array<*>
-        assertThat(map.first).isEqualTo(ParameterType.Lambda)
-        assertThat(array.contentEquals(arrayOf<Any>(a))).isTrue()
-    }
-
-    @Test
-    fun testLocale() {
-        assertThat(lookup(Locale("fr-CA"))).isEqualTo(ParameterType.String to "fr-CA")
-        assertThat(lookup(Locale("fr-BE"))).isEqualTo(ParameterType.String to "fr-BE")
-    }
-
-    @Test
-    fun testLocaleList() {
-        validate(factory.create(node, "locales", LocaleList(Locale("fr-ca"), Locale("fr-be")))!!) {
-            parameter("locales", ParameterType.String, "") {
-                parameter("0", ParameterType.String, "fr-CA")
-                parameter("1", ParameterType.String, "fr-BE")
-            }
-        }
-    }
-
-    @Test
-    fun testLong() {
-        assertThat(lookup(12345L)).isEqualTo(ParameterType.Int64 to 12345L)
-    }
-
-    @Test
-    fun testModifier() {
-        validate(
-            factory.create(
-                node, "modifier",
-                Modifier
-                    .background(Color.Blue)
-                    .border(width = 5.dp, color = Color.Red)
-                    .padding(2.0.dp)
-                    .fillMaxWidth()
-                    .wrapContentHeight(Alignment.Bottom)
-                    .width(30.0.dp)
-                    .paint(TestPainter(10f, 20f))
-            )!!
-        ) {
-            parameter("modifier", ParameterType.String, "") {
-                parameter("background", ParameterType.Color, Color.Blue.toArgb()) {
-                    parameter("color", ParameterType.Color, Color.Blue.toArgb())
-                    parameter("shape", ParameterType.String, "RectangleShape")
-                }
-                parameter("border", ParameterType.Color, Color.Red.toArgb()) {
-                    parameter("color", ParameterType.Color, Color.Red.toArgb())
-                    parameter("shape", ParameterType.String, "RectangleShape")
-                    parameter("width", ParameterType.DimensionDp, 5.0f)
-                }
-                parameter("padding", ParameterType.DimensionDp, 2.0f)
-                parameter("fillMaxWidth", ParameterType.String, "") {
-                    parameter("fraction", ParameterType.Float, 1.0f)
-                }
-                parameter("wrapContentHeight", ParameterType.String, "") {
-                    parameter("align", ParameterType.String, "Bottom")
-                    parameter("unbounded", ParameterType.Boolean, false)
-                }
-                parameter("width", ParameterType.DimensionDp, 30.0f)
-                parameter("paint", ParameterType.String, "") {
-                    parameter("alignment", ParameterType.String, "Center")
-                    parameter("alpha", ParameterType.Float, 1.0f)
-                    parameter("contentScale", ParameterType.String, "Inside")
-                    parameter("painter", ParameterType.String, "TestPainter") {
-                        parameter("alpha", ParameterType.Float, 1.0f)
-                        parameter("color", ParameterType.Color, Color.Red.toArgb())
-                        parameter("drawLambda", ParameterType.Lambda, null)
-                        parameter("height", ParameterType.Float, 20.0f)
-                        parameter("intrinsicSize", ParameterType.String, "Size") {
-                            parameter("height", ParameterType.Float, 20.0f)
-                            parameter("maxDimension", ParameterType.Float, 20.0f)
-                            parameter("minDimension", ParameterType.Float, 10.0f)
-                            parameter("packedValue", ParameterType.Int64, 4692750812821061632L)
-                            parameter("width", ParameterType.Float, 10.0f)
-                        }
-                        parameter("layoutDirection", ParameterType.String, "Ltr")
-                        parameter("useLayer", ParameterType.Boolean, false)
-                        parameter("width", ParameterType.Float, 10.0f)
-                    }
-                    parameter("sizeToIntrinsics", ParameterType.Boolean, true)
-                }
-            }
-        }
-    }
-
-    @Test
-    fun testSingleModifier() {
-        validate(factory.create(node, "modifier", Modifier.padding(2.0.dp))!!) {
-            parameter("modifier", ParameterType.String, "") {
-                parameter("padding", ParameterType.DimensionDp, 2.0f)
-            }
-        }
-    }
-
-    @Test
-    fun testSingleModifierWithParameters() {
-        validate(factory.create(node, "modifier", Modifier.padding(1.dp, 2.dp, 3.dp, 4.dp))!!) {
-            parameter("modifier", ParameterType.String, "") {
-                parameter("padding", ParameterType.String, "") {
-                    parameter("bottom", ParameterType.DimensionDp, 4.0f)
-                    parameter("end", ParameterType.DimensionDp, 3.0f)
-                    parameter("start", ParameterType.DimensionDp, 1.0f)
-                    parameter("top", ParameterType.DimensionDp, 2.0f)
-                }
-            }
-        }
-    }
-
-    @Test
-    fun testOffset() {
-        validate(factory.create(node, "offset", Offset(1.0f, 5.0f))!!) {
-            parameter("offset", ParameterType.String, Offset::class.java.simpleName) {
-                parameter("x", ParameterType.DimensionDp, 0.5f)
-                parameter("y", ParameterType.DimensionDp, 2.5f)
-            }
-        }
-        validate(factory.create(node, "offset", Offset.Zero)!!) {
-            parameter("offset", ParameterType.String, "Zero")
-        }
-    }
-
-    @Test
-    fun testRecursiveStructure() {
-        val v1 = MyClass()
-        val v2 = MyClass()
-        v1.other = v2
-        v2.other = v1
-        val name = MyClass::class.java.simpleName
-        validate(factory.create(node, "mine", v1)!!) {
-            parameter("mine", ParameterType.String, name) {
-                parameter("other", ParameterType.String, name) {
-                    parameter("other", ParameterType.String, name) {
-                        parameter("other", ParameterType.String, name) {
-                            parameter("other", ParameterType.String, name) {
-                                parameter("other", ParameterType.String, name) {
-                                    parameter("other", ParameterType.String, name) {
-                                        parameter("other", ParameterType.String, name) {
-                                            parameter("other", ParameterType.String, name) {
-                                                parameter("other", ParameterType.String, name)
-                                            }
-                                        }
-                                    }
-                                }
-                            }
-                        }
-                    }
-                }
-            }
-        }
-    }
-
-    @Test
-    fun testDoNotRecurseIntoAndroidAndJavaPackages() {
-        runBlocking {
-            assertThat(factory.create(node, "v1", java.net.URL("http://domain.com"))).isNull()
-            assertThat(factory.create(node, "v1", android.app.Notification())).isNull()
-        }
-    }
-
-    @Test
-    fun testShadow() {
-        assertThat(lookup(Shadow.None)).isEqualTo(ParameterType.String to "None")
-        validate(factory.create(node, "shadow", Shadow(Color.Cyan, Offset.Zero, 2.5f))!!) {
-            parameter("shadow", ParameterType.String, Shadow::class.java.simpleName) {
-                parameter("blurRadius", ParameterType.DimensionDp, 1.25f)
-                parameter("color", ParameterType.Color, Color.Cyan.toArgb())
-                parameter("offset", ParameterType.String, "Zero")
-            }
-        }
-        validate(factory.create(node, "shadow", Shadow(Color.Blue, Offset(1.0f, 4.0f), 1.5f))!!) {
-            parameter("shadow", ParameterType.String, Shadow::class.java.simpleName) {
-                parameter("blurRadius", ParameterType.DimensionDp, 0.75f)
-                parameter("color", ParameterType.Color, Color.Blue.toArgb())
-                parameter("offset", ParameterType.String, Offset::class.java.simpleName) {
-                    parameter("x", ParameterType.DimensionDp, 0.5f)
-                    parameter("y", ParameterType.DimensionDp, 2.0f)
-                }
-            }
-        }
-    }
-
-    @Test
-    fun testShape() {
-        assertThat(lookup(RectangleShape)).isEqualTo(ParameterType.String to "RectangleShape")
-    }
-
-    @Test
-    fun testString() {
-        assertThat(lookup("Hello")).isEqualTo(ParameterType.String to "Hello")
-    }
-
-    @Test
-    fun testTextDecoration() {
-        assertThat(lookup(TextDecoration.None)).isEqualTo(ParameterType.String to "None")
-        assertThat(lookup(TextDecoration.LineThrough))
-            .isEqualTo(ParameterType.String to "LineThrough")
-        assertThat(lookup(TextDecoration.Underline))
-            .isEqualTo(ParameterType.String to "Underline")
-        assertThat(lookup(TextDecoration.LineThrough + TextDecoration.Underline))
-            .isEqualTo(ParameterType.String to "LineThrough+Underline")
-    }
-
-    @Test
-    fun testTextGeometricTransform() {
-        validate(factory.create(node, "transform", TextGeometricTransform(2.0f, 1.5f))!!) {
-            parameter(
-                "transform", ParameterType.String,
-                TextGeometricTransform::class.java.simpleName
-            ) {
-                parameter("scaleX", ParameterType.Float, 2.0f)
-                parameter("skewX", ParameterType.Float, 1.5f)
-            }
-        }
-    }
-
-    @Test
-    fun testTextIndent() {
-        assertThat(lookup(TextIndent.None)).isEqualTo(ParameterType.String to "None")
-
-        validate(factory.create(node, "textIndent", TextIndent(4.0.sp, 0.5.sp))!!) {
-            parameter("textIndent", ParameterType.String, "TextIndent") {
-                parameter("firstLine", ParameterType.DimensionSp, 4.0f)
-                parameter("restLine", ParameterType.DimensionSp, 0.5f)
-            }
-        }
-    }
-
-    @Test
-    fun testTextStyle() {
-        val style = TextStyle(
-            color = Color.Red,
-            textDecoration = TextDecoration.Underline
-        )
-        validate(factory.create(node, "style", style)!!) {
-            parameter("style", ParameterType.String, TextStyle::class.java.simpleName) {
-                parameter("background", ParameterType.String, "Unspecified")
-                parameter("color", ParameterType.Color, Color.Red.toArgb())
-                parameter("fontSize", ParameterType.String, "Unspecified")
-                parameter("letterSpacing", ParameterType.String, "Unspecified")
-                parameter("lineHeight", ParameterType.String, "Unspecified")
-                parameter("textDecoration", ParameterType.String, "Underline")
-            }
-        }
-    }
-
-    @Test
-    fun testTextUnit() {
-        assertThat(lookup(TextUnit.Unspecified)).isEqualTo(ParameterType.String to "Unspecified")
-        assertThat(lookup(12.0.sp)).isEqualTo(ParameterType.DimensionSp to 12.0f)
-        assertThat(lookup(2.0.em)).isEqualTo(ParameterType.DimensionEm to 2.0f)
-        assertThat(lookup(9.0f.sp)).isEqualTo(ParameterType.DimensionSp to 9.0f)
-        assertThat(lookup(10.sp)).isEqualTo(ParameterType.DimensionSp to 10.0f)
-        assertThat(lookup(26.0.sp)).isEqualTo(ParameterType.DimensionSp to 26.0f)
-        assertThat(lookup(2.0f.em)).isEqualTo(ParameterType.DimensionEm to 2.0f)
-        assertThat(lookup(1.em)).isEqualTo(ParameterType.DimensionEm to 1.0f)
-        assertThat(lookup(3.0.em)).isEqualTo(ParameterType.DimensionEm to 3.0f)
-    }
-
-    @Test
-    fun testVectorAssert() {
-        assertThat(lookup(Icons.Filled.Call)).isEqualTo(ParameterType.String to "Filled.Call")
-        assertThat(lookup(Icons.Rounded.Add)).isEqualTo(ParameterType.String to "Rounded.Add")
-    }
-
-    private fun lookup(value: Any): Pair<ParameterType, Any?>? {
-        val parameter = factory.create(node, "property", value) ?: return null
-        assertThat(parameter.elements).isEmpty()
-        return Pair(parameter.type, parameter.value)
-    }
-
-    private fun validate(
-        parameter: NodeParameter,
-        expected: ParameterValidationReceiver.() -> Unit = {}
-    ) {
-        val elements = ParameterValidationReceiver(listOf(parameter).listIterator())
-        elements.expected()
-    }
-}
-
-private class TestPainter(
-    val width: Float,
-    val height: Float
-) : Painter() {
-
-    var color = Color.Red
-
-    override val intrinsicSize: Size
-        get() = Size(width, height)
-
-    override fun applyLayoutDirection(layoutDirection: LayoutDirection): Boolean {
-        color = if (layoutDirection == LayoutDirection.Rtl) Color.Blue else Color.Red
-        return true
-    }
-
-    override fun DrawScope.onDraw() {
-        drawRect(color = color)
-    }
-}
-
-class ParameterValidationReceiver(val parameterIterator: Iterator<NodeParameter>) {
-    fun parameter(
-        name: String,
-        type: ParameterType,
-        value: Any?,
-        block: ParameterValidationReceiver.() -> Unit = {}
-    ) {
-        assertWithMessage("No such element found: $name").that(parameterIterator.hasNext()).isTrue()
-        val parameter = parameterIterator.next()
-        assertThat(parameter.name).isEqualTo(name)
-        assertWithMessage(name).that(parameter.type).isEqualTo(type)
-        if (type != ParameterType.Lambda || value != null) {
-            assertWithMessage(name).that(parameter.value).isEqualTo(value)
-        }
-        var elements: List<NodeParameter> = parameter.elements
-        if (name != "modifier") {
-            // Do not sort modifiers: the order is important
-            elements = elements.sortedBy { it.name }
-        }
-        val children = ParameterValidationReceiver(elements.listIterator())
-        children.block()
-        if (children.parameterIterator.hasNext()) {
-            val elementNames = mutableListOf<String>()
-            while (children.parameterIterator.hasNext()) {
-                elementNames.add(children.parameterIterator.next().name)
-            }
-            error("$name: has more elements like: ${elementNames.joinToString()}")
-        }
-    }
-}
-
-class MyClass {
-    var other: MyClass? = null
-}
diff --git a/compose/ui/ui-tooling/src/androidTest/java/androidx/compose/ui/tooling/inspector/SynthesizedLambdaNameTest.kt b/compose/ui/ui-tooling/src/androidTest/java/androidx/compose/ui/tooling/inspector/SynthesizedLambdaNameTest.kt
deleted file mode 100644
index 68c361e..0000000
--- a/compose/ui/ui-tooling/src/androidTest/java/androidx/compose/ui/tooling/inspector/SynthesizedLambdaNameTest.kt
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * Copyright 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.compose.ui.tooling.inspector
-
-import androidx.test.filters.SmallTest
-import com.google.common.truth.Truth.assertThat
-import org.junit.Test
-
-private val topLambda1 = {}
-private val topLambda2 = withArgument {}
-private val topLambda3 = withArguments({}, {})
-
-private fun withArgument(a: (Int) -> Unit = {}): (Int) -> Unit = a
-private fun withArguments(a1: () -> Unit = {}, a2: () -> Unit = {}): List<() -> Unit> =
-    listOf(a1, a2)
-
-/**
- * Test the compiler generated lambda names.
- *
- * There is code in Studio that relies on this format.
- * If this test should start to fail, please check the LambdaResolver in the Layout Inspector.
- */
-@SmallTest
-@Suppress("JoinDeclarationAndAssignment")
-class SynthesizedLambdaNameTest {
-    private val cls = SynthesizedLambdaNameTest::class.java.name
-    private val memberLambda1 = {}
-    private val memberLambda2 = withArgument {}
-    private val memberLambda3 = withArguments({}, {})
-    private val initLambda1: (Int) -> Unit
-    private val initLambda2: (Int) -> Unit
-    private val defaultLambda1 = withArgument()
-    private val defaultLambda2 = withArguments()
-
-    init {
-        initLambda1 = withArgument {}
-        initLambda2 = withArgument {}
-    }
-
-    @Test
-    fun testSynthesizedNames() {
-        assertThat(name(topLambda1)).isEqualTo("${cls}Kt\$topLambda1$1")
-        assertThat(name(topLambda2)).isEqualTo("${cls}Kt\$topLambda2$1")
-        assertThat(name(topLambda3[0])).isEqualTo("${cls}Kt\$topLambda3$1")
-        assertThat(name(topLambda3[1])).isEqualTo("${cls}Kt\$topLambda3$2")
-        assertThat(name(memberLambda1)).isEqualTo("$cls\$memberLambda1$1")
-        assertThat(name(memberLambda2)).isEqualTo("$cls\$memberLambda2$1")
-        assertThat(name(memberLambda3[0])).isEqualTo("$cls\$memberLambda3$1")
-        assertThat(name(memberLambda3[1])).isEqualTo("$cls\$memberLambda3$2")
-        assertThat(name(initLambda1)).isEqualTo("$cls$1")
-        assertThat(name(initLambda2)).isEqualTo("$cls$2")
-        assertThat(name(defaultLambda1)).isEqualTo("${cls}Kt\$withArgument$1")
-        assertThat(name(defaultLambda2[0])).isEqualTo("${cls}Kt\$withArguments$1")
-        assertThat(name(defaultLambda2[1])).isEqualTo("${cls}Kt\$withArguments$2")
-    }
-
-    private fun name(lambda: Any) = lambda.javaClass.name
-}
\ No newline at end of file
diff --git a/compose/ui/ui-tooling/src/main/java/androidx/compose/ui/tooling/inspector/InlineClassConverter.kt b/compose/ui/ui-tooling/src/main/java/androidx/compose/ui/tooling/inspector/InlineClassConverter.kt
deleted file mode 100644
index e80975f..0000000
--- a/compose/ui/ui-tooling/src/main/java/androidx/compose/ui/tooling/inspector/InlineClassConverter.kt
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * Copyright 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.compose.ui.tooling.inspector
-
-/**
- * Converter for casting a parameter represented by its primitive value to its inline class type.
- *
- * For example: an androidx.compose.ui.graphics.Color instance is often represented by a long
- */
-internal class InlineClassConverter {
-    // Map from inline type name to inline class and conversion lambda
-    private val typeMap = mutableMapOf<String, (Any) -> Any>()
-    // Return value used in functions
-    private val notInlineType: (Any) -> Any = { it }
-
-    /**
-     * Clear any cached data.
-     */
-    fun clear() {
-        typeMap.clear()
-    }
-
-    /**
-     * Cast the specified [value] to a value of type [inlineClassName] if possible.
-     *
-     * @param inlineClassName the fully qualified name of the inline class.
-     * @param value the value to convert to an instance of [inlineClassName].
-     */
-    fun castParameterValue(inlineClassName: String?, value: Any?): Any? =
-        if (value != null && inlineClassName != null)
-            typeMapperFor(inlineClassName)(value) else value
-
-    private fun typeMapperFor(typeName: String): (Any) -> (Any) =
-        typeMap.getOrPut(typeName) { loadTypeMapper(typeName.replace('.', '/')) }
-
-    private fun loadTypeMapper(className: String): (Any) -> Any {
-        val javaClass = loadClassOrNull(className) ?: return notInlineType
-        val create = javaClass.declaredConstructors.singleOrNull() ?: return notInlineType
-        create.isAccessible = true
-        return { value -> create.newInstance(value) }
-    }
-
-    private fun loadClassOrNull(className: String): Class<*>? =
-        try {
-            javaClass.classLoader!!.loadClass(className)
-        } catch (ex: Exception) {
-            null
-        }
-}
diff --git a/compose/ui/ui-tooling/src/main/java/androidx/compose/ui/tooling/inspector/InspectorNode.kt b/compose/ui/ui-tooling/src/main/java/androidx/compose/ui/tooling/inspector/InspectorNode.kt
deleted file mode 100644
index ae66150..0000000
--- a/compose/ui/ui-tooling/src/main/java/androidx/compose/ui/tooling/inspector/InspectorNode.kt
+++ /dev/null
@@ -1,178 +0,0 @@
-/*
- * Copyright 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.compose.ui.tooling.inspector
-
-import androidx.compose.ui.layout.LayoutInfo
-
-private val EmptyIntArray = IntArray(0)
-
-/**
- * Node representing a Composable for the Layout Inspector.
- */
-class InspectorNode internal constructor(
-    /**
-     * The associated render node id or 0.
-     */
-    val id: Long,
-
-    /**
-     * The name of the Composable.
-     */
-    val name: String,
-
-    /**
-     * The fileName where the Composable was called.
-     */
-    val fileName: String,
-
-    /**
-     * A hash of the package name to help disambiguate duplicate [fileName] values.
-     *
-     * This hash is calculated by,
-     *
-     *   `packageName.fold(0) { hash, current -> hash * 31 + current.toInt() }?.absoluteValue`
-     *
-     * where the package name is the dotted name of the package. This can be used to disambiguate
-     * which file is referenced by [fileName]. This number is -1 if there was no package hash
-     * information generated such as when the file does not contain a package declaration.
-     */
-    val packageHash: Int,
-
-    /**
-     * The line number where the Composable was called.
-     */
-    val lineNumber: Int,
-
-    /**
-     * The UTF-16 offset in the file where the Composable was called
-     */
-    val offset: Int,
-
-    /**
-     * The number of UTF-16 code point comprise the Composable call
-     */
-    val length: Int,
-
-    /**
-     * Left side of the Composable in pixels.
-     */
-    val left: Int,
-
-    /**
-     * Top of the Composable in pixels.
-     */
-    val top: Int,
-
-    /**
-     * Width of the Composable in pixels.
-     */
-    val width: Int,
-
-    /**
-     * Width of the Composable in pixels.
-     */
-    val height: Int,
-
-    /**
-     * The 4 corners of the node after modifier transformations.
-     * If there are no coordinate transforms the array will be empty.
-     * Otherwise the content will be 8 integers representing 4 (x,y) corners
-     * in clockwise order of the original, untransformed coordinates.
-     */
-    val bounds: IntArray,
-
-    /**
-     * The parameters of this Composable.
-     */
-    val parameters: List<RawParameter>,
-
-    /**
-     * The children nodes of this Composable.
-     */
-    val children: List<InspectorNode>
-)
-
-/**
- * Parameter definition with a raw value reference.
- */
-class RawParameter(val name: String, val value: Any?)
-
-/**
- * Mutable version of [InspectorNode].
- */
-internal class MutableInspectorNode {
-    var id = 0L
-    var layoutNodes = mutableListOf<LayoutInfo>()
-    var name = ""
-    var fileName = ""
-    var packageHash = -1
-    var lineNumber = 0
-    var offset = 0
-    var length = 0
-    var left = 0
-    var top = 0
-    var width = 0
-    var height = 0
-    var bounds = EmptyIntArray
-    val parameters = mutableListOf<RawParameter>()
-    val children = mutableListOf<InspectorNode>()
-
-    fun reset() {
-        markUnwanted()
-        id = 0L
-        left = 0
-        top = 0
-        width = 0
-        height = 0
-        layoutNodes.clear()
-        bounds = EmptyIntArray
-        children.clear()
-    }
-
-    fun markUnwanted() {
-        name = ""
-        fileName = ""
-        packageHash = -1
-        lineNumber = 0
-        offset = 0
-        length = 0
-        parameters.clear()
-    }
-
-    fun shallowCopy(node: InspectorNode): MutableInspectorNode = apply {
-        id = node.id
-        name = node.name
-        fileName = node.fileName
-        packageHash = node.packageHash
-        lineNumber = node.lineNumber
-        offset = node.offset
-        length = node.length
-        left = node.left
-        top = node.top
-        width = node.width
-        height = node.height
-        bounds = node.bounds
-        parameters.addAll(node.parameters)
-        children.addAll(node.children)
-    }
-
-    fun build(): InspectorNode =
-        InspectorNode(
-            id, name, fileName, packageHash, lineNumber, offset, length, left, top, width, height,
-            bounds, parameters.toList(), children.toList()
-        )
-}
diff --git a/compose/ui/ui-tooling/src/main/java/androidx/compose/ui/tooling/inspector/LayoutInspectorTree.kt b/compose/ui/ui-tooling/src/main/java/androidx/compose/ui/tooling/inspector/LayoutInspectorTree.kt
deleted file mode 100644
index 85f0933..0000000
--- a/compose/ui/ui-tooling/src/main/java/androidx/compose/ui/tooling/inspector/LayoutInspectorTree.kt
+++ /dev/null
@@ -1,439 +0,0 @@
-/*
- * Copyright 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.compose.ui.tooling.inspector
-
-import android.view.View
-import androidx.compose.runtime.tooling.CompositionData
-import androidx.compose.runtime.InternalComposeApi
-import androidx.compose.ui.geometry.Offset
-import androidx.compose.ui.layout.GraphicLayerInfo
-import androidx.compose.ui.layout.LayoutInfo
-import androidx.compose.ui.tooling.R
-import androidx.compose.ui.tooling.data.Group
-import androidx.compose.ui.tooling.data.NodeGroup
-import androidx.compose.ui.tooling.data.ParameterInformation
-import androidx.compose.ui.tooling.data.UiToolingDataApi
-import androidx.compose.ui.tooling.data.asTree
-import androidx.compose.ui.unit.Density
-import androidx.compose.ui.unit.IntOffset
-import androidx.compose.ui.unit.toSize
-import java.util.ArrayDeque
-import java.util.Collections
-import java.util.IdentityHashMap
-import kotlin.math.absoluteValue
-import kotlin.math.roundToInt
-
-private val systemPackages = setOf(
-    -1,
-    packageNameHash("androidx.compose.animation"),
-    packageNameHash("androidx.compose.animation.core"),
-    packageNameHash("androidx.compose.desktop"),
-    packageNameHash("androidx.compose.foundation"),
-    packageNameHash("androidx.compose.foundation.layout"),
-    packageNameHash("androidx.compose.foundation.text"),
-    packageNameHash("androidx.compose.material"),
-    packageNameHash("androidx.compose.material.ripple"),
-    packageNameHash("androidx.compose.runtime"),
-    packageNameHash("androidx.compose.ui"),
-    packageNameHash("androidx.compose.ui.layout"),
-    packageNameHash("androidx.compose.ui.platform"),
-    packageNameHash("androidx.compose.ui.tooling"),
-    packageNameHash("androidx.compose.ui.selection"),
-    packageNameHash("androidx.compose.ui.semantics"),
-    packageNameHash("androidx.compose.ui.viewinterop"),
-    packageNameHash("androidx.compose.ui.window"),
-)
-
-private val unwantedCalls = setOf(
-    "emit",
-    "remember",
-    "CompositionLocalProvider",
-    "Content",
-    "Inspectable",
-    "ProvideAndroidCompositionLocals",
-    "ProvideCommonCompositionLocals",
-)
-
-private fun packageNameHash(packageName: String) =
-    packageName.fold(0) { hash, char -> hash * 31 + char.toInt() }.absoluteValue
-
-/**
- * Generator of a tree for the Layout Inspector.
- */
-class LayoutInspectorTree {
-    @Suppress("MemberVisibilityCanBePrivate")
-    var hideSystemNodes = true
-    private val inlineClassConverter = InlineClassConverter()
-    private val parameterFactory = ParameterFactory(inlineClassConverter)
-    private val cache = ArrayDeque<MutableInspectorNode>()
-    private var generatedId = -1L
-    /** Map from [LayoutInfo] to the nearest [InspectorNode] that contains it */
-    private val claimedNodes = IdentityHashMap<LayoutInfo, InspectorNode>()
-    /** Map from parent tree to child trees that are about to be stitched together */
-    private val treeMap = IdentityHashMap<MutableInspectorNode, MutableList<MutableInspectorNode>>()
-    /** Map from owner node to child trees that are about to be stitched to this owner */
-    private val ownerMap = IdentityHashMap<InspectorNode, MutableList<MutableInspectorNode>>()
-    /** Set of tree nodes that were stitched into another tree */
-    private val stitched =
-        Collections.newSetFromMap(IdentityHashMap<MutableInspectorNode, Boolean>())
-
-    /**
-     * Converts the [CompositionData] set held by [view] into a list of root nodes.
-     */
-    @OptIn(InternalComposeApi::class)
-    fun convert(view: View): List<InspectorNode> {
-        parameterFactory.density = Density(view.context)
-        @Suppress("UNCHECKED_CAST")
-        val tables = view.getTag(R.id.inspection_slot_table_set) as? Set<CompositionData>
-            ?: return emptyList()
-        clear()
-        val result = convert(tables)
-        clear()
-        return result
-    }
-
-    /**
-     * Converts the [RawParameter]s of the [node] into displayable parameters.
-     */
-    fun convertParameters(node: InspectorNode): List<NodeParameter> {
-        return node.parameters.mapNotNull { parameterFactory.create(node, it.name, it.value) }
-    }
-
-    /**
-     * Reset the generated id. Nodes are assigned an id if there isn't a layout node id present.
-     */
-    @Suppress("unused")
-    fun resetGeneratedId() {
-        generatedId = -1L
-    }
-
-    private fun clear() {
-        cache.clear()
-        inlineClassConverter.clear()
-        claimedNodes.clear()
-        treeMap.clear()
-        ownerMap.clear()
-        stitched.clear()
-    }
-
-    @OptIn(InternalComposeApi::class)
-    private fun convert(tables: Set<CompositionData>): List<InspectorNode> {
-        val trees = tables.map { convert(it) }
-        return when (trees.size) {
-            0 -> listOf()
-            1 -> addTree(mutableListOf(), trees.single())
-            else -> stitchTreesByLayoutInfo(trees)
-        }
-    }
-
-    /**
-     * Stitch separate trees together using the [LayoutInfo]s found in the [CompositionData]s.
-     *
-     * Some constructs in Compose (e.g. ModalDrawer) will result is multiple
-     * [CompositionData]s. This code will attempt to stitch the resulting [InspectorNode] trees
-     * together by looking at the parent of each [LayoutInfo].
-     *
-     * If this algorithm is successful the result of this function will be a list with a single
-     * tree.
-     */
-    private fun stitchTreesByLayoutInfo(trees: List<MutableInspectorNode>): List<InspectorNode> {
-        val layoutToTreeMap = IdentityHashMap<LayoutInfo, MutableInspectorNode>()
-        trees.forEach { tree -> tree.layoutNodes.forEach { layoutToTreeMap[it] = tree } }
-        trees.forEach { tree ->
-            val layout = tree.layoutNodes.lastOrNull()
-            val parentLayout = generateSequence(layout) { it.parentInfo }.firstOrNull {
-                val otherTree = layoutToTreeMap[it]
-                otherTree != null && otherTree != tree
-            }
-            if (parentLayout != null) {
-                val ownerNode = claimedNodes[parentLayout]
-                val ownerTree = layoutToTreeMap[parentLayout]
-                if (ownerNode != null && ownerTree != null) {
-                    ownerMap.getOrPut(ownerNode) { mutableListOf() }.add(tree)
-                    treeMap.getOrPut(ownerTree) { mutableListOf() }.add(tree)
-                }
-            }
-        }
-        var parentTree = findDeepParentTree()
-        while (parentTree != null) {
-            addSubTrees(parentTree)
-            treeMap.remove(parentTree)
-            parentTree = findDeepParentTree()
-        }
-        val result = mutableListOf<InspectorNode>()
-        trees.asSequence().filter { !stitched.contains(it) }.forEach { addTree(result, it) }
-        return result
-    }
-
-    /**
-     * Return a parent tree where the children trees (to be stitched under the parent) are not
-     * a parent themselves. Do this to avoid rebuilding the same tree more than once.
-     */
-    private fun findDeepParentTree(): MutableInspectorNode? =
-        treeMap.entries.asSequence()
-            .filter { (_, children) -> children.none { treeMap.containsKey(it) } }
-            .firstOrNull()?.key
-
-    private fun addSubTrees(tree: MutableInspectorNode) {
-        for ((index, child) in tree.children.withIndex()) {
-            tree.children[index] = addSubTrees(child) ?: child
-        }
-    }
-
-    /**
-     * Rebuild [node] with any possible sub trees added (stitched in).
-     * Return the rebuild node, or null if no changes were found in this node or its children.
-     * Lazily allocate the new node to avoid unnecessary allocations.
-     */
-    private fun addSubTrees(node: InspectorNode): InspectorNode? {
-        var newNode: MutableInspectorNode? = null
-        for ((index, child) in node.children.withIndex()) {
-            val newChild = addSubTrees(child)
-            if (newChild != null) {
-                val newCopy = newNode ?: newNode(node)
-                newCopy.children[index] = newChild
-                newNode = newCopy
-            }
-        }
-        val trees = ownerMap[node]
-        if (trees == null && newNode == null) {
-            return null
-        }
-        val newCopy = newNode ?: newNode(node)
-        if (trees != null) {
-            trees.forEach { addTree(newCopy.children, it) }
-            stitched.addAll(trees)
-        }
-        return buildAndRelease(newCopy)
-    }
-
-    /**
-     * Add [tree] to the end of the [out] list.
-     * The root nodes of [tree] may be a fake node that hold a list of [LayoutInfo].
-     */
-    private fun addTree(
-        out: MutableList<InspectorNode>,
-        tree: MutableInspectorNode
-    ): List<InspectorNode> {
-        tree.children.forEach {
-            if (it.name.isNotEmpty()) {
-                out.add(it)
-            } else {
-                out.addAll(it.children)
-            }
-        }
-        return out
-    }
-
-    @OptIn(InternalComposeApi::class, UiToolingDataApi::class)
-    private fun convert(table: CompositionData): MutableInspectorNode {
-        val fakeParent = newNode()
-        addToParent(fakeParent, listOf(convert(table.asTree())), buildFakeChildNodes = true)
-        return fakeParent
-    }
-
-    @OptIn(UiToolingDataApi::class)
-    private fun convert(group: Group): MutableInspectorNode {
-        val children = convertChildren(group)
-        val parent = parse(group)
-        addToParent(parent, children)
-        return parent
-    }
-
-    @OptIn(UiToolingDataApi::class)
-    private fun convertChildren(group: Group): List<MutableInspectorNode> {
-        if (group.children.isEmpty()) {
-            return emptyList()
-        }
-        val result = mutableListOf<MutableInspectorNode>()
-        for (child in group.children) {
-            val node = convert(child)
-            if (node.name.isNotEmpty() || node.children.isNotEmpty() ||
-                node.id != 0L || node.layoutNodes.isNotEmpty()
-            ) {
-                result.add(node)
-            } else {
-                release(node)
-            }
-        }
-        return result
-    }
-
-    /**
-     * Adds the nodes in [input] to the children of [parentNode].
-     * Nodes without a reference to a wanted Composable are skipped unless [buildFakeChildNodes].
-     * A single skipped render id and layoutNode will be added to [parentNode].
-     */
-    private fun addToParent(
-        parentNode: MutableInspectorNode,
-        input: List<MutableInspectorNode>,
-        buildFakeChildNodes: Boolean = false
-    ) {
-        var id: Long? = null
-        input.forEach { node ->
-            if (node.name.isEmpty() && !(buildFakeChildNodes && node.layoutNodes.isNotEmpty())) {
-                parentNode.children.addAll(node.children)
-                if (node.id != 0L) {
-                    // If multiple siblings with a render ids are dropped:
-                    // Ignore them all. And delegate the drawing to a parent in the inspector.
-                    id = if (id == null) node.id else 0L
-                }
-            } else {
-                node.id = if (node.id != 0L) node.id else --generatedId
-                val resultNode = node.build()
-                // TODO: replace getOrPut with putIfAbsent which requires API level 24
-                node.layoutNodes.forEach { claimedNodes.getOrPut(it) { resultNode } }
-                parentNode.children.add(resultNode)
-            }
-            if (node.bounds.isNotEmpty() && sameBoundingRectangle(parentNode, node)) {
-                parentNode.bounds = node.bounds
-            }
-            parentNode.layoutNodes.addAll(node.layoutNodes)
-            release(node)
-        }
-        val nodeId = id
-        parentNode.id = if (parentNode.id == 0L && nodeId != null) nodeId else parentNode.id
-    }
-
-    @OptIn(UiToolingDataApi::class)
-    private fun parse(group: Group): MutableInspectorNode {
-        val node = newNode()
-        node.id = getRenderNode(group)
-        parsePosition(group, node)
-        parseLayoutInfo(group, node)
-        if (node.height <= 0 && node.width <= 0) {
-            return markUnwanted(node)
-        }
-        if (!parseCallLocation(group, node) && group.name.isNullOrEmpty()) {
-            return markUnwanted(node)
-        }
-        group.name?.let { node.name = it }
-        if (unwantedGroup(node)) {
-            return markUnwanted(node)
-        }
-        addParameters(group.parameters, node)
-        return node
-    }
-
-    @OptIn(UiToolingDataApi::class)
-    private fun parsePosition(group: Group, node: MutableInspectorNode) {
-        val box = group.box
-        node.top = box.top
-        node.left = box.left
-        node.height = box.bottom - box.top
-        node.width = box.right - box.left
-    }
-
-    @OptIn(UiToolingDataApi::class)
-    private fun parseLayoutInfo(group: Group, node: MutableInspectorNode) {
-        val layoutInfo = (group as? NodeGroup)?.node as? LayoutInfo ?: return
-        node.layoutNodes.add(layoutInfo)
-        val box = group.box
-        val size = box.size.toSize()
-        val coordinates = layoutInfo.coordinates
-        val topLeft = toIntOffset(coordinates.localToWindow(Offset.Zero))
-        val topRight = toIntOffset(coordinates.localToWindow(Offset(size.width, 0f)))
-        val bottomRight = toIntOffset(coordinates.localToWindow(Offset(size.width, size.height)))
-        val bottomLeft = toIntOffset(coordinates.localToWindow(Offset(0f, size.height)))
-        if (
-            topLeft.x == box.left && topLeft.y == box.top &&
-            topRight.x == box.right && topRight.y == box.top &&
-            bottomRight.x == box.right && bottomRight.y == box.bottom &&
-            bottomLeft.x == box.left && bottomLeft.y == box.bottom
-        ) {
-            return
-        }
-        node.bounds = intArrayOf(
-            topLeft.x, topLeft.y,
-            topRight.x, topRight.y,
-            bottomRight.x, bottomRight.y,
-            bottomLeft.x, bottomLeft.y
-        )
-    }
-
-    private fun toIntOffset(offset: Offset): IntOffset =
-        IntOffset(offset.x.roundToInt(), offset.y.roundToInt())
-
-    private fun markUnwanted(node: MutableInspectorNode): MutableInspectorNode =
-        node.apply { markUnwanted() }
-
-    @OptIn(UiToolingDataApi::class)
-    private fun parseCallLocation(group: Group, node: MutableInspectorNode): Boolean {
-        val location = group.location ?: return false
-        val fileName = location.sourceFile ?: return false
-        node.fileName = fileName
-        node.packageHash = location.packageHash
-        node.lineNumber = location.lineNumber
-        node.offset = location.offset
-        node.length = location.length
-        return true
-    }
-
-    @OptIn(UiToolingDataApi::class)
-    private fun getRenderNode(group: Group): Long =
-        group.modifierInfo.asSequence()
-            .map { it.extra }
-            .filterIsInstance<GraphicLayerInfo>()
-            .map { it.layerId }
-            .firstOrNull() ?: 0
-
-    @OptIn(UiToolingDataApi::class)
-    private fun addParameters(parameters: List<ParameterInformation>, node: MutableInspectorNode) =
-        parameters.forEach { addParameter(it, node) }
-
-    @OptIn(UiToolingDataApi::class)
-    private fun addParameter(parameter: ParameterInformation, node: MutableInspectorNode) {
-        val castedValue = castValue(parameter)
-        node.parameters.add(RawParameter(parameter.name, castedValue))
-    }
-
-    @OptIn(UiToolingDataApi::class)
-    private fun castValue(parameter: ParameterInformation): Any? {
-        val value = parameter.value ?: return null
-        if (parameter.inlineClass == null) return value
-        return inlineClassConverter.castParameterValue(parameter.inlineClass, value)
-    }
-
-    private fun unwantedGroup(node: MutableInspectorNode): Boolean =
-        node.packageHash in systemPackages && (hideSystemNodes || node.name in unwantedCalls)
-
-    private fun newNode(): MutableInspectorNode =
-        if (cache.isNotEmpty()) cache.pop() else MutableInspectorNode()
-
-    private fun newNode(copyFrom: InspectorNode): MutableInspectorNode =
-        newNode().shallowCopy(copyFrom)
-
-    private fun release(node: MutableInspectorNode) {
-        node.reset()
-        cache.add(node)
-    }
-
-    private fun buildAndRelease(node: MutableInspectorNode): InspectorNode {
-        val result = node.build()
-        release(node)
-        return result
-    }
-
-    private fun sameBoundingRectangle(
-        node1: MutableInspectorNode,
-        node2: MutableInspectorNode
-    ): Boolean =
-        node1.left == node2.left &&
-            node1.top == node2.top &&
-            node1.width == node2.width &&
-            node1.height == node2.height
-}
diff --git a/compose/ui/ui-tooling/src/main/java/androidx/compose/ui/tooling/inspector/NodeParameter.kt b/compose/ui/ui-tooling/src/main/java/androidx/compose/ui/tooling/inspector/NodeParameter.kt
deleted file mode 100644
index 86bd7ad..0000000
--- a/compose/ui/ui-tooling/src/main/java/androidx/compose/ui/tooling/inspector/NodeParameter.kt
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * Copyright 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.compose.ui.tooling.inspector
-
-/**
- * Holds data representing a Composable parameter for the Layout Inspector.
- */
-class NodeParameter internal constructor(
-    /**
-     * The name of the parameter.
-     */
-    val name: String,
-
-    /**
-     * The type of the parameter.
-     */
-    val type: ParameterType,
-
-    /**
-     * The value of the parameter.
-     */
-    val value: Any?
-) {
-    /**
-     * Sub elements of the parameter.
-     */
-    val elements = mutableListOf<NodeParameter>()
-}
-
-/**
- * The type of a parameter.
- */
-enum class ParameterType {
-    String,
-    Boolean,
-    Double,
-    Float,
-    Int32,
-    Int64,
-    Color,
-    Resource,
-    DimensionDp,
-    DimensionSp,
-    DimensionEm,
-    Lambda,
-    FunctionReference,
-}
diff --git a/compose/ui/ui-tooling/src/main/java/androidx/compose/ui/tooling/inspector/ParameterFactory.kt b/compose/ui/ui-tooling/src/main/java/androidx/compose/ui/tooling/inspector/ParameterFactory.kt
deleted file mode 100644
index 0e935b5..0000000
--- a/compose/ui/ui-tooling/src/main/java/androidx/compose/ui/tooling/inspector/ParameterFactory.kt
+++ /dev/null
@@ -1,481 +0,0 @@
-/*
- * Copyright 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.compose.ui.tooling.inspector
-
-import android.util.Log
-import android.view.View
-import androidx.compose.foundation.shape.CornerSize
-import androidx.compose.runtime.internal.ComposableLambda
-import androidx.compose.ui.AbsoluteAlignment
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.geometry.Offset
-import androidx.compose.ui.geometry.Size
-import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.graphics.RectangleShape
-import androidx.compose.ui.graphics.Shadow
-import androidx.compose.ui.graphics.SolidColor
-import androidx.compose.ui.graphics.toArgb
-import androidx.compose.ui.graphics.vector.ImageVector
-import androidx.compose.ui.platform.InspectableValue
-import androidx.compose.ui.text.AnnotatedString
-import androidx.compose.ui.text.font.FontListFontFamily
-import androidx.compose.ui.text.font.FontStyle
-import androidx.compose.ui.text.font.FontWeight
-import androidx.compose.ui.text.font.ResourceFont
-import androidx.compose.ui.text.intl.Locale
-import androidx.compose.ui.text.intl.LocaleList
-import androidx.compose.ui.text.style.BaselineShift
-import androidx.compose.ui.text.style.TextDecoration
-import androidx.compose.ui.tooling.inspector.ParameterType.DimensionDp
-import androidx.compose.ui.unit.Density
-import androidx.compose.ui.unit.Dp
-import androidx.compose.ui.unit.TextUnit
-import androidx.compose.ui.unit.TextUnitType
-import java.lang.reflect.Field
-import kotlin.jvm.internal.FunctionReference
-import kotlin.jvm.internal.Lambda
-import kotlin.math.abs
-import kotlin.reflect.KClass
-import kotlin.reflect.KProperty1
-import kotlin.reflect.full.allSuperclasses
-import kotlin.reflect.full.declaredMemberProperties
-import kotlin.reflect.jvm.isAccessible
-import kotlin.reflect.jvm.javaField
-import kotlin.reflect.jvm.javaGetter
-import java.lang.reflect.Modifier as JavaModifier
-
-private const val MAX_RECURSIONS = 10
-private const val MAX_ITERABLE = 25
-
-/**
- * Factory of [NodeParameter]s.
- *
- * Each parameter value is converted to a user readable value.
- */
-internal class ParameterFactory(private val inlineClassConverter: InlineClassConverter) {
-    /**
-     * A map from known values to a user readable string representation.
-     */
-    private val valueLookup = mutableMapOf<Any, String>()
-
-    /**
-     * The classes we have loaded constants from.
-     */
-    private val valuesLoaded = mutableSetOf<Class<*>>()
-
-    /**
-     * Do not load constant names from instances of these classes.
-     * We prefer showing the raw values of Color and Dimensions.
-     */
-    private val ignoredClasses = listOf(Color::class.java, Dp::class.java)
-    private var creatorCache: ParameterCreator? = null
-    private val kotlinReflectionSupported = try {
-        Class.forName("kotlin.reflect.full.KClasses")
-        true
-    } catch (ex: Exception) {
-        false
-    }
-
-    /**
-     * Do not decompose instances or lookup constants from these package prefixes
-     *
-     * The following instances are known to contain self recursion:
-     * - kotlinx.coroutines.flow.StateFlowImpl
-     * - androidx.compose.ui.node.LayoutNode
-     */
-    private val ignoredPackagePrefixes = listOf(
-        "android.", "java.", "javax.", "kotlinx.", "androidx.compose.ui.node."
-    )
-
-    var density = Density(1.0f)
-
-    init {
-        val textDecorationCombination = TextDecoration.combine(
-            listOf(TextDecoration.LineThrough, TextDecoration.Underline)
-        )
-        valueLookup[textDecorationCombination] = "LineThrough+Underline"
-        valueLookup[Color.Unspecified] = "Unspecified"
-        valueLookup[RectangleShape] = "RectangleShape"
-        valuesLoaded.add(Enum::class.java)
-        valuesLoaded.add(Any::class.java)
-
-        // AbsoluteAlignment is not found from an instance of BiasAbsoluteAlignment,
-        // because Alignment has no file level class.
-        loadConstantsFromEnclosedClasses(AbsoluteAlignment::class.java)
-    }
-
-    /**
-     * Create a [NodeParameter] from the specified parameter [name] and [value].
-     *
-     * Attempt to convert the value to a user readable value.
-     * For now: return null when a conversion is not possible/found.
-     */
-    fun create(node: InspectorNode, name: String, value: Any?): NodeParameter? {
-        val creator = creatorCache ?: ParameterCreator()
-        try {
-            return creator.create(node, name, value)
-        } finally {
-            creatorCache = creator
-        }
-    }
-
-    private fun loadConstantsFrom(javaClass: Class<*>) {
-        if (valuesLoaded.contains(javaClass) ||
-            ignoredPackagePrefixes.any { javaClass.name.startsWith(it) }
-        ) {
-            return
-        }
-        val related = generateSequence(javaClass) { it.superclass }.plus(javaClass.interfaces)
-        related.forEach { aClass ->
-            val topClass = generateSequence(aClass) { safeEnclosingClass(it) }.last()
-            loadConstantsFromEnclosedClasses(topClass)
-            findPackageLevelClass(topClass)?.let { loadConstantsFromStaticFinal(it) }
-        }
-    }
-
-    private fun safeEnclosingClass(klass: Class<*>): Class<*>? = try {
-        klass.enclosingClass
-    } catch (_: Error) {
-        // Exceptions seen on API 23...
-        null
-    }
-
-    private fun findPackageLevelClass(javaClass: Class<*>): Class<*>? = try {
-        // Note: This doesn't work when @file.JvmName is specified
-        Class.forName("${javaClass.name}Kt")
-    } catch (ex: Throwable) {
-        null
-    }
-
-    private fun loadConstantsFromEnclosedClasses(javaClass: Class<*>) {
-        if (valuesLoaded.contains(javaClass)) {
-            return
-        }
-        loadConstantsFromObjectInstance(javaClass.kotlin)
-        loadConstantsFromStaticFinal(javaClass)
-        valuesLoaded.add(javaClass)
-        javaClass.declaredClasses.forEach { loadConstantsFromEnclosedClasses(it) }
-    }
-
-    /**
-     * Load all constants from companion objects and singletons
-     *
-     * Exclude: primary types and types of ignoredClasses, open and lateinit vals.
-     */
-    private fun loadConstantsFromObjectInstance(kClass: KClass<*>) {
-        try {
-            val instance = kClass.objectInstance ?: return
-            kClass.declaredMemberProperties.asSequence()
-                .filter { it.isFinal && !it.isLateinit }
-                .mapNotNull { constantValueOf(it, instance)?.let { key -> Pair(key, it.name) } }
-                .filter { !ignoredValue(it.first) }
-                .toMap(valueLookup)
-        } catch (_: Throwable) {
-            // KT-16479 :  kotlin reflection does currently not support packages and files.
-            // We load top level values using Java reflection instead.
-            // Ignore other reflection errors as well
-        }
-    }
-
-    /**
-     * Load all constants from top level values from Java.
-     *
-     * Exclude: primary types and types of ignoredClasses.
-     * Since this is Java, inline types will also (unfortunately) be excluded.
-     */
-    private fun loadConstantsFromStaticFinal(javaClass: Class<*>) {
-        try {
-            javaClass.declaredMethods.asSequence()
-                .filter {
-                    it.returnType != Void.TYPE &&
-                        JavaModifier.isStatic(it.modifiers) &&
-                        JavaModifier.isFinal(it.modifiers) &&
-                        !it.returnType.isPrimitive &&
-                        it.parameterTypes.isEmpty() &&
-                        it.name.startsWith("get")
-                }
-                .mapNotNull { javaClass.getDeclaredField(it.name.substring(3)) }
-                .mapNotNull { constantValueOf(it)?.let { key -> Pair(key, it.name) } }
-                .filter { !ignoredValue(it.first) }
-                .toMap(valueLookup)
-        } catch (_: ReflectiveOperationException) {
-            // ignore reflection errors
-        } catch (_: NoClassDefFoundError) {
-            // ignore missing classes on lower level SDKs
-        }
-    }
-
-    private fun constantValueOf(field: Field?): Any? = try {
-        field?.isAccessible = true
-        field?.get(null)
-    } catch (_: ReflectiveOperationException) {
-        // ignore reflection errors
-        null
-    }
-
-    private fun constantValueOf(property: KProperty1<out Any, *>, instance: Any): Any? = try {
-        val field = property.javaField
-        field?.isAccessible = true
-        inlineClassConverter.castParameterValue(inlineResultClass(property), field?.get(instance))
-    } catch (_: ReflectiveOperationException) {
-        // ignore reflection errors
-        null
-    }
-
-    private fun inlineResultClass(property: KProperty1<out Any, *>): String? {
-        // The Java getter name will be mangled if it contains parameters of an inline class.
-        // The mangled part starts with a '-'.
-        if (property.javaGetter?.name?.contains('-') == true) {
-            return property.returnType.toString()
-        }
-        return null
-    }
-
-    private fun ignoredValue(value: Any?): Boolean =
-        value == null ||
-            ignoredClasses.any { ignored -> ignored.isInstance(value) } ||
-            value::class.java.isPrimitive
-
-    /**
-     * Convenience class for building [NodeParameter]s.
-     */
-    private inner class ParameterCreator {
-        private var node: InspectorNode? = null
-        private var recursions = 0
-
-        fun create(node: InspectorNode, name: String, value: Any?): NodeParameter? = try {
-            this.node = node
-            recursions = 0
-            create(name, value)
-        } finally {
-            this.node = null
-        }
-
-        private fun create(name: String, value: Any?): NodeParameter? {
-            if (value == null || recursions >= MAX_RECURSIONS) {
-                return null
-            }
-            try {
-                recursions++
-                createFromConstant(name, value)?.let { return it }
-                return when (value) {
-                    is AnnotatedString -> NodeParameter(name, ParameterType.String, value.text)
-                    is BaselineShift -> createFromBaselineShift(name, value)
-                    is Boolean -> NodeParameter(name, ParameterType.Boolean, value)
-                    is ComposableLambda -> createFromCLambda(name, value)
-                    is Color -> NodeParameter(name, ParameterType.Color, value.toArgb())
-                    is CornerSize -> createFromCornerSize(name, value)
-                    is Double -> NodeParameter(name, ParameterType.Double, value)
-                    is Dp -> NodeParameter(name, DimensionDp, value.value)
-                    is Enum<*> -> NodeParameter(name, ParameterType.String, value.toString())
-                    is Float -> NodeParameter(name, ParameterType.Float, value)
-                    is FunctionReference -> NodeParameter(
-                        name, ParameterType.FunctionReference, arrayOf<Any>(value, value.name)
-                    )
-                    is FontListFontFamily -> createFromFontListFamily(name, value)
-                    is FontWeight -> NodeParameter(name, ParameterType.Int32, value.weight)
-                    is Modifier -> createFromModifier(name, value)
-                    is InspectableValue -> createFromInspectableValue(name, value)
-                    is Int -> NodeParameter(name, ParameterType.Int32, value)
-                    is Iterable<*> -> createFromIterable(name, value)
-                    is Lambda<*> -> createFromLambda(name, value)
-                    is Locale -> NodeParameter(name, ParameterType.String, value.toString())
-                    is LocaleList ->
-                        NodeParameter(name, ParameterType.String, value.localeList.joinToString())
-                    is Long -> NodeParameter(name, ParameterType.Int64, value)
-                    is Offset -> createFromOffset(name, value)
-                    is Shadow -> createFromShadow(name, value)
-                    is SolidColor -> NodeParameter(name, ParameterType.Color, value.value.toArgb())
-                    is String -> NodeParameter(name, ParameterType.String, value)
-                    is TextUnit -> createFromTextUnit(name, value)
-                    is ImageVector -> createFromImageVector(name, value)
-                    is View -> NodeParameter(name, ParameterType.String, value.javaClass.simpleName)
-                    else -> createFromKotlinReflection(name, value)
-                }
-            } finally {
-                recursions--
-            }
-        }
-
-        private fun createFromBaselineShift(name: String, value: BaselineShift): NodeParameter {
-            val converted = when (value.multiplier) {
-                BaselineShift.None.multiplier -> "None"
-                BaselineShift.Subscript.multiplier -> "Subscript"
-                BaselineShift.Superscript.multiplier -> "Superscript"
-                else -> return NodeParameter(name, ParameterType.Float, value.multiplier)
-            }
-            return NodeParameter(name, ParameterType.String, converted)
-        }
-
-        private fun createFromCLambda(name: String, value: ComposableLambda): NodeParameter? = try {
-            val lambda = value.javaClass.getDeclaredField("_block")
-                .apply { isAccessible = true }
-                .get(value)
-            NodeParameter(name, ParameterType.Lambda, arrayOf<Any?>(lambda))
-        } catch (_: Throwable) {
-            null
-        }
-
-        private fun createFromConstant(name: String, value: Any): NodeParameter? {
-            if (!kotlinReflectionSupported) {
-                return null
-            }
-            loadConstantsFrom(value.javaClass)
-            return valueLookup[value]?.let { NodeParameter(name, ParameterType.String, it) }
-        }
-
-        private fun createFromCornerSize(name: String, value: CornerSize): NodeParameter {
-            val size = Size(node!!.width.toFloat(), node!!.height.toFloat())
-            val pixels = value.toPx(size, density)
-            return NodeParameter(name, DimensionDp, with(density) { pixels.toDp().value })
-        }
-
-        // For now: select ResourceFontFont closest to W400 and Normal, and return the resId
-        private fun createFromFontListFamily(
-            name: String,
-            value: FontListFontFamily
-        ): NodeParameter? =
-            findBestResourceFont(value)?.let {
-                NodeParameter(name, ParameterType.Resource, it.resId)
-            }
-
-        private fun createFromKotlinReflection(name: String, value: Any): NodeParameter? {
-            val kClass = value::class
-            val qualifiedName = kClass.qualifiedName
-            if (kClass.simpleName == null ||
-                qualifiedName == null ||
-                ignoredPackagePrefixes.any { qualifiedName.startsWith(it) } ||
-                !kotlinReflectionSupported
-            ) {
-                // Exit without creating a parameter for:
-                // - internal synthetic classes
-                // - certain android packages
-                // - if kotlin reflection library not available
-                return null
-            }
-            val parameter = NodeParameter(name, ParameterType.String, kClass.simpleName)
-            val properties = mutableMapOf<String, KProperty1<Any, *>>()
-            try {
-                sequenceOf(kClass).plus(kClass.allSuperclasses.asSequence())
-                    .flatMap { it.declaredMemberProperties.asSequence() }
-                    .filterIsInstance<KProperty1<Any, *>>()
-                    .associateByTo(properties) { it.name }
-            } catch (ex: Throwable) {
-                Log.w("Compose", "Could not decompose ${kClass.simpleName}")
-                return parameter
-            }
-            properties.values.mapNotNullTo(parameter.elements) {
-                create(it.name, valueOf(it, value))
-            }
-            return parameter
-        }
-
-        private fun valueOf(property: KProperty1<Any, *>, instance: Any): Any? = try {
-            property.isAccessible = true
-            // Bug in kotlin reflection API: if the type is a nullable inline type with a null
-            // value, we get an IllegalArgumentException in this line:
-            property.get(instance)
-        } catch (ex: Throwable) {
-            // TODO: Remove this warning since this is expected with nullable inline types
-            Log.w("Compose", "Could not get value of ${property.name}")
-            null
-        }
-
-        private fun createFromInspectableValue(
-            name: String,
-            value: InspectableValue
-        ): NodeParameter {
-            val tempValue = value.valueOverride ?: ""
-            val parameterName = name.ifEmpty { value.nameFallback } ?: "element"
-            val parameterValue = if (tempValue is InspectableValue) "" else tempValue
-            val parameter = create(parameterName, parameterValue)
-                ?: NodeParameter(parameterName, ParameterType.String, "")
-            val elements = parameter.elements
-            value.inspectableElements.mapNotNullTo(elements) { create(it.name, it.value) }
-            return parameter
-        }
-
-        private fun createFromIterable(name: String, value: Iterable<*>): NodeParameter {
-            val parameter = NodeParameter(name, ParameterType.String, "")
-            val elements = parameter.elements
-            value.asSequence()
-                .mapNotNull { create(elements.size.toString(), it) }
-                .takeWhile { elements.size < MAX_ITERABLE }
-                .toCollection(elements)
-            return parameter
-        }
-
-        private fun createFromLambda(name: String, value: Lambda<*>): NodeParameter =
-            NodeParameter(name, ParameterType.Lambda, arrayOf<Any>(value))
-
-        private fun createFromModifier(name: String, value: Modifier): NodeParameter? =
-            when {
-                name.isNotEmpty() -> {
-                    val parameter = NodeParameter(name, ParameterType.String, "")
-                    val elements = parameter.elements
-                    value.foldIn(elements) { acc, m ->
-                        create("", m)?.let { param -> acc.apply { add(param) } } ?: acc
-                    }
-                    parameter
-                }
-                value is InspectableValue -> createFromInspectableValue(name, value)
-                else -> null
-            }
-
-        private fun createFromOffset(name: String, value: Offset): NodeParameter {
-            val parameter = NodeParameter(name, ParameterType.String, Offset::class.java.simpleName)
-            val elements = parameter.elements
-            elements.add(NodeParameter("x", DimensionDp, with(density) { value.x.toDp().value }))
-            elements.add(NodeParameter("y", DimensionDp, with(density) { value.y.toDp().value }))
-            return parameter
-        }
-
-        // Special handling of blurRadius: convert to dp:
-        private fun createFromShadow(name: String, value: Shadow): NodeParameter? {
-            val parameter = createFromKotlinReflection(name, value) ?: return null
-            val elements = parameter.elements
-            val index = elements.indexOfFirst { it.name == "blurRadius" }
-            if (index >= 0) {
-                val blurRadius = with(density) { value.blurRadius.toDp().value }
-                elements[index] = NodeParameter("blurRadius", DimensionDp, blurRadius)
-            }
-            return parameter
-        }
-
-        @Suppress("DEPRECATION")
-        private fun createFromTextUnit(name: String, value: TextUnit): NodeParameter =
-            when (value.type) {
-                TextUnitType.Sp -> NodeParameter(name, ParameterType.DimensionSp, value.value)
-                TextUnitType.Em -> NodeParameter(name, ParameterType.DimensionEm, value.value)
-                TextUnitType.Unspecified ->
-                    NodeParameter(name, ParameterType.String, "Unspecified")
-            }
-
-        private fun createFromImageVector(name: String, value: ImageVector): NodeParameter =
-            NodeParameter(name, ParameterType.String, value.name)
-
-        /**
-         * Select a resource font among the font in the family to represent the font
-         *
-         * Prefer the font closest to [FontWeight.Normal] and [FontStyle.Normal]
-         */
-        private fun findBestResourceFont(value: FontListFontFamily): ResourceFont? =
-            value.fonts.asSequence().filterIsInstance<ResourceFont>().minByOrNull {
-                abs(it.weight.weight - FontWeight.Normal.weight) + it.style.ordinal
-            }
-    }
-}
diff --git a/compose/ui/ui/api/1.0.0-beta07.txt b/compose/ui/ui/api/1.0.0-beta07.txt
index ac74859..25aed62 100644
--- a/compose/ui/ui/api/1.0.0-beta07.txt
+++ b/compose/ui/ui/api/1.0.0-beta07.txt
@@ -236,13 +236,35 @@
     method public static androidx.compose.ui.Modifier onFocusChanged(androidx.compose.ui.Modifier, kotlin.jvm.functions.Function1<? super androidx.compose.ui.focus.FocusState,kotlin.Unit> onFocusChanged);
   }
 
-  public enum FocusDirection {
-    enum_constant public static final androidx.compose.ui.focus.FocusDirection Down;
-    enum_constant public static final androidx.compose.ui.focus.FocusDirection Left;
-    enum_constant public static final androidx.compose.ui.focus.FocusDirection Next;
-    enum_constant public static final androidx.compose.ui.focus.FocusDirection Previous;
-    enum_constant public static final androidx.compose.ui.focus.FocusDirection Right;
-    enum_constant public static final androidx.compose.ui.focus.FocusDirection Up;
+  public final inline class FocusDirection {
+    ctor public FocusDirection();
+    method public static int constructor-impl(int value);
+    method public static inline boolean equals-impl(int p, Object? p1);
+    method public static boolean equals-impl0(int p1, int p2);
+    method public int getValue();
+    method public static inline int hashCode-impl(int p);
+    method public static String toString-impl(int $this);
+    property public final int value;
+    field public static final androidx.compose.ui.focus.FocusDirection.Companion Companion;
+  }
+
+  public static final class FocusDirection.Companion {
+    method public int getDown-dhqQ-8s();
+    method public int getIn-dhqQ-8s();
+    method public int getLeft-dhqQ-8s();
+    method public int getNext-dhqQ-8s();
+    method public int getOut-dhqQ-8s();
+    method public int getPrevious-dhqQ-8s();
+    method public int getRight-dhqQ-8s();
+    method public int getUp-dhqQ-8s();
+    property public final int Down;
+    property public final int In;
+    property public final int Left;
+    property public final int Next;
+    property public final int Out;
+    property public final int Previous;
+    property public final int Right;
+    property public final int Up;
   }
 
   public interface FocusEventModifier extends androidx.compose.ui.Modifier.Element {
@@ -255,7 +277,7 @@
 
   public interface FocusManager {
     method public void clearFocus(optional boolean forcedClear);
-    method public boolean moveFocus(androidx.compose.ui.focus.FocusDirection focusDirection);
+    method public boolean moveFocus-3ESFkO8(int focusDirection);
   }
 
   public final class FocusModifierKt {
@@ -1219,15 +1241,30 @@
     property public final android.view.KeyEvent nativeKeyEvent;
   }
 
-  public enum KeyEventType {
-    enum_constant public static final androidx.compose.ui.input.key.KeyEventType KeyDown;
-    enum_constant public static final androidx.compose.ui.input.key.KeyEventType KeyUp;
-    enum_constant public static final androidx.compose.ui.input.key.KeyEventType Unknown;
+  public final inline class KeyEventType {
+    ctor public KeyEventType();
+    method public static int constructor-impl(int value);
+    method public static inline boolean equals-impl(int p, Object? p1);
+    method public static boolean equals-impl0(int p1, int p2);
+    method public int getValue();
+    method public static inline int hashCode-impl(int p);
+    method public static String toString-impl(int $this);
+    property public final int value;
+    field public static final androidx.compose.ui.input.key.KeyEventType.Companion Companion;
+  }
+
+  public static final class KeyEventType.Companion {
+    method public int getKeyDown-CS__XNY();
+    method public int getKeyUp-CS__XNY();
+    method public int getUnknown-CS__XNY();
+    property public final int KeyDown;
+    property public final int KeyUp;
+    property public final int Unknown;
   }
 
   public final class KeyEvent_androidKt {
     method public static long getKey-ZmokQxo(android.view.KeyEvent);
-    method public static androidx.compose.ui.input.key.KeyEventType getType-ZmokQxo(android.view.KeyEvent);
+    method public static int getType-ZmokQxo(android.view.KeyEvent);
     method public static int getUtf16CodePoint-ZmokQxo(android.view.KeyEvent);
     method public static boolean isAltPressed-ZmokQxo(android.view.KeyEvent);
     method public static boolean isCtrlPressed-ZmokQxo(android.view.KeyEvent);
@@ -1734,6 +1771,17 @@
 
   public final class SubcomposeLayoutKt {
     method @androidx.compose.runtime.Composable public static void SubcomposeLayout(optional androidx.compose.ui.Modifier modifier, kotlin.jvm.functions.Function2<? super androidx.compose.ui.layout.SubcomposeMeasureScope,? super androidx.compose.ui.unit.Constraints,? extends androidx.compose.ui.layout.MeasureResult> measurePolicy);
+    method @androidx.compose.runtime.Composable public static void SubcomposeLayout(androidx.compose.ui.layout.SubcomposeLayoutState state, optional androidx.compose.ui.Modifier modifier, kotlin.jvm.functions.Function2<? super androidx.compose.ui.layout.SubcomposeMeasureScope,? super androidx.compose.ui.unit.Constraints,? extends androidx.compose.ui.layout.MeasureResult> measurePolicy);
+  }
+
+  public final class SubcomposeLayoutState {
+    ctor public SubcomposeLayoutState(int maxSlotsToRetainForReuse);
+    ctor public SubcomposeLayoutState();
+    method public androidx.compose.ui.layout.SubcomposeLayoutState.PrecomposedSlotHandle precompose(Object? slotId, kotlin.jvm.functions.Function0<kotlin.Unit> content);
+  }
+
+  public static interface SubcomposeLayoutState.PrecomposedSlotHandle {
+    method public void dispose();
   }
 
   public interface SubcomposeMeasureScope extends androidx.compose.ui.layout.MeasureScope {
diff --git a/compose/ui/ui/api/current.ignore b/compose/ui/ui/api/current.ignore
index 42d6635..260888e 100644
--- a/compose/ui/ui/api/current.ignore
+++ b/compose/ui/ui/api/current.ignore
@@ -1,3 +1,45 @@
 // Baseline format: 1.0
+AddedAbstractMethod: androidx.compose.ui.focus.FocusManager#moveFocus-3ESFkO8(int):
+    Added method androidx.compose.ui.focus.FocusManager.moveFocus-3ESFkO8(int)
+
+
+ChangedSuperclass: androidx.compose.ui.focus.FocusDirection:
+    Class androidx.compose.ui.focus.FocusDirection superclass changed from java.lang.Enum to java.lang.Object
+ChangedSuperclass: androidx.compose.ui.input.key.KeyEventType:
+    Class androidx.compose.ui.input.key.KeyEventType superclass changed from java.lang.Enum to java.lang.Object
+
+
+ChangedType: androidx.compose.ui.input.key.KeyEvent_androidKt#getType-ZmokQxo(android.view.KeyEvent):
+    Method androidx.compose.ui.input.key.KeyEvent_androidKt.getType-ZmokQxo has changed return type from androidx.compose.ui.input.key.KeyEventType to int
+
+
+InvalidNullConversion: androidx.compose.ui.input.key.KeyEvent_androidKt#getType-ZmokQxo(android.view.KeyEvent):
+    Attempted to remove @NonNull annotation from method androidx.compose.ui.input.key.KeyEvent_androidKt.getType-ZmokQxo(android.view.KeyEvent)
+
+
 RemovedClass: androidx.compose.ui.input.pointer.HitPathTrackerKt:
     Removed class androidx.compose.ui.input.pointer.HitPathTrackerKt
+
+
+RemovedField: androidx.compose.ui.focus.FocusDirection#Down:
+    Removed enum constant androidx.compose.ui.focus.FocusDirection.Down
+RemovedField: androidx.compose.ui.focus.FocusDirection#Left:
+    Removed enum constant androidx.compose.ui.focus.FocusDirection.Left
+RemovedField: androidx.compose.ui.focus.FocusDirection#Next:
+    Removed enum constant androidx.compose.ui.focus.FocusDirection.Next
+RemovedField: androidx.compose.ui.focus.FocusDirection#Previous:
+    Removed enum constant androidx.compose.ui.focus.FocusDirection.Previous
+RemovedField: androidx.compose.ui.focus.FocusDirection#Right:
+    Removed enum constant androidx.compose.ui.focus.FocusDirection.Right
+RemovedField: androidx.compose.ui.focus.FocusDirection#Up:
+    Removed enum constant androidx.compose.ui.focus.FocusDirection.Up
+RemovedField: androidx.compose.ui.input.key.KeyEventType#KeyDown:
+    Removed enum constant androidx.compose.ui.input.key.KeyEventType.KeyDown
+RemovedField: androidx.compose.ui.input.key.KeyEventType#KeyUp:
+    Removed enum constant androidx.compose.ui.input.key.KeyEventType.KeyUp
+RemovedField: androidx.compose.ui.input.key.KeyEventType#Unknown:
+    Removed enum constant androidx.compose.ui.input.key.KeyEventType.Unknown
+
+
+RemovedMethod: androidx.compose.ui.focus.FocusManager#moveFocus(androidx.compose.ui.focus.FocusDirection):
+    Removed method androidx.compose.ui.focus.FocusManager.moveFocus(androidx.compose.ui.focus.FocusDirection)
diff --git a/compose/ui/ui/api/current.txt b/compose/ui/ui/api/current.txt
index ac74859..25aed62 100644
--- a/compose/ui/ui/api/current.txt
+++ b/compose/ui/ui/api/current.txt
@@ -236,13 +236,35 @@
     method public static androidx.compose.ui.Modifier onFocusChanged(androidx.compose.ui.Modifier, kotlin.jvm.functions.Function1<? super androidx.compose.ui.focus.FocusState,kotlin.Unit> onFocusChanged);
   }
 
-  public enum FocusDirection {
-    enum_constant public static final androidx.compose.ui.focus.FocusDirection Down;
-    enum_constant public static final androidx.compose.ui.focus.FocusDirection Left;
-    enum_constant public static final androidx.compose.ui.focus.FocusDirection Next;
-    enum_constant public static final androidx.compose.ui.focus.FocusDirection Previous;
-    enum_constant public static final androidx.compose.ui.focus.FocusDirection Right;
-    enum_constant public static final androidx.compose.ui.focus.FocusDirection Up;
+  public final inline class FocusDirection {
+    ctor public FocusDirection();
+    method public static int constructor-impl(int value);
+    method public static inline boolean equals-impl(int p, Object? p1);
+    method public static boolean equals-impl0(int p1, int p2);
+    method public int getValue();
+    method public static inline int hashCode-impl(int p);
+    method public static String toString-impl(int $this);
+    property public final int value;
+    field public static final androidx.compose.ui.focus.FocusDirection.Companion Companion;
+  }
+
+  public static final class FocusDirection.Companion {
+    method public int getDown-dhqQ-8s();
+    method public int getIn-dhqQ-8s();
+    method public int getLeft-dhqQ-8s();
+    method public int getNext-dhqQ-8s();
+    method public int getOut-dhqQ-8s();
+    method public int getPrevious-dhqQ-8s();
+    method public int getRight-dhqQ-8s();
+    method public int getUp-dhqQ-8s();
+    property public final int Down;
+    property public final int In;
+    property public final int Left;
+    property public final int Next;
+    property public final int Out;
+    property public final int Previous;
+    property public final int Right;
+    property public final int Up;
   }
 
   public interface FocusEventModifier extends androidx.compose.ui.Modifier.Element {
@@ -255,7 +277,7 @@
 
   public interface FocusManager {
     method public void clearFocus(optional boolean forcedClear);
-    method public boolean moveFocus(androidx.compose.ui.focus.FocusDirection focusDirection);
+    method public boolean moveFocus-3ESFkO8(int focusDirection);
   }
 
   public final class FocusModifierKt {
@@ -1219,15 +1241,30 @@
     property public final android.view.KeyEvent nativeKeyEvent;
   }
 
-  public enum KeyEventType {
-    enum_constant public static final androidx.compose.ui.input.key.KeyEventType KeyDown;
-    enum_constant public static final androidx.compose.ui.input.key.KeyEventType KeyUp;
-    enum_constant public static final androidx.compose.ui.input.key.KeyEventType Unknown;
+  public final inline class KeyEventType {
+    ctor public KeyEventType();
+    method public static int constructor-impl(int value);
+    method public static inline boolean equals-impl(int p, Object? p1);
+    method public static boolean equals-impl0(int p1, int p2);
+    method public int getValue();
+    method public static inline int hashCode-impl(int p);
+    method public static String toString-impl(int $this);
+    property public final int value;
+    field public static final androidx.compose.ui.input.key.KeyEventType.Companion Companion;
+  }
+
+  public static final class KeyEventType.Companion {
+    method public int getKeyDown-CS__XNY();
+    method public int getKeyUp-CS__XNY();
+    method public int getUnknown-CS__XNY();
+    property public final int KeyDown;
+    property public final int KeyUp;
+    property public final int Unknown;
   }
 
   public final class KeyEvent_androidKt {
     method public static long getKey-ZmokQxo(android.view.KeyEvent);
-    method public static androidx.compose.ui.input.key.KeyEventType getType-ZmokQxo(android.view.KeyEvent);
+    method public static int getType-ZmokQxo(android.view.KeyEvent);
     method public static int getUtf16CodePoint-ZmokQxo(android.view.KeyEvent);
     method public static boolean isAltPressed-ZmokQxo(android.view.KeyEvent);
     method public static boolean isCtrlPressed-ZmokQxo(android.view.KeyEvent);
@@ -1734,6 +1771,17 @@
 
   public final class SubcomposeLayoutKt {
     method @androidx.compose.runtime.Composable public static void SubcomposeLayout(optional androidx.compose.ui.Modifier modifier, kotlin.jvm.functions.Function2<? super androidx.compose.ui.layout.SubcomposeMeasureScope,? super androidx.compose.ui.unit.Constraints,? extends androidx.compose.ui.layout.MeasureResult> measurePolicy);
+    method @androidx.compose.runtime.Composable public static void SubcomposeLayout(androidx.compose.ui.layout.SubcomposeLayoutState state, optional androidx.compose.ui.Modifier modifier, kotlin.jvm.functions.Function2<? super androidx.compose.ui.layout.SubcomposeMeasureScope,? super androidx.compose.ui.unit.Constraints,? extends androidx.compose.ui.layout.MeasureResult> measurePolicy);
+  }
+
+  public final class SubcomposeLayoutState {
+    ctor public SubcomposeLayoutState(int maxSlotsToRetainForReuse);
+    ctor public SubcomposeLayoutState();
+    method public androidx.compose.ui.layout.SubcomposeLayoutState.PrecomposedSlotHandle precompose(Object? slotId, kotlin.jvm.functions.Function0<kotlin.Unit> content);
+  }
+
+  public static interface SubcomposeLayoutState.PrecomposedSlotHandle {
+    method public void dispose();
   }
 
   public interface SubcomposeMeasureScope extends androidx.compose.ui.layout.MeasureScope {
diff --git a/compose/ui/ui/api/public_plus_experimental_1.0.0-beta07.txt b/compose/ui/ui/api/public_plus_experimental_1.0.0-beta07.txt
index 86f417b..9fa0749 100644
--- a/compose/ui/ui/api/public_plus_experimental_1.0.0-beta07.txt
+++ b/compose/ui/ui/api/public_plus_experimental_1.0.0-beta07.txt
@@ -307,13 +307,35 @@
     method public static androidx.compose.ui.Modifier onFocusChanged(androidx.compose.ui.Modifier, kotlin.jvm.functions.Function1<? super androidx.compose.ui.focus.FocusState,kotlin.Unit> onFocusChanged);
   }
 
-  public enum FocusDirection {
-    enum_constant public static final androidx.compose.ui.focus.FocusDirection Down;
-    enum_constant public static final androidx.compose.ui.focus.FocusDirection Left;
-    enum_constant public static final androidx.compose.ui.focus.FocusDirection Next;
-    enum_constant public static final androidx.compose.ui.focus.FocusDirection Previous;
-    enum_constant public static final androidx.compose.ui.focus.FocusDirection Right;
-    enum_constant public static final androidx.compose.ui.focus.FocusDirection Up;
+  public final inline class FocusDirection {
+    ctor public FocusDirection();
+    method public static int constructor-impl(int value);
+    method public static inline boolean equals-impl(int p, Object? p1);
+    method public static boolean equals-impl0(int p1, int p2);
+    method public int getValue();
+    method public static inline int hashCode-impl(int p);
+    method public static String toString-impl(int $this);
+    property public final int value;
+    field public static final androidx.compose.ui.focus.FocusDirection.Companion Companion;
+  }
+
+  public static final class FocusDirection.Companion {
+    method public int getDown-dhqQ-8s();
+    method public int getIn-dhqQ-8s();
+    method public int getLeft-dhqQ-8s();
+    method public int getNext-dhqQ-8s();
+    method public int getOut-dhqQ-8s();
+    method public int getPrevious-dhqQ-8s();
+    method public int getRight-dhqQ-8s();
+    method public int getUp-dhqQ-8s();
+    property public final int Down;
+    property public final int In;
+    property public final int Left;
+    property public final int Next;
+    property public final int Out;
+    property public final int Previous;
+    property public final int Right;
+    property public final int Up;
   }
 
   public interface FocusEventModifier extends androidx.compose.ui.Modifier.Element {
@@ -326,9 +348,9 @@
 
   public interface FocusManager {
     method public void clearFocus(optional boolean forcedClear);
-    method public boolean moveFocus(androidx.compose.ui.focus.FocusDirection focusDirection);
-    method @androidx.compose.ui.ExperimentalComposeUiApi public boolean moveFocusIn();
-    method @androidx.compose.ui.ExperimentalComposeUiApi public boolean moveFocusOut();
+    method public boolean moveFocus-3ESFkO8(int focusDirection);
+    method @Deprecated @androidx.compose.ui.ExperimentalComposeUiApi public default boolean moveFocusIn();
+    method @Deprecated @androidx.compose.ui.ExperimentalComposeUiApi public default boolean moveFocusOut();
   }
 
   public final class FocusModifierKt {
@@ -1321,15 +1343,30 @@
     property public final android.view.KeyEvent nativeKeyEvent;
   }
 
-  public enum KeyEventType {
-    enum_constant public static final androidx.compose.ui.input.key.KeyEventType KeyDown;
-    enum_constant public static final androidx.compose.ui.input.key.KeyEventType KeyUp;
-    enum_constant public static final androidx.compose.ui.input.key.KeyEventType Unknown;
+  public final inline class KeyEventType {
+    ctor public KeyEventType();
+    method public static int constructor-impl(int value);
+    method public static inline boolean equals-impl(int p, Object? p1);
+    method public static boolean equals-impl0(int p1, int p2);
+    method public int getValue();
+    method public static inline int hashCode-impl(int p);
+    method public static String toString-impl(int $this);
+    property public final int value;
+    field public static final androidx.compose.ui.input.key.KeyEventType.Companion Companion;
+  }
+
+  public static final class KeyEventType.Companion {
+    method public int getKeyDown-CS__XNY();
+    method public int getKeyUp-CS__XNY();
+    method public int getUnknown-CS__XNY();
+    property public final int KeyDown;
+    property public final int KeyUp;
+    property public final int Unknown;
   }
 
   public final class KeyEvent_androidKt {
     method public static long getKey-ZmokQxo(android.view.KeyEvent);
-    method public static androidx.compose.ui.input.key.KeyEventType getType-ZmokQxo(android.view.KeyEvent);
+    method public static int getType-ZmokQxo(android.view.KeyEvent);
     method public static int getUtf16CodePoint-ZmokQxo(android.view.KeyEvent);
     method public static boolean isAltPressed-ZmokQxo(android.view.KeyEvent);
     method public static boolean isCtrlPressed-ZmokQxo(android.view.KeyEvent);
@@ -1838,6 +1875,17 @@
 
   public final class SubcomposeLayoutKt {
     method @androidx.compose.runtime.Composable public static void SubcomposeLayout(optional androidx.compose.ui.Modifier modifier, kotlin.jvm.functions.Function2<? super androidx.compose.ui.layout.SubcomposeMeasureScope,? super androidx.compose.ui.unit.Constraints,? extends androidx.compose.ui.layout.MeasureResult> measurePolicy);
+    method @androidx.compose.runtime.Composable public static void SubcomposeLayout(androidx.compose.ui.layout.SubcomposeLayoutState state, optional androidx.compose.ui.Modifier modifier, kotlin.jvm.functions.Function2<? super androidx.compose.ui.layout.SubcomposeMeasureScope,? super androidx.compose.ui.unit.Constraints,? extends androidx.compose.ui.layout.MeasureResult> measurePolicy);
+  }
+
+  public final class SubcomposeLayoutState {
+    ctor public SubcomposeLayoutState(int maxSlotsToRetainForReuse);
+    ctor public SubcomposeLayoutState();
+    method public androidx.compose.ui.layout.SubcomposeLayoutState.PrecomposedSlotHandle precompose(Object? slotId, kotlin.jvm.functions.Function0<kotlin.Unit> content);
+  }
+
+  public static interface SubcomposeLayoutState.PrecomposedSlotHandle {
+    method public void dispose();
   }
 
   public interface SubcomposeMeasureScope extends androidx.compose.ui.layout.MeasureScope {
diff --git a/compose/ui/ui/api/public_plus_experimental_current.txt b/compose/ui/ui/api/public_plus_experimental_current.txt
index 86f417b..9fa0749 100644
--- a/compose/ui/ui/api/public_plus_experimental_current.txt
+++ b/compose/ui/ui/api/public_plus_experimental_current.txt
@@ -307,13 +307,35 @@
     method public static androidx.compose.ui.Modifier onFocusChanged(androidx.compose.ui.Modifier, kotlin.jvm.functions.Function1<? super androidx.compose.ui.focus.FocusState,kotlin.Unit> onFocusChanged);
   }
 
-  public enum FocusDirection {
-    enum_constant public static final androidx.compose.ui.focus.FocusDirection Down;
-    enum_constant public static final androidx.compose.ui.focus.FocusDirection Left;
-    enum_constant public static final androidx.compose.ui.focus.FocusDirection Next;
-    enum_constant public static final androidx.compose.ui.focus.FocusDirection Previous;
-    enum_constant public static final androidx.compose.ui.focus.FocusDirection Right;
-    enum_constant public static final androidx.compose.ui.focus.FocusDirection Up;
+  public final inline class FocusDirection {
+    ctor public FocusDirection();
+    method public static int constructor-impl(int value);
+    method public static inline boolean equals-impl(int p, Object? p1);
+    method public static boolean equals-impl0(int p1, int p2);
+    method public int getValue();
+    method public static inline int hashCode-impl(int p);
+    method public static String toString-impl(int $this);
+    property public final int value;
+    field public static final androidx.compose.ui.focus.FocusDirection.Companion Companion;
+  }
+
+  public static final class FocusDirection.Companion {
+    method public int getDown-dhqQ-8s();
+    method public int getIn-dhqQ-8s();
+    method public int getLeft-dhqQ-8s();
+    method public int getNext-dhqQ-8s();
+    method public int getOut-dhqQ-8s();
+    method public int getPrevious-dhqQ-8s();
+    method public int getRight-dhqQ-8s();
+    method public int getUp-dhqQ-8s();
+    property public final int Down;
+    property public final int In;
+    property public final int Left;
+    property public final int Next;
+    property public final int Out;
+    property public final int Previous;
+    property public final int Right;
+    property public final int Up;
   }
 
   public interface FocusEventModifier extends androidx.compose.ui.Modifier.Element {
@@ -326,9 +348,9 @@
 
   public interface FocusManager {
     method public void clearFocus(optional boolean forcedClear);
-    method public boolean moveFocus(androidx.compose.ui.focus.FocusDirection focusDirection);
-    method @androidx.compose.ui.ExperimentalComposeUiApi public boolean moveFocusIn();
-    method @androidx.compose.ui.ExperimentalComposeUiApi public boolean moveFocusOut();
+    method public boolean moveFocus-3ESFkO8(int focusDirection);
+    method @Deprecated @androidx.compose.ui.ExperimentalComposeUiApi public default boolean moveFocusIn();
+    method @Deprecated @androidx.compose.ui.ExperimentalComposeUiApi public default boolean moveFocusOut();
   }
 
   public final class FocusModifierKt {
@@ -1321,15 +1343,30 @@
     property public final android.view.KeyEvent nativeKeyEvent;
   }
 
-  public enum KeyEventType {
-    enum_constant public static final androidx.compose.ui.input.key.KeyEventType KeyDown;
-    enum_constant public static final androidx.compose.ui.input.key.KeyEventType KeyUp;
-    enum_constant public static final androidx.compose.ui.input.key.KeyEventType Unknown;
+  public final inline class KeyEventType {
+    ctor public KeyEventType();
+    method public static int constructor-impl(int value);
+    method public static inline boolean equals-impl(int p, Object? p1);
+    method public static boolean equals-impl0(int p1, int p2);
+    method public int getValue();
+    method public static inline int hashCode-impl(int p);
+    method public static String toString-impl(int $this);
+    property public final int value;
+    field public static final androidx.compose.ui.input.key.KeyEventType.Companion Companion;
+  }
+
+  public static final class KeyEventType.Companion {
+    method public int getKeyDown-CS__XNY();
+    method public int getKeyUp-CS__XNY();
+    method public int getUnknown-CS__XNY();
+    property public final int KeyDown;
+    property public final int KeyUp;
+    property public final int Unknown;
   }
 
   public final class KeyEvent_androidKt {
     method public static long getKey-ZmokQxo(android.view.KeyEvent);
-    method public static androidx.compose.ui.input.key.KeyEventType getType-ZmokQxo(android.view.KeyEvent);
+    method public static int getType-ZmokQxo(android.view.KeyEvent);
     method public static int getUtf16CodePoint-ZmokQxo(android.view.KeyEvent);
     method public static boolean isAltPressed-ZmokQxo(android.view.KeyEvent);
     method public static boolean isCtrlPressed-ZmokQxo(android.view.KeyEvent);
@@ -1838,6 +1875,17 @@
 
   public final class SubcomposeLayoutKt {
     method @androidx.compose.runtime.Composable public static void SubcomposeLayout(optional androidx.compose.ui.Modifier modifier, kotlin.jvm.functions.Function2<? super androidx.compose.ui.layout.SubcomposeMeasureScope,? super androidx.compose.ui.unit.Constraints,? extends androidx.compose.ui.layout.MeasureResult> measurePolicy);
+    method @androidx.compose.runtime.Composable public static void SubcomposeLayout(androidx.compose.ui.layout.SubcomposeLayoutState state, optional androidx.compose.ui.Modifier modifier, kotlin.jvm.functions.Function2<? super androidx.compose.ui.layout.SubcomposeMeasureScope,? super androidx.compose.ui.unit.Constraints,? extends androidx.compose.ui.layout.MeasureResult> measurePolicy);
+  }
+
+  public final class SubcomposeLayoutState {
+    ctor public SubcomposeLayoutState(int maxSlotsToRetainForReuse);
+    ctor public SubcomposeLayoutState();
+    method public androidx.compose.ui.layout.SubcomposeLayoutState.PrecomposedSlotHandle precompose(Object? slotId, kotlin.jvm.functions.Function0<kotlin.Unit> content);
+  }
+
+  public static interface SubcomposeLayoutState.PrecomposedSlotHandle {
+    method public void dispose();
   }
 
   public interface SubcomposeMeasureScope extends androidx.compose.ui.layout.MeasureScope {
diff --git a/compose/ui/ui/api/restricted_1.0.0-beta07.txt b/compose/ui/ui/api/restricted_1.0.0-beta07.txt
index d741df2..d35964c 100644
--- a/compose/ui/ui/api/restricted_1.0.0-beta07.txt
+++ b/compose/ui/ui/api/restricted_1.0.0-beta07.txt
@@ -236,13 +236,35 @@
     method public static androidx.compose.ui.Modifier onFocusChanged(androidx.compose.ui.Modifier, kotlin.jvm.functions.Function1<? super androidx.compose.ui.focus.FocusState,kotlin.Unit> onFocusChanged);
   }
 
-  public enum FocusDirection {
-    enum_constant public static final androidx.compose.ui.focus.FocusDirection Down;
-    enum_constant public static final androidx.compose.ui.focus.FocusDirection Left;
-    enum_constant public static final androidx.compose.ui.focus.FocusDirection Next;
-    enum_constant public static final androidx.compose.ui.focus.FocusDirection Previous;
-    enum_constant public static final androidx.compose.ui.focus.FocusDirection Right;
-    enum_constant public static final androidx.compose.ui.focus.FocusDirection Up;
+  public final inline class FocusDirection {
+    ctor public FocusDirection();
+    method public static int constructor-impl(int value);
+    method public static inline boolean equals-impl(int p, Object? p1);
+    method public static boolean equals-impl0(int p1, int p2);
+    method public int getValue();
+    method public static inline int hashCode-impl(int p);
+    method public static String toString-impl(int $this);
+    property public final int value;
+    field public static final androidx.compose.ui.focus.FocusDirection.Companion Companion;
+  }
+
+  public static final class FocusDirection.Companion {
+    method public int getDown-dhqQ-8s();
+    method public int getIn-dhqQ-8s();
+    method public int getLeft-dhqQ-8s();
+    method public int getNext-dhqQ-8s();
+    method public int getOut-dhqQ-8s();
+    method public int getPrevious-dhqQ-8s();
+    method public int getRight-dhqQ-8s();
+    method public int getUp-dhqQ-8s();
+    property public final int Down;
+    property public final int In;
+    property public final int Left;
+    property public final int Next;
+    property public final int Out;
+    property public final int Previous;
+    property public final int Right;
+    property public final int Up;
   }
 
   public interface FocusEventModifier extends androidx.compose.ui.Modifier.Element {
@@ -255,7 +277,7 @@
 
   public interface FocusManager {
     method public void clearFocus(optional boolean forcedClear);
-    method public boolean moveFocus(androidx.compose.ui.focus.FocusDirection focusDirection);
+    method public boolean moveFocus-3ESFkO8(int focusDirection);
   }
 
   public final class FocusModifierKt {
@@ -1219,15 +1241,30 @@
     property public final android.view.KeyEvent nativeKeyEvent;
   }
 
-  public enum KeyEventType {
-    enum_constant public static final androidx.compose.ui.input.key.KeyEventType KeyDown;
-    enum_constant public static final androidx.compose.ui.input.key.KeyEventType KeyUp;
-    enum_constant public static final androidx.compose.ui.input.key.KeyEventType Unknown;
+  public final inline class KeyEventType {
+    ctor public KeyEventType();
+    method public static int constructor-impl(int value);
+    method public static inline boolean equals-impl(int p, Object? p1);
+    method public static boolean equals-impl0(int p1, int p2);
+    method public int getValue();
+    method public static inline int hashCode-impl(int p);
+    method public static String toString-impl(int $this);
+    property public final int value;
+    field public static final androidx.compose.ui.input.key.KeyEventType.Companion Companion;
+  }
+
+  public static final class KeyEventType.Companion {
+    method public int getKeyDown-CS__XNY();
+    method public int getKeyUp-CS__XNY();
+    method public int getUnknown-CS__XNY();
+    property public final int KeyDown;
+    property public final int KeyUp;
+    property public final int Unknown;
   }
 
   public final class KeyEvent_androidKt {
     method public static long getKey-ZmokQxo(android.view.KeyEvent);
-    method public static androidx.compose.ui.input.key.KeyEventType getType-ZmokQxo(android.view.KeyEvent);
+    method public static int getType-ZmokQxo(android.view.KeyEvent);
     method public static int getUtf16CodePoint-ZmokQxo(android.view.KeyEvent);
     method public static boolean isAltPressed-ZmokQxo(android.view.KeyEvent);
     method public static boolean isCtrlPressed-ZmokQxo(android.view.KeyEvent);
@@ -1735,6 +1772,17 @@
 
   public final class SubcomposeLayoutKt {
     method @androidx.compose.runtime.Composable public static void SubcomposeLayout(optional androidx.compose.ui.Modifier modifier, kotlin.jvm.functions.Function2<? super androidx.compose.ui.layout.SubcomposeMeasureScope,? super androidx.compose.ui.unit.Constraints,? extends androidx.compose.ui.layout.MeasureResult> measurePolicy);
+    method @androidx.compose.runtime.Composable public static void SubcomposeLayout(androidx.compose.ui.layout.SubcomposeLayoutState state, optional androidx.compose.ui.Modifier modifier, kotlin.jvm.functions.Function2<? super androidx.compose.ui.layout.SubcomposeMeasureScope,? super androidx.compose.ui.unit.Constraints,? extends androidx.compose.ui.layout.MeasureResult> measurePolicy);
+  }
+
+  public final class SubcomposeLayoutState {
+    ctor public SubcomposeLayoutState(int maxSlotsToRetainForReuse);
+    ctor public SubcomposeLayoutState();
+    method public androidx.compose.ui.layout.SubcomposeLayoutState.PrecomposedSlotHandle precompose(Object? slotId, kotlin.jvm.functions.Function0<kotlin.Unit> content);
+  }
+
+  public static interface SubcomposeLayoutState.PrecomposedSlotHandle {
+    method public void dispose();
   }
 
   public interface SubcomposeMeasureScope extends androidx.compose.ui.layout.MeasureScope {
diff --git a/compose/ui/ui/api/restricted_current.ignore b/compose/ui/ui/api/restricted_current.ignore
index 42d6635..260888e 100644
--- a/compose/ui/ui/api/restricted_current.ignore
+++ b/compose/ui/ui/api/restricted_current.ignore
@@ -1,3 +1,45 @@
 // Baseline format: 1.0
+AddedAbstractMethod: androidx.compose.ui.focus.FocusManager#moveFocus-3ESFkO8(int):
+    Added method androidx.compose.ui.focus.FocusManager.moveFocus-3ESFkO8(int)
+
+
+ChangedSuperclass: androidx.compose.ui.focus.FocusDirection:
+    Class androidx.compose.ui.focus.FocusDirection superclass changed from java.lang.Enum to java.lang.Object
+ChangedSuperclass: androidx.compose.ui.input.key.KeyEventType:
+    Class androidx.compose.ui.input.key.KeyEventType superclass changed from java.lang.Enum to java.lang.Object
+
+
+ChangedType: androidx.compose.ui.input.key.KeyEvent_androidKt#getType-ZmokQxo(android.view.KeyEvent):
+    Method androidx.compose.ui.input.key.KeyEvent_androidKt.getType-ZmokQxo has changed return type from androidx.compose.ui.input.key.KeyEventType to int
+
+
+InvalidNullConversion: androidx.compose.ui.input.key.KeyEvent_androidKt#getType-ZmokQxo(android.view.KeyEvent):
+    Attempted to remove @NonNull annotation from method androidx.compose.ui.input.key.KeyEvent_androidKt.getType-ZmokQxo(android.view.KeyEvent)
+
+
 RemovedClass: androidx.compose.ui.input.pointer.HitPathTrackerKt:
     Removed class androidx.compose.ui.input.pointer.HitPathTrackerKt
+
+
+RemovedField: androidx.compose.ui.focus.FocusDirection#Down:
+    Removed enum constant androidx.compose.ui.focus.FocusDirection.Down
+RemovedField: androidx.compose.ui.focus.FocusDirection#Left:
+    Removed enum constant androidx.compose.ui.focus.FocusDirection.Left
+RemovedField: androidx.compose.ui.focus.FocusDirection#Next:
+    Removed enum constant androidx.compose.ui.focus.FocusDirection.Next
+RemovedField: androidx.compose.ui.focus.FocusDirection#Previous:
+    Removed enum constant androidx.compose.ui.focus.FocusDirection.Previous
+RemovedField: androidx.compose.ui.focus.FocusDirection#Right:
+    Removed enum constant androidx.compose.ui.focus.FocusDirection.Right
+RemovedField: androidx.compose.ui.focus.FocusDirection#Up:
+    Removed enum constant androidx.compose.ui.focus.FocusDirection.Up
+RemovedField: androidx.compose.ui.input.key.KeyEventType#KeyDown:
+    Removed enum constant androidx.compose.ui.input.key.KeyEventType.KeyDown
+RemovedField: androidx.compose.ui.input.key.KeyEventType#KeyUp:
+    Removed enum constant androidx.compose.ui.input.key.KeyEventType.KeyUp
+RemovedField: androidx.compose.ui.input.key.KeyEventType#Unknown:
+    Removed enum constant androidx.compose.ui.input.key.KeyEventType.Unknown
+
+
+RemovedMethod: androidx.compose.ui.focus.FocusManager#moveFocus(androidx.compose.ui.focus.FocusDirection):
+    Removed method androidx.compose.ui.focus.FocusManager.moveFocus(androidx.compose.ui.focus.FocusDirection)
diff --git a/compose/ui/ui/api/restricted_current.txt b/compose/ui/ui/api/restricted_current.txt
index d741df2..d35964c 100644
--- a/compose/ui/ui/api/restricted_current.txt
+++ b/compose/ui/ui/api/restricted_current.txt
@@ -236,13 +236,35 @@
     method public static androidx.compose.ui.Modifier onFocusChanged(androidx.compose.ui.Modifier, kotlin.jvm.functions.Function1<? super androidx.compose.ui.focus.FocusState,kotlin.Unit> onFocusChanged);
   }
 
-  public enum FocusDirection {
-    enum_constant public static final androidx.compose.ui.focus.FocusDirection Down;
-    enum_constant public static final androidx.compose.ui.focus.FocusDirection Left;
-    enum_constant public static final androidx.compose.ui.focus.FocusDirection Next;
-    enum_constant public static final androidx.compose.ui.focus.FocusDirection Previous;
-    enum_constant public static final androidx.compose.ui.focus.FocusDirection Right;
-    enum_constant public static final androidx.compose.ui.focus.FocusDirection Up;
+  public final inline class FocusDirection {
+    ctor public FocusDirection();
+    method public static int constructor-impl(int value);
+    method public static inline boolean equals-impl(int p, Object? p1);
+    method public static boolean equals-impl0(int p1, int p2);
+    method public int getValue();
+    method public static inline int hashCode-impl(int p);
+    method public static String toString-impl(int $this);
+    property public final int value;
+    field public static final androidx.compose.ui.focus.FocusDirection.Companion Companion;
+  }
+
+  public static final class FocusDirection.Companion {
+    method public int getDown-dhqQ-8s();
+    method public int getIn-dhqQ-8s();
+    method public int getLeft-dhqQ-8s();
+    method public int getNext-dhqQ-8s();
+    method public int getOut-dhqQ-8s();
+    method public int getPrevious-dhqQ-8s();
+    method public int getRight-dhqQ-8s();
+    method public int getUp-dhqQ-8s();
+    property public final int Down;
+    property public final int In;
+    property public final int Left;
+    property public final int Next;
+    property public final int Out;
+    property public final int Previous;
+    property public final int Right;
+    property public final int Up;
   }
 
   public interface FocusEventModifier extends androidx.compose.ui.Modifier.Element {
@@ -255,7 +277,7 @@
 
   public interface FocusManager {
     method public void clearFocus(optional boolean forcedClear);
-    method public boolean moveFocus(androidx.compose.ui.focus.FocusDirection focusDirection);
+    method public boolean moveFocus-3ESFkO8(int focusDirection);
   }
 
   public final class FocusModifierKt {
@@ -1219,15 +1241,30 @@
     property public final android.view.KeyEvent nativeKeyEvent;
   }
 
-  public enum KeyEventType {
-    enum_constant public static final androidx.compose.ui.input.key.KeyEventType KeyDown;
-    enum_constant public static final androidx.compose.ui.input.key.KeyEventType KeyUp;
-    enum_constant public static final androidx.compose.ui.input.key.KeyEventType Unknown;
+  public final inline class KeyEventType {
+    ctor public KeyEventType();
+    method public static int constructor-impl(int value);
+    method public static inline boolean equals-impl(int p, Object? p1);
+    method public static boolean equals-impl0(int p1, int p2);
+    method public int getValue();
+    method public static inline int hashCode-impl(int p);
+    method public static String toString-impl(int $this);
+    property public final int value;
+    field public static final androidx.compose.ui.input.key.KeyEventType.Companion Companion;
+  }
+
+  public static final class KeyEventType.Companion {
+    method public int getKeyDown-CS__XNY();
+    method public int getKeyUp-CS__XNY();
+    method public int getUnknown-CS__XNY();
+    property public final int KeyDown;
+    property public final int KeyUp;
+    property public final int Unknown;
   }
 
   public final class KeyEvent_androidKt {
     method public static long getKey-ZmokQxo(android.view.KeyEvent);
-    method public static androidx.compose.ui.input.key.KeyEventType getType-ZmokQxo(android.view.KeyEvent);
+    method public static int getType-ZmokQxo(android.view.KeyEvent);
     method public static int getUtf16CodePoint-ZmokQxo(android.view.KeyEvent);
     method public static boolean isAltPressed-ZmokQxo(android.view.KeyEvent);
     method public static boolean isCtrlPressed-ZmokQxo(android.view.KeyEvent);
@@ -1735,6 +1772,17 @@
 
   public final class SubcomposeLayoutKt {
     method @androidx.compose.runtime.Composable public static void SubcomposeLayout(optional androidx.compose.ui.Modifier modifier, kotlin.jvm.functions.Function2<? super androidx.compose.ui.layout.SubcomposeMeasureScope,? super androidx.compose.ui.unit.Constraints,? extends androidx.compose.ui.layout.MeasureResult> measurePolicy);
+    method @androidx.compose.runtime.Composable public static void SubcomposeLayout(androidx.compose.ui.layout.SubcomposeLayoutState state, optional androidx.compose.ui.Modifier modifier, kotlin.jvm.functions.Function2<? super androidx.compose.ui.layout.SubcomposeMeasureScope,? super androidx.compose.ui.unit.Constraints,? extends androidx.compose.ui.layout.MeasureResult> measurePolicy);
+  }
+
+  public final class SubcomposeLayoutState {
+    ctor public SubcomposeLayoutState(int maxSlotsToRetainForReuse);
+    ctor public SubcomposeLayoutState();
+    method public androidx.compose.ui.layout.SubcomposeLayoutState.PrecomposedSlotHandle precompose(Object? slotId, kotlin.jvm.functions.Function0<kotlin.Unit> content);
+  }
+
+  public static interface SubcomposeLayoutState.PrecomposedSlotHandle {
+    method public void dispose();
   }
 
   public interface SubcomposeMeasureScope extends androidx.compose.ui.layout.MeasureScope {
diff --git a/compose/ui/ui/integration-tests/ui-demos/src/main/java/androidx/compose/ui/demos/focus/FocusManagerMoveFocusDemo.kt b/compose/ui/ui/integration-tests/ui-demos/src/main/java/androidx/compose/ui/demos/focus/FocusManagerMoveFocusDemo.kt
index d434562..b82e2f7 100644
--- a/compose/ui/ui/integration-tests/ui-demos/src/main/java/androidx/compose/ui/demos/focus/FocusManagerMoveFocusDemo.kt
+++ b/compose/ui/ui/integration-tests/ui-demos/src/main/java/androidx/compose/ui/demos/focus/FocusManagerMoveFocusDemo.kt
@@ -36,12 +36,12 @@
 import androidx.compose.ui.Alignment.Companion.CenterHorizontally
 import androidx.compose.ui.ExperimentalComposeUiApi
 import androidx.compose.ui.Modifier
-import androidx.compose.ui.focus.FocusDirection.Down
-import androidx.compose.ui.focus.FocusDirection.Left
-import androidx.compose.ui.focus.FocusDirection.Next
-import androidx.compose.ui.focus.FocusDirection.Previous
-import androidx.compose.ui.focus.FocusDirection.Right
-import androidx.compose.ui.focus.FocusDirection.Up
+import androidx.compose.ui.focus.FocusDirection.Companion.Down
+import androidx.compose.ui.focus.FocusDirection.Companion.Left
+import androidx.compose.ui.focus.FocusDirection.Companion.Next
+import androidx.compose.ui.focus.FocusDirection.Companion.Previous
+import androidx.compose.ui.focus.FocusDirection.Companion.Right
+import androidx.compose.ui.focus.FocusDirection.Companion.Up
 import androidx.compose.ui.focus.FocusRequester
 import androidx.compose.ui.focus.focusModifier
 import androidx.compose.ui.focus.focusOrder
diff --git a/compose/ui/ui/integration-tests/ui-demos/src/main/java/androidx/compose/ui/demos/keyinput/KeyInputDemo.kt b/compose/ui/ui/integration-tests/ui-demos/src/main/java/androidx/compose/ui/demos/keyinput/KeyInputDemo.kt
index 126a3cd..68bff5d 100644
--- a/compose/ui/ui/integration-tests/ui-demos/src/main/java/androidx/compose/ui/demos/keyinput/KeyInputDemo.kt
+++ b/compose/ui/ui/integration-tests/ui-demos/src/main/java/androidx/compose/ui/demos/keyinput/KeyInputDemo.kt
@@ -35,7 +35,7 @@
 import androidx.compose.ui.focus.isFocused
 import androidx.compose.ui.focus.onFocusChanged
 import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.input.key.KeyEventType.KeyDown
+import androidx.compose.ui.input.key.KeyEventType.Companion.KeyDown
 import androidx.compose.ui.input.key.onKeyEvent
 import androidx.compose.ui.input.key.type
 import androidx.compose.ui.input.key.utf16CodePoint
diff --git a/compose/ui/ui/samples/src/main/java/androidx/compose/ui/samples/FocusSamples.kt b/compose/ui/ui/samples/src/main/java/androidx/compose/ui/samples/FocusSamples.kt
new file mode 100644
index 0000000..f01d362
--- /dev/null
+++ b/compose/ui/ui/samples/src/main/java/androidx/compose/ui/samples/FocusSamples.kt
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.ui.samples
+
+import androidx.annotation.Sampled
+import androidx.compose.foundation.focusable
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.material.Button
+import androidx.compose.material.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.focus.FocusDirection
+import androidx.compose.ui.platform.LocalFocusManager
+
+@Sampled
+@Composable
+fun MoveFocusSample() {
+    val focusManager = LocalFocusManager.current
+    Column {
+        Row {
+            Box(Modifier.focusable())
+            Box(Modifier.focusable())
+        }
+        Row {
+            Box(Modifier.focusable())
+            Box(Modifier.focusable())
+        }
+        Button(onClick = { focusManager.moveFocus(FocusDirection.Right) }) { Text("Right") }
+        Button(onClick = { focusManager.moveFocus(FocusDirection.Left) }) { Text("Left") }
+        Button(onClick = { focusManager.moveFocus(FocusDirection.Up) }) { Text("Up") }
+        Button(onClick = { focusManager.moveFocus(FocusDirection.Down) }) { Text("Down") }
+    }
+}
\ No newline at end of file
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/AndroidAccessibilityTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/AndroidAccessibilityTest.kt
index 01171d3..65bb050 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/AndroidAccessibilityTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/AndroidAccessibilityTest.kt
@@ -57,6 +57,7 @@
 import androidx.compose.runtime.setValue
 import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.graphics.toAndroidRect
+import androidx.compose.ui.graphics.graphicsLayer
 import androidx.compose.ui.platform.AndroidComposeView
 import androidx.compose.ui.platform.AndroidComposeViewAccessibilityDelegateCompat
 import androidx.compose.ui.platform.LocalDensity
@@ -1356,6 +1357,87 @@
     }
 
     @Test
+    fun testLayerParamChange_setCorrectBounds_syntaxOne() {
+        var scale by mutableStateOf(1f)
+        container.setContent {
+            // testTag must not be on the same node with graphicsLayer, otherwise we will have
+            // semantics change notification.
+            Box(Modifier.graphicsLayer(scaleX = scale, scaleY = scale).requiredSize(300.dp)) {
+                Box(Modifier.matchParentSize().testTag("node"))
+            }
+        }
+
+        val node = rule.onNodeWithTag("node").fetchSemanticsNode()
+        var info: AccessibilityNodeInfo = AccessibilityNodeInfo.obtain()
+        rule.runOnUiThread {
+            info = provider.createAccessibilityNodeInfo(node.id)
+        }
+        with(rule.density) {
+            val size = 300.dp.roundToPx()
+            val rect = Rect()
+            info.getBoundsInScreen(rect)
+            assertEquals(size, rect.width())
+            assertEquals(size, rect.height())
+        }
+
+        scale = 0.5f
+        info.recycle()
+        rule.runOnIdle {
+            info = provider.createAccessibilityNodeInfo(node.id)
+        }
+        with(rule.density) {
+            val size = 150.dp.roundToPx()
+            val rect = Rect()
+            info.getBoundsInScreen(rect)
+            assertEquals(size, rect.width())
+            assertEquals(size, rect.height())
+        }
+    }
+
+    @Test
+    fun testLayerParamChange_setCorrectBounds_syntaxTwo() {
+        var scale by mutableStateOf(1f)
+        container.setContent {
+            // testTag must not be on the same node with graphicsLayer, otherwise we will have
+            // semantics change notification.
+            Box(
+                Modifier.graphicsLayer {
+                    scaleX = scale
+                    scaleY = scale
+                }.requiredSize(300.dp)
+            ) {
+                Box(Modifier.matchParentSize().testTag("node"))
+            }
+        }
+
+        val node = rule.onNodeWithTag("node").fetchSemanticsNode()
+        var info: AccessibilityNodeInfo = AccessibilityNodeInfo.obtain()
+        rule.runOnUiThread {
+            info = provider.createAccessibilityNodeInfo(node.id)
+        }
+        with(rule.density) {
+            val size = 300.dp.roundToPx()
+            val rect = Rect()
+            info.getBoundsInScreen(rect)
+            assertEquals(size, rect.width())
+            assertEquals(size, rect.height())
+        }
+
+        scale = 0.5f
+        info.recycle()
+        rule.runOnIdle {
+            info = provider.createAccessibilityNodeInfo(node.id)
+        }
+        with(rule.density) {
+            val size = 150.dp.roundToPx()
+            val rect = Rect()
+            info.getBoundsInScreen(rect)
+            assertEquals(size, rect.width())
+            assertEquals(size, rect.height())
+        }
+    }
+
+    @Test
     fun testDialog_setCorrectBounds() {
         var dialogComposeView: AndroidComposeView? = null
         container.setContent {
@@ -1389,10 +1471,10 @@
             val textPositionOnScreenX = viewPosition[0] + offset
             val textPositionOnScreenY = viewPosition[1] + offset
 
-            val textRect = android.graphics.Rect()
+            val textRect = Rect()
             info.getBoundsInScreen(textRect)
             assertEquals(
-                android.graphics.Rect(
+                Rect(
                     textPositionOnScreenX,
                     textPositionOnScreenY,
                     textPositionOnScreenX + size,
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/KeyEventToFocusDirectionTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/KeyEventToFocusDirectionTest.kt
index d389b61..d3805e3 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/KeyEventToFocusDirectionTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/KeyEventToFocusDirectionTest.kt
@@ -16,14 +16,15 @@
 
 package androidx.compose.ui.focus
 
-import androidx.compose.ui.focus.FocusDirectionInternal.Down
-import androidx.compose.ui.focus.FocusDirectionInternal.In
-import androidx.compose.ui.focus.FocusDirectionInternal.Left
-import androidx.compose.ui.focus.FocusDirectionInternal.Next
-import androidx.compose.ui.focus.FocusDirectionInternal.Out
-import androidx.compose.ui.focus.FocusDirectionInternal.Previous
-import androidx.compose.ui.focus.FocusDirectionInternal.Right
-import androidx.compose.ui.focus.FocusDirectionInternal.Up
+import androidx.compose.ui.ExperimentalComposeUiApi
+import androidx.compose.ui.focus.FocusDirection.Companion.Down
+import androidx.compose.ui.focus.FocusDirection.Companion.In
+import androidx.compose.ui.focus.FocusDirection.Companion.Left
+import androidx.compose.ui.focus.FocusDirection.Companion.Next
+import androidx.compose.ui.focus.FocusDirection.Companion.Out
+import androidx.compose.ui.focus.FocusDirection.Companion.Previous
+import androidx.compose.ui.focus.FocusDirection.Companion.Right
+import androidx.compose.ui.focus.FocusDirection.Companion.Up
 import androidx.compose.ui.input.key.Key
 import androidx.compose.ui.input.key.KeyEvent
 import androidx.compose.ui.input.key.nativeKeyCode
@@ -137,6 +138,7 @@
         val focusDirection = owner.getFocusDirection(keyEvent)
 
         // Assert.
+        @OptIn(ExperimentalComposeUiApi::class)
         assertThat(focusDirection).isEqualTo(In)
     }
 
@@ -149,6 +151,7 @@
         val focusDirection = owner.getFocusDirection(keyEvent)
 
         // Assert.
+        @OptIn(ExperimentalComposeUiApi::class)
         assertThat(focusDirection).isEqualTo(Out)
     }
 }
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/TwoDimensionalFocusTraversalInTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/TwoDimensionalFocusTraversalInTest.kt
index b758a44..a8d6b64 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/TwoDimensionalFocusTraversalInTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/TwoDimensionalFocusTraversalInTest.kt
@@ -20,6 +20,7 @@
 import androidx.compose.runtime.MutableState
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.ui.ExperimentalComposeUiApi
+import androidx.compose.ui.focus.FocusDirection.Companion.In
 import androidx.compose.ui.platform.LocalFocusManager
 import androidx.compose.ui.test.junit4.ComposeContentTestRule
 import androidx.compose.ui.test.junit4.createComposeRule
@@ -56,7 +57,7 @@
         }
 
         // Act.
-        focusManager.moveFocusIn()
+        focusManager.moveFocus(In)
 
         // Assert.
         rule.runOnIdle {
@@ -84,7 +85,7 @@
         }
 
         // Act.
-        focusManager.moveFocusIn()
+        focusManager.moveFocus(In)
 
         // Assert.
         rule.runOnIdle {
@@ -117,7 +118,7 @@
         }
 
         // Act.
-        focusManager.moveFocusIn()
+        focusManager.moveFocus(In)
 
         // Assert.
         rule.runOnIdle {
@@ -156,7 +157,7 @@
         }
 
         // Act.
-        focusManager.moveFocusIn()
+        focusManager.moveFocus(In)
 
         // Assert.
         rule.runOnIdle {
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/TwoDimensionalFocusTraversalInitialFocusTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/TwoDimensionalFocusTraversalInitialFocusTest.kt
index b9cdd1a..2980541 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/TwoDimensionalFocusTraversalInitialFocusTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/TwoDimensionalFocusTraversalInitialFocusTest.kt
@@ -25,10 +25,10 @@
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.ui.ExperimentalComposeUiApi
 import androidx.compose.ui.Modifier
-import androidx.compose.ui.focus.FocusDirection.Down
-import androidx.compose.ui.focus.FocusDirection.Left
-import androidx.compose.ui.focus.FocusDirection.Right
-import androidx.compose.ui.focus.FocusDirection.Up
+import androidx.compose.ui.focus.FocusDirection.Companion.Down
+import androidx.compose.ui.focus.FocusDirection.Companion.Left
+import androidx.compose.ui.focus.FocusDirection.Companion.Right
+import androidx.compose.ui.focus.FocusDirection.Companion.Up
 import androidx.compose.ui.platform.LocalFocusManager
 import androidx.compose.ui.platform.LocalView
 import androidx.compose.ui.test.junit4.createComposeRule
@@ -43,14 +43,16 @@
 
 @MediumTest
 @RunWith(Parameterized::class)
-class TwoDimensionalFocusTraversalInitialFocusTest(private val focusDirection: FocusDirection) {
+class TwoDimensionalFocusTraversalInitialFocusTest(focusDirectionInt: Int) {
     @get:Rule
     val rule = createComposeRule()
 
+    private val focusDirection = FocusDirection(focusDirectionInt)
+
     companion object {
         @JvmStatic
         @Parameterized.Parameters(name = "{0}")
-        fun initParameters() = listOf(Left, Right, Up, Down)
+        fun initParameters() = listOf(Left, Right, Up, Down).map { it.value }
     }
 
     @Test
@@ -93,7 +95,7 @@
     @Test
     fun initialFocus_whenThereIsOnlyOneFocusable() {
         // Arrange.
-        var isFocused = mutableStateOf(false)
+        val isFocused = mutableStateOf(false)
         lateinit var view: View
         lateinit var focusManager: FocusManager
         rule.setContent {
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/TwoDimensionalFocusTraversalOutTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/TwoDimensionalFocusTraversalOutTest.kt
index 671cd23..9af59e7 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/TwoDimensionalFocusTraversalOutTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/TwoDimensionalFocusTraversalOutTest.kt
@@ -19,10 +19,11 @@
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.ui.ExperimentalComposeUiApi
-import androidx.compose.ui.focus.FocusDirection.Down
-import androidx.compose.ui.focus.FocusDirection.Left
-import androidx.compose.ui.focus.FocusDirection.Right
-import androidx.compose.ui.focus.FocusDirection.Up
+import androidx.compose.ui.focus.FocusDirection.Companion.Down
+import androidx.compose.ui.focus.FocusDirection.Companion.Left
+import androidx.compose.ui.focus.FocusDirection.Companion.Out
+import androidx.compose.ui.focus.FocusDirection.Companion.Right
+import androidx.compose.ui.focus.FocusDirection.Companion.Up
 import androidx.compose.ui.platform.LocalFocusManager
 import androidx.compose.ui.test.junit4.ComposeContentTestRule
 import androidx.compose.ui.test.junit4.createComposeRule
@@ -57,7 +58,7 @@
         }
 
         // Act.
-        focusManager.moveFocusOut()
+        focusManager.moveFocus(Out)
 
         // Assert.
         rule.runOnIdle {
@@ -84,7 +85,7 @@
         }
 
         // Act.
-        focusManager.moveFocusOut()
+        focusManager.moveFocus(Out)
 
         // Assert.
         rule.runOnIdle {
@@ -117,7 +118,7 @@
         }
 
         // Act.
-        focusManager.moveFocusOut()
+        focusManager.moveFocus(Out)
 
         // Assert.
         rule.runOnIdle {
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/TwoDimensionalFocusTraversalThreeItemsTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/TwoDimensionalFocusTraversalThreeItemsTest.kt
index 603bca7..faef106 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/TwoDimensionalFocusTraversalThreeItemsTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/TwoDimensionalFocusTraversalThreeItemsTest.kt
@@ -18,10 +18,10 @@
 
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.mutableStateOf
-import androidx.compose.ui.focus.FocusDirection.Down
-import androidx.compose.ui.focus.FocusDirection.Left
-import androidx.compose.ui.focus.FocusDirection.Right
-import androidx.compose.ui.focus.FocusDirection.Up
+import androidx.compose.ui.focus.FocusDirection.Companion.Down
+import androidx.compose.ui.focus.FocusDirection.Companion.Left
+import androidx.compose.ui.focus.FocusDirection.Companion.Right
+import androidx.compose.ui.focus.FocusDirection.Companion.Up
 import androidx.compose.ui.platform.LocalFocusManager
 import androidx.compose.ui.test.junit4.ComposeContentTestRule
 import androidx.compose.ui.test.junit4.createComposeRule
@@ -36,17 +36,18 @@
 private const val invalid = "Not applicable to a 2D focus search."
 
 @RunWith(Parameterized::class)
-class TwoDimensionalFocusTraversalThreeItemsTest(private val focusDirection: FocusDirection) {
+class TwoDimensionalFocusTraversalThreeItemsTest(focusDirectionInt: Int) {
     @get:Rule
     val rule = createComposeRule()
 
     private lateinit var focusManager: FocusManager
     private val initialFocus: FocusRequester = FocusRequester()
+    private val focusDirection = FocusDirection(focusDirectionInt)
 
     companion object {
         @JvmStatic
-        @Parameterized.Parameters(name = "direction={0}")
-        fun initParameters() = listOf(Left, Right, Up, Down)
+        @Parameterized.Parameters(name = "{0}")
+        fun initParameters() = listOf(Left, Right, Up, Down).map { it.value }
     }
 
     /**
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/TwoDimensionalFocusTraversalTwoItemsTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/TwoDimensionalFocusTraversalTwoItemsTest.kt
index 067199a..c5459ce 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/TwoDimensionalFocusTraversalTwoItemsTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/TwoDimensionalFocusTraversalTwoItemsTest.kt
@@ -19,10 +19,10 @@
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.ui.ExperimentalComposeUiApi
-import androidx.compose.ui.focus.FocusDirection.Down
-import androidx.compose.ui.focus.FocusDirection.Left
-import androidx.compose.ui.focus.FocusDirection.Right
-import androidx.compose.ui.focus.FocusDirection.Up
+import androidx.compose.ui.focus.FocusDirection.Companion.Down
+import androidx.compose.ui.focus.FocusDirection.Companion.Left
+import androidx.compose.ui.focus.FocusDirection.Companion.Right
+import androidx.compose.ui.focus.FocusDirection.Companion.Up
 import androidx.compose.ui.platform.LocalFocusManager
 import androidx.compose.ui.test.junit4.ComposeContentTestRule
 import androidx.compose.ui.test.junit4.createComposeRule
@@ -37,7 +37,7 @@
 private const val invalid = "Not applicable to a 2D focus search."
 
 @RunWith(Parameterized::class)
-class TwoDimensionalFocusTraversalTwoItemsTest(private val focusDirection: FocusDirection) {
+class TwoDimensionalFocusTraversalTwoItemsTest(focusDirectionInt: Int) {
     @get:Rule
     val rule = createComposeRule()
 
@@ -45,11 +45,12 @@
     private val initialFocus: FocusRequester = FocusRequester()
     private val focusedItem = mutableStateOf(false)
     private val candidate = mutableStateOf(false)
+    private val focusDirection = FocusDirection(focusDirectionInt)
 
     companion object {
         @JvmStatic
-        @Parameterized.Parameters(name = "direction={0}")
-        fun initParameters() = listOf(Left, Right, Up, Down)
+        @Parameterized.Parameters(name = "{0}")
+        fun initParameters() = listOf(Left, Right, Up, Down).map { it.value }
     }
 
     /**
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/input/key/AndroidProcessKeyInputTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/input/key/AndroidProcessKeyInputTest.kt
index 750100f..085ecbd 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/input/key/AndroidProcessKeyInputTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/input/key/AndroidProcessKeyInputTest.kt
@@ -27,8 +27,8 @@
 import androidx.compose.ui.focus.focusRequester
 import androidx.compose.ui.focus.setFocusableContent
 import androidx.compose.ui.input.key.Key.Companion.A
-import androidx.compose.ui.input.key.KeyEventType.KeyDown
-import androidx.compose.ui.input.key.KeyEventType.KeyUp
+import androidx.compose.ui.input.key.KeyEventType.Companion.KeyDown
+import androidx.compose.ui.input.key.KeyEventType.Companion.KeyUp
 import androidx.compose.ui.platform.LocalView
 import androidx.compose.ui.test.junit4.createComposeRule
 import androidx.test.filters.SmallTest
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/input/key/ProcessKeyInputTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/input/key/ProcessKeyInputTest.kt
index d719480..7aad0ba 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/input/key/ProcessKeyInputTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/input/key/ProcessKeyInputTest.kt
@@ -27,9 +27,8 @@
 import android.view.KeyEvent.ACTION_DOWN
 import android.view.KeyEvent.ACTION_UP
 import androidx.compose.ui.input.key.Key.Companion.A
-import androidx.compose.ui.input.key.KeyEventType.KeyUp
-import androidx.compose.ui.input.key.KeyEventType.KeyDown
-import androidx.compose.ui.input.key.KeyEventType.Unknown
+import androidx.compose.ui.input.key.KeyEventType.Companion.KeyDown
+import androidx.compose.ui.input.key.KeyEventType.Companion.KeyUp
 import androidx.compose.ui.test.junit4.createComposeRule
 import androidx.compose.ui.test.onRoot
 import androidx.compose.ui.test.performKeyPress
@@ -400,7 +399,7 @@
         val action = when (keyEventType) {
             KeyDown -> ACTION_DOWN
             KeyUp -> ACTION_UP
-            Unknown -> error("Unknown key event type")
+            else -> error("Unknown key event type")
         }
         return KeyEvent(AndroidKeyEvent(0L, 0L, action, keycode, 0, 0))
     }
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/input/pointer/HitPathTrackerTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/input/pointer/HitPathTrackerTest.kt
index 622151f..d74228e 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/input/pointer/HitPathTrackerTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/input/pointer/HitPathTrackerTest.kt
@@ -443,10 +443,12 @@
 
     @Test
     fun dispatchChanges_noNodes_nothingChanges() {
-        val (result, _) = hitPathTracker.dispatchChanges(internalPointerEventOf(down(5)))
+        val internalPointerEvent = internalPointerEventOf(down(5))
+
+        hitPathTracker.dispatchChanges(internalPointerEvent)
 
         PointerInputChangeSubject
-            .assertThat(result.changes.values.first())
+            .assertThat(internalPointerEvent.changes.values.first())
             .isStructurallyEqualTo(down(5))
     }
 
@@ -463,10 +465,12 @@
 
         hitPathTracker.addHitPath(PointerId(13), listOf(pif1))
 
-        val (result, _) = hitPathTracker.dispatchChanges(internalPointerEventOf(down(13)))
+        val internalPointerEvent = internalPointerEventOf(down(13))
+
+        hitPathTracker.dispatchChanges(internalPointerEvent)
 
         PointerInputChangeSubject
-            .assertThat(result.changes.values.first())
+            .assertThat(internalPointerEvent.changes.values.first())
             .isStructurallyEqualTo(down(13).apply { consumeDownChange() })
     }
 
@@ -508,7 +512,9 @@
         val expectedChange = actualChange.deepCopy()
         val consumedExpectedChange = actualChange.deepCopy().apply { consumePositionChange() }
 
-        val (result, _) = hitPathTracker.dispatchChanges(internalPointerEventOf(actualChange))
+        val internalPointerEvent = internalPointerEventOf(actualChange)
+
+        hitPathTracker.dispatchChanges(internalPointerEvent)
 
         val log1 = log.getOnPointerEventLog()
             .filter { it.pass == PointerEventPass.Initial || it.pass == PointerEventPass.Main }
@@ -570,7 +576,7 @@
         assertThat(log1[5].pass).isEqualTo(PointerEventPass.Main)
 
         PointerInputChangeSubject
-            .assertThat(result.changes.values.first())
+            .assertThat(internalPointerEvent.changes.values.first())
             .isStructurallyEqualTo(
                 consumedExpectedChange
             )
@@ -628,9 +634,9 @@
         val consumedExpectedEvent1 = expectedEvent1.deepCopy().apply { consumePositionChange() }
         val consumedExpectedEvent2 = expectedEvent2.deepCopy().apply { consumePositionChange() }
 
-        val (result, _) = hitPathTracker.dispatchChanges(
-            internalPointerEventOf(actualEvent1, actualEvent2)
-        )
+        val internalPointerEvent = internalPointerEventOf(actualEvent1, actualEvent2)
+
+        hitPathTracker.dispatchChanges(internalPointerEvent)
 
         val log1 = log.getOnPointerEventLog()
             .filter { it.pass == PointerEventPass.Initial || it.pass == PointerEventPass.Main }
@@ -712,14 +718,14 @@
             )
         assertThat(log2[3].pass).isEqualTo(PointerEventPass.Main)
 
-        assertThat(result.changes).hasSize(2)
+        assertThat(internalPointerEvent.changes).hasSize(2)
         PointerInputChangeSubject
-            .assertThat(result.changes[actualEvent1.id])
+            .assertThat(internalPointerEvent.changes[actualEvent1.id])
             .isStructurallyEqualTo(
                 consumedExpectedEvent1
             )
         PointerInputChangeSubject
-            .assertThat(result.changes[actualEvent2.id])
+            .assertThat(internalPointerEvent.changes[actualEvent2.id])
             .isStructurallyEqualTo(
                 consumedExpectedEvent2
             )
@@ -770,9 +776,9 @@
         val consumedEvent1 = expectedEvent1.deepCopy().apply { consumePositionChange() }
         val consumedEvent2 = expectedEvent2.deepCopy().apply { consumePositionChange() }
 
-        val (result, _) = hitPathTracker.dispatchChanges(
-            internalPointerEventOf(actualEvent1, actualEvent2)
-        )
+        val internalPointerEvent = internalPointerEventOf(actualEvent1, actualEvent2)
+
+        hitPathTracker.dispatchChanges(internalPointerEvent)
 
         val log1 = log.getOnPointerEventLog()
             .filter { it.pass == PointerEventPass.Initial || it.pass == PointerEventPass.Main }
@@ -823,13 +829,13 @@
             )
         assertThat(log1[5].pass).isEqualTo(PointerEventPass.Main)
 
-        assertThat(result.changes).hasSize(2)
+        assertThat(internalPointerEvent.changes).hasSize(2)
         PointerInputChangeSubject
-            .assertThat(result.changes[actualEvent1.id])
+            .assertThat(internalPointerEvent.changes[actualEvent1.id])
             .isStructurallyEqualTo(consumedEvent1)
 
         PointerInputChangeSubject
-            .assertThat(result.changes[actualEvent2.id])
+            .assertThat(internalPointerEvent.changes[actualEvent2.id])
             .isStructurallyEqualTo(consumedEvent2)
     }
 
@@ -866,9 +872,9 @@
         val consumedEvent1 = expectedEvent1.deepCopy().apply { consumePositionChange() }
         val consumedEvent2 = expectedEvent2.deepCopy().apply { consumePositionChange() }
 
-        val (result, _) = hitPathTracker.dispatchChanges(
-            internalPointerEventOf(actualEvent1, actualEvent2)
-        )
+        val internalPointerEvent = internalPointerEventOf(actualEvent1, actualEvent2)
+
+        hitPathTracker.dispatchChanges(internalPointerEvent)
 
         val log1 = log.getOnPointerEventLog()
             .filter { it.pass == PointerEventPass.Initial || it.pass == PointerEventPass.Main }
@@ -912,14 +918,14 @@
             )
         assertThat(log1[3].pass).isEqualTo(PointerEventPass.Main)
 
-        assertThat(result.changes).hasSize(2)
+        assertThat(internalPointerEvent.changes).hasSize(2)
         PointerInputChangeSubject
-            .assertThat(result.changes[actualEvent1.id])
+            .assertThat(internalPointerEvent.changes[actualEvent1.id])
             .isStructurallyEqualTo(
                 consumedEvent1
             )
         PointerInputChangeSubject
-            .assertThat(result.changes[actualEvent2.id])
+            .assertThat(internalPointerEvent.changes[actualEvent2.id])
             .isStructurallyEqualTo(
                 consumedEvent2
             )
@@ -2842,7 +2848,7 @@
 
     @Test
     fun dispatchChanges_noNodes_reportsWasDispatchedToNothing() {
-        val (_, hitSomething) = hitPathTracker.dispatchChanges(internalPointerEventOf(down(0)))
+        val hitSomething = hitPathTracker.dispatchChanges(internalPointerEventOf(down(0)))
         assertThat(hitSomething).isFalse()
     }
 
@@ -2851,7 +2857,7 @@
         val pif = PointerInputFilterMock()
         hitPathTracker.addHitPath(PointerId(13), listOf(pif))
 
-        val (_, hitSomething) = hitPathTracker.dispatchChanges(internalPointerEventOf(down(13)))
+        val hitSomething = hitPathTracker.dispatchChanges(internalPointerEventOf(down(13)))
 
         assertThat(hitSomething).isTrue()
     }
@@ -2861,7 +2867,7 @@
         val pif = PointerInputFilterMock()
         hitPathTracker.addHitPath(PointerId(13), listOf(pif))
 
-        val (_, hitSomething) = hitPathTracker.dispatchChanges(internalPointerEventOf(down(69)))
+        val hitSomething = hitPathTracker.dispatchChanges(internalPointerEventOf(down(69)))
 
         assertThat(hitSomething).isFalse()
     }
@@ -3109,7 +3115,7 @@
         if (actualNode.children.size != expectedNode.children.size) {
             return false
         }
-        for (child in actualNode.children) {
+        actualNode.children.forEach { child ->
             check = check && expectedNode.children.any {
                 areEqual(child, it)
             }
@@ -3137,7 +3143,7 @@
         if (actualNode.children.size != expectedNode.children.size) {
             return false
         }
-        for (child in actualNode.children) {
+        actualNode.children.forEach { child ->
             check = check && expectedNode.children.any {
                 areEqual(child, it)
             }
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/input/pointer/MotionEventAdapterTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/input/pointer/MotionEventAdapterTest.kt
index b55dd28..db78ad7 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/input/pointer/MotionEventAdapterTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/input/pointer/MotionEventAdapterTest.kt
@@ -1050,25 +1050,32 @@
             arrayOf(PointerCoords(30f, 31f))
         )
 
+        // Test the different events sequentially, since the returned event contains a list that
+        // will be reused by convertToPointerInputEvent for performance, so it shouldn't be held
+        // for longer than needed during the sequential dispatch.
+
         val pointerInputEventDown1 = motionEventAdapter.convertToPointerInputEvent(down1)
-        val pointerInputEventUp1 = motionEventAdapter.convertToPointerInputEvent(up1)
-        val pointerInputEventDown2 = motionEventAdapter.convertToPointerInputEvent(down2)
-        val pointerInputEventUp2 = motionEventAdapter.convertToPointerInputEvent(up2)
-        val pointerInputEventDown3 = motionEventAdapter.convertToPointerInputEvent(down3)
-        val pointerInputEventUp3 = motionEventAdapter.convertToPointerInputEvent(up3)
-
         assertThat(pointerInputEventDown1).isNotNull()
-        assertThat(pointerInputEventUp1).isNotNull()
-        assertThat(pointerInputEventDown2).isNotNull()
-        assertThat(pointerInputEventUp2).isNotNull()
-        assertThat(pointerInputEventDown3).isNotNull()
-        assertThat(pointerInputEventUp3).isNotNull()
-
         assertThat(pointerInputEventDown1!!.pointers[0].id).isEqualTo(PointerId(0))
+
+        val pointerInputEventUp1 = motionEventAdapter.convertToPointerInputEvent(up1)
+        assertThat(pointerInputEventUp1).isNotNull()
         assertThat(pointerInputEventUp1!!.pointers[0].id).isEqualTo(PointerId(0))
+
+        val pointerInputEventDown2 = motionEventAdapter.convertToPointerInputEvent(down2)
+        assertThat(pointerInputEventDown2).isNotNull()
         assertThat(pointerInputEventDown2!!.pointers[0].id).isEqualTo(PointerId(1))
+
+        val pointerInputEventUp2 = motionEventAdapter.convertToPointerInputEvent(up2)
+        assertThat(pointerInputEventUp2).isNotNull()
         assertThat(pointerInputEventUp2!!.pointers[0].id).isEqualTo(PointerId(1))
+
+        val pointerInputEventDown3 = motionEventAdapter.convertToPointerInputEvent(down3)
+        assertThat(pointerInputEventDown3).isNotNull()
         assertThat(pointerInputEventDown3!!.pointers[0].id).isEqualTo(PointerId(2))
+
+        val pointerInputEventUp3 = motionEventAdapter.convertToPointerInputEvent(up3)
+        assertThat(pointerInputEventUp3).isNotNull()
         assertThat(pointerInputEventUp3!!.pointers[0].id).isEqualTo(PointerId(2))
     }
 
@@ -1119,21 +1126,26 @@
             )
         )
 
+        // Test the different events sequentially, since the returned event contains a list that
+        // will be reused by convertToPointerInputEvent for performance, so it shouldn't be held
+        // for longer than needed during the sequential dispatch.
+
         val pointerInputEventDown1 = motionEventAdapter.convertToPointerInputEvent(down1)
-        val pointerInputEventDown2 = motionEventAdapter.convertToPointerInputEvent(down2)
-        val pointerInputEventDown3 = motionEventAdapter.convertToPointerInputEvent(down3)
 
         assertThat(pointerInputEventDown1).isNotNull()
-        assertThat(pointerInputEventDown2).isNotNull()
-        assertThat(pointerInputEventDown3).isNotNull()
-
         assertThat(pointerInputEventDown1!!.pointers).hasSize(1)
         assertThat(pointerInputEventDown1.pointers[0].id).isEqualTo(PointerId(0))
 
+        val pointerInputEventDown2 = motionEventAdapter.convertToPointerInputEvent(down2)
+
+        assertThat(pointerInputEventDown2).isNotNull()
         assertThat(pointerInputEventDown2!!.pointers).hasSize(2)
         assertThat(pointerInputEventDown2.pointers[0].id).isEqualTo(PointerId(0))
         assertThat(pointerInputEventDown2.pointers[1].id).isEqualTo(PointerId(1))
 
+        val pointerInputEventDown3 = motionEventAdapter.convertToPointerInputEvent(down3)
+
+        assertThat(pointerInputEventDown3).isNotNull()
         assertThat(pointerInputEventDown3!!.pointers).hasSize(3)
         assertThat(pointerInputEventDown2.pointers[0].id).isEqualTo(PointerId(0))
         assertThat(pointerInputEventDown2.pointers[1].id).isEqualTo(PointerId(1))
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/input/pointer/PointerInputEventProcessorTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/input/pointer/PointerInputEventProcessorTest.kt
index 4bd3812..f67e8b7 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/input/pointer/PointerInputEventProcessorTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/input/pointer/PointerInputEventProcessorTest.kt
@@ -21,7 +21,7 @@
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.autofill.Autofill
 import androidx.compose.ui.autofill.AutofillTree
-import androidx.compose.ui.focus.FocusDirectionInternal
+import androidx.compose.ui.focus.FocusDirection
 import androidx.compose.ui.focus.FocusManager
 import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.graphics.Canvas
@@ -3085,7 +3085,7 @@
     override fun onLayoutChange(layoutNode: LayoutNode) {
     }
 
-    override fun getFocusDirection(keyEvent: KeyEvent): FocusDirectionInternal? {
+    override fun getFocusDirection(keyEvent: KeyEvent): FocusDirection? {
         TODO("Not yet implemented")
     }
 
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/input/pointer/SuspendingPointerInputFilterTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/input/pointer/SuspendingPointerInputFilterTest.kt
index 9046500..49c2c27 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/input/pointer/SuspendingPointerInputFilterTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/input/pointer/SuspendingPointerInputFilterTest.kt
@@ -22,11 +22,11 @@
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.setValue
 import androidx.compose.runtime.snapshots.Snapshot
+import androidx.compose.testutils.TestViewConfiguration
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.platform.InspectableValue
 import androidx.compose.ui.platform.ValueElement
-import androidx.compose.ui.platform.ViewConfiguration
 import androidx.compose.ui.platform.isDebugInspectorInfoEnabled
 import androidx.compose.ui.test.TestActivity
 import androidx.compose.ui.unit.IntSize
@@ -68,7 +68,7 @@
 
     @Test
     fun testAwaitSingleEvent(): Unit = runBlockingTest {
-        val filter = SuspendingPointerInputFilter(FakeViewConfiguration())
+        val filter = SuspendingPointerInputFilter(TestViewConfiguration())
 
         val result = CompletableDeferred<PointerEvent>()
         launch {
@@ -97,7 +97,7 @@
 
     @Test
     fun testAwaitSeveralEvents(): Unit = runBlockingTest {
-        val filter = SuspendingPointerInputFilter(FakeViewConfiguration())
+        val filter = SuspendingPointerInputFilter(TestViewConfiguration())
         val results = Channel<PointerEvent>(Channel.UNLIMITED)
         launch {
             with(filter) {
@@ -132,7 +132,7 @@
 
     @Test
     fun testSyntheticCancelEvent(): Unit = runBlockingTest {
-        val filter = SuspendingPointerInputFilter(FakeViewConfiguration())
+        val filter = SuspendingPointerInputFilter(TestViewConfiguration())
         val results = Channel<PointerEvent>(Channel.UNLIMITED)
         launch {
             with(filter) {
@@ -200,7 +200,7 @@
 
     @Test
     fun testCancelledHandlerBlock() = runBlockingTest {
-        val filter = SuspendingPointerInputFilter(FakeViewConfiguration())
+        val filter = SuspendingPointerInputFilter(TestViewConfiguration())
         val counter = TestCounter()
         val handler = launch {
             with(filter) {
@@ -305,17 +305,6 @@
     }
 }
 
-private class FakeViewConfiguration : ViewConfiguration {
-    override val longPressTimeoutMillis: Long
-        get() = 500
-    override val doubleTapTimeoutMillis: Long
-        get() = 300
-    override val doubleTapMinTimeMillis: Long
-        get() = 40
-    override val touchSlop: Float
-        get() = 18f
-}
-
 private class TestCounter {
     private var count = 0
 
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/layout/SubcomposeLayoutTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/layout/SubcomposeLayoutTest.kt
index 22e4869..88cd537 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/layout/SubcomposeLayoutTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/layout/SubcomposeLayoutTest.kt
@@ -20,17 +20,22 @@
 import android.widget.FrameLayout
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.requiredSize
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.CompositionLocalProvider
 import androidx.compose.runtime.DisposableEffect
+import androidx.compose.runtime.MutableState
+import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.setValue
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.background
 import androidx.compose.ui.draw.assertColor
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.graphics.ImageBitmap
 import androidx.compose.ui.graphics.asAndroidBitmap
+import androidx.compose.ui.layout.RootMeasurePolicy.measure
 import androidx.compose.ui.platform.AndroidOwnerExtraAssertionsRule
 import androidx.compose.ui.platform.ComposeView
 import androidx.compose.ui.platform.LocalDensity
@@ -66,6 +71,7 @@
 
     @get:Rule
     val rule = createAndroidComposeRule<TestActivity>()
+
     @get:Rule
     val excessiveAssertions = AndroidOwnerExtraAssertionsRule()
 
@@ -555,6 +561,450 @@
             stateUsedLatch.await(1, TimeUnit.SECONDS)
         )
     }
+
+    @Test
+    fun precompose() {
+        val addSlot = mutableStateOf(false)
+        var composingCounter = 0
+        var composedDuringMeasure = false
+        val state = SubcomposeLayoutState()
+        val content: @Composable () -> Unit = {
+            composingCounter++
+        }
+
+        rule.setContent {
+            SubcomposeLayout(state) {
+                if (addSlot.value) {
+                    composedDuringMeasure = true
+                    subcompose(Unit, content)
+                }
+                layout(10, 10) {}
+            }
+        }
+
+        rule.runOnIdle {
+            assertThat(composingCounter).isEqualTo(0)
+            state.precompose(Unit, content)
+        }
+
+        rule.runOnIdle {
+            assertThat(composingCounter).isEqualTo(1)
+
+            assertThat(composedDuringMeasure).isFalse()
+            addSlot.value = true
+        }
+
+        rule.runOnIdle {
+            assertThat(composedDuringMeasure).isTrue()
+            assertThat(composingCounter).isEqualTo(1)
+        }
+    }
+
+    @Test
+    fun disposePrecomposedItem() {
+        var composed = false
+        var disposed = false
+        val state = SubcomposeLayoutState()
+
+        rule.setContent {
+            SubcomposeLayout(state) {
+                layout(10, 10) {}
+            }
+        }
+
+        val slot = rule.runOnIdle {
+            state.precompose(Unit) {
+                DisposableEffect(Unit) {
+                    composed = true
+                    onDispose {
+                        disposed = true
+                    }
+                }
+            }
+        }
+
+        rule.runOnIdle {
+            assertThat(composed).isTrue()
+            assertThat(disposed).isFalse()
+
+            slot.dispose()
+        }
+
+        rule.runOnIdle {
+            assertThat(disposed).isTrue()
+        }
+    }
+
+    @Test
+    fun composeItemRegularlyAfterDisposingPrecomposedItem() {
+        val addSlot = mutableStateOf(false)
+        var composingCounter = 0
+        var enterCounter = 0
+        var exitCounter = 0
+        val state = SubcomposeLayoutState()
+        val content: @Composable () -> Unit = @Composable {
+            composingCounter++
+            DisposableEffect(Unit) {
+                enterCounter++
+                onDispose {
+                    exitCounter++
+                }
+            }
+        }
+
+        rule.setContent {
+            SubcomposeLayout(state) {
+                if (addSlot.value) {
+                    subcompose(Unit, content)
+                }
+                layout(10, 10) {}
+            }
+        }
+
+        val slot = rule.runOnIdle {
+            state.precompose(Unit, content)
+        }
+
+        rule.runOnIdle {
+            slot.dispose()
+        }
+
+        rule.runOnIdle {
+            assertThat(composingCounter).isEqualTo(1)
+            assertThat(enterCounter).isEqualTo(1)
+            assertThat(exitCounter).isEqualTo(1)
+
+            addSlot.value = true
+        }
+
+        rule.runOnIdle {
+            assertThat(composingCounter).isEqualTo(2)
+            assertThat(enterCounter).isEqualTo(2)
+            assertThat(exitCounter).isEqualTo(1)
+        }
+    }
+
+    @Test
+    fun precomposeTwoItems() {
+        val addSlots = mutableStateOf(false)
+        var composing1Counter = 0
+        var composing2Counter = 0
+        val state = SubcomposeLayoutState()
+        val content1: @Composable () -> Unit = {
+            composing1Counter++
+        }
+        val content2: @Composable () -> Unit = {
+            composing2Counter++
+        }
+
+        rule.setContent {
+            SubcomposeLayout(state) {
+                subcompose(0) { }
+                if (addSlots.value) {
+                    subcompose(1, content1)
+                    subcompose(2, content2)
+                }
+                subcompose(3) { }
+                layout(10, 10) {}
+            }
+        }
+
+        rule.runOnIdle {
+            assertThat(composing1Counter).isEqualTo(0)
+            assertThat(composing2Counter).isEqualTo(0)
+            state.precompose(1, content1)
+            state.precompose(2, content2)
+        }
+
+        rule.runOnIdle {
+            assertThat(composing1Counter).isEqualTo(1)
+            assertThat(composing2Counter).isEqualTo(1)
+            addSlots.value = true
+        }
+
+        rule.runOnIdle {
+            assertThat(composing1Counter).isEqualTo(1)
+            assertThat(composing2Counter).isEqualTo(1)
+        }
+    }
+
+    @Test
+    fun precomposedItemDisposedWhenSubcomposeLayoutIsDisposed() {
+        val emitLayout = mutableStateOf(true)
+        var enterCounter = 0
+        var exitCounter = 0
+        val state = SubcomposeLayoutState()
+        val content: @Composable () -> Unit = @Composable {
+            DisposableEffect(Unit) {
+                enterCounter++
+                onDispose {
+                    exitCounter++
+                }
+            }
+        }
+
+        rule.setContent {
+            if (emitLayout.value) {
+                SubcomposeLayout(state) {
+                    layout(10, 10) {}
+                }
+            }
+        }
+
+        rule.runOnIdle {
+            state.precompose(Unit, content)
+        }
+
+        rule.runOnIdle {
+            assertThat(enterCounter).isEqualTo(1)
+            assertThat(exitCounter).isEqualTo(0)
+            emitLayout.value = false
+        }
+
+        rule.runOnIdle {
+            assertThat(exitCounter).isEqualTo(1)
+        }
+    }
+
+    @Test
+    fun precomposeIsNotTriggeringParentRemeasure() {
+        val state = SubcomposeLayoutState()
+
+        var measureCount = 0
+        var layoutCount = 0
+
+        rule.setContent {
+            SubcomposeLayout(state) {
+                measureCount++
+                layout(10, 10) {
+                    layoutCount++
+                }
+            }
+        }
+
+        rule.runOnIdle {
+            assertThat(measureCount).isEqualTo(1)
+            assertThat(layoutCount).isEqualTo(1)
+            state.precompose(Unit) {
+                Box(Modifier.fillMaxSize())
+            }
+        }
+
+        rule.runOnIdle {
+            assertThat(measureCount).isEqualTo(1)
+            assertThat(layoutCount).isEqualTo(1)
+        }
+    }
+
+    @Test
+    fun precomposedItemDisposalIsNotTriggeringParentRemeasure() {
+        val state = SubcomposeLayoutState()
+
+        var measureCount = 0
+        var layoutCount = 0
+
+        rule.setContent {
+            SubcomposeLayout(state) {
+                measureCount++
+                layout(10, 10) {
+                    layoutCount++
+                }
+            }
+        }
+
+        rule.runOnIdle {
+            assertThat(measureCount).isEqualTo(1)
+            assertThat(layoutCount).isEqualTo(1)
+            val handle = state.precompose(Unit) {
+                Box(Modifier.fillMaxSize())
+            }
+            handle.dispose()
+        }
+
+        rule.runOnIdle {
+            assertThat(measureCount).isEqualTo(1)
+            assertThat(layoutCount).isEqualTo(1)
+        }
+    }
+
+    @Test
+    fun slotsKeptForReuse() {
+        val items = mutableStateOf(listOf(0, 1, 2, 3, 4))
+        val state = SubcomposeLayoutState(2)
+
+        composeItems(state, items)
+
+        rule.runOnIdle {
+            items.value = listOf(2, 3)
+        }
+
+        assertNodes(
+            exists = /*active*/ listOf(2, 3) + /*reusable*/ listOf(1, 4),
+            doesNotExist = /*disposed*/ listOf(0)
+        )
+    }
+
+    @Test
+    fun newSlotIsUsingReusedSlot() {
+        val items = mutableStateOf(listOf(0, 1, 2, 3, 4))
+        val state = SubcomposeLayoutState(2)
+
+        composeItems(state, items)
+
+        rule.runOnIdle {
+            items.value = listOf(2, 3)
+            // 1 and 4 are now in reusable buffer
+        }
+
+        rule.runOnIdle {
+            items.value = listOf(2, 3, 5)
+            // the last reusable slot (4) will be used for composing 5
+        }
+
+        assertNodes(
+            exists = /*active*/ listOf(2, 3, 5) + /*reusable*/ listOf(1),
+            doesNotExist = /*disposed*/ listOf(0, 4)
+        )
+    }
+
+    @Test
+    fun theSameSlotIsUsedWhileItIsInReusableList() {
+        val items = mutableStateOf(listOf(0, 1, 2, 3, 4))
+        val state = SubcomposeLayoutState(2)
+
+        composeItems(state, items)
+
+        rule.runOnIdle {
+            items.value = listOf(2, 3)
+            // 1 and 4 are now in reusable buffer
+        }
+
+        rule.runOnIdle {
+            items.value = listOf(2, 3, 1)
+            // slot 1 should be taken back from reusable
+        }
+
+        assertNodes(
+            exists = /*active*/ listOf(2, 3, 1) + /*reusable*/ listOf(4)
+        )
+    }
+
+    @Test
+    fun prefetchIsUsingReusableNodes() {
+        val items = mutableStateOf(listOf(0, 1, 2, 3, 4))
+        val state = SubcomposeLayoutState(2)
+
+        composeItems(state, items)
+
+        rule.runOnIdle {
+            items.value = listOf(2, 3)
+            // 1 and 4 are now in reusable buffer
+        }
+
+        rule.runOnIdle {
+            state.precompose(5) {
+                ItemContent(5)
+            }
+            // prefetch should take slot 4 from reuse
+        }
+
+        assertNodes(
+            exists = /*active*/ listOf(2, 3) + /*prefetch*/ listOf(5) + /*reusable*/ listOf(1)
+        )
+    }
+
+    @Test
+    fun prefetchSlotWhichIsInReusableList() {
+        val items = mutableStateOf(listOf(0, 1, 2, 3, 4))
+        val state = SubcomposeLayoutState(3)
+
+        composeItems(state, items)
+
+        rule.runOnIdle {
+            items.value = listOf(2)
+            // 1, 3 and 4 are now in reusable buffer
+        }
+
+        rule.runOnIdle {
+            state.precompose(3) {
+                ItemContent(3)
+            }
+            // prefetch should take slot 3 from reuse
+        }
+
+        assertNodes(
+            exists = /*active*/ listOf(2) + /*prefetch*/ listOf(3) + /*reusable*/ listOf(1, 4),
+            doesNotExist = listOf(0)
+        )
+    }
+
+    @Test
+    fun nothingIsReusedWhenMaxSlotsAre0() {
+        val items = mutableStateOf(listOf(0, 1, 2, 3, 4))
+        val state = SubcomposeLayoutState(0)
+
+        composeItems(state, items)
+
+        rule.runOnIdle {
+            items.value = listOf(2, 4)
+        }
+
+        assertNodes(
+            exists = listOf(2, 4),
+            doesNotExist = listOf(0, 1, 3)
+        )
+    }
+
+    @Test
+    fun reuse1Node() {
+        val items = mutableStateOf(listOf(0, 1, 2, 3))
+        val state = SubcomposeLayoutState(1)
+
+        composeItems(state, items)
+
+        rule.runOnIdle {
+            items.value = listOf(0, 1)
+        }
+
+        assertNodes(
+            exists = /*active*/ listOf(0, 1) + /*reusable*/ listOf(3),
+            doesNotExist = /*disposed*/ listOf(2)
+        )
+    }
+
+    private fun composeItems(
+        state: SubcomposeLayoutState,
+        items: MutableState<List<Int>>
+    ) {
+        rule.setContent {
+            SubcomposeLayout(state) { constraints ->
+                items.value.forEach {
+                    subcompose(it) {
+                        ItemContent(it)
+                    }.forEach {
+                        it.measure(constraints)
+                    }
+                }
+                layout(10, 10) {}
+            }
+        }
+    }
+
+    @Composable
+    private fun ItemContent(index: Int) {
+        Box(Modifier.fillMaxSize().testTag("$index"))
+    }
+
+    private fun assertNodes(exists: List<Int>, doesNotExist: List<Int> = emptyList()) {
+        exists.forEach {
+            rule.onNodeWithTag("$it")
+                .assertExists()
+        }
+        doesNotExist.forEach {
+            rule.onNodeWithTag("$it")
+                .assertDoesNotExist()
+        }
+    }
 }
 
 fun ImageBitmap.assertCenterPixelColor(expectedColor: Color) {
diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/input/key/KeyEvent.android.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/input/key/KeyEvent.android.kt
index fec9aaa..e0a35a3 100644
--- a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/input/key/KeyEvent.android.kt
+++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/input/key/KeyEvent.android.kt
@@ -18,9 +18,9 @@
 
 import android.view.KeyEvent.ACTION_DOWN
 import android.view.KeyEvent.ACTION_UP
-import androidx.compose.ui.input.key.KeyEventType.KeyDown
-import androidx.compose.ui.input.key.KeyEventType.KeyUp
-import androidx.compose.ui.input.key.KeyEventType.Unknown
+import androidx.compose.ui.input.key.KeyEventType.Companion.KeyDown
+import androidx.compose.ui.input.key.KeyEventType.Companion.KeyUp
+import androidx.compose.ui.input.key.KeyEventType.Companion.Unknown
 
 /**
  * The native Android [KeyEvent][NativeKeyEvent].
diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/input/pointer/MotionEventAdapter.android.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/input/pointer/MotionEventAdapter.android.kt
index 1284beb..7ebf120 100644
--- a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/input/pointer/MotionEventAdapter.android.kt
+++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/input/pointer/MotionEventAdapter.android.kt
@@ -43,6 +43,8 @@
     @VisibleForTesting
     internal val motionEventToComposePointerIdMap: MutableMap<Int, PointerId> = mutableMapOf()
 
+    private val pointers: MutableList<PointerInputEventData> = mutableListOf()
+
     /**
      * Converts a single [MotionEvent] from an Android event stream into a [PointerInputEvent], or
      * null if the [MotionEvent.getActionMasked] is [ACTION_CANCEL].
@@ -76,12 +78,10 @@
             else -> null
         }
 
-        // TODO(shepshapard): Avoid allocating for every event.
-        val pointers: MutableList<PointerInputEventData> = mutableListOf()
+        pointers.clear()
 
         // This converts the MotionEvent into a list of PointerInputEventData, and updates
         // internal record keeping.
-        @Suppress("NAME_SHADOWING")
         for (i in 0 until motionEvent.pointerCount) {
             pointers.add(
                 createPointerInputEventData(
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 ab7fbac..cdff2ff 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
@@ -19,7 +19,6 @@
 import android.annotation.SuppressLint
 import android.content.Context
 import android.content.res.Configuration
-import android.graphics.Matrix
 import android.graphics.Rect
 import android.os.Build
 import android.os.Looper
@@ -52,20 +51,21 @@
 import androidx.compose.ui.autofill.unregisterCallback
 import androidx.compose.ui.focus.FOCUS_TAG
 import androidx.compose.ui.focus.FocusDirection
-import androidx.compose.ui.focus.FocusDirectionInternal
-import androidx.compose.ui.focus.FocusDirectionInternal.Down
-import androidx.compose.ui.focus.FocusDirectionInternal.In
-import androidx.compose.ui.focus.FocusDirectionInternal.Left
-import androidx.compose.ui.focus.FocusDirectionInternal.Next
-import androidx.compose.ui.focus.FocusDirectionInternal.Out
-import androidx.compose.ui.focus.FocusDirectionInternal.Previous
-import androidx.compose.ui.focus.FocusDirectionInternal.Right
-import androidx.compose.ui.focus.FocusDirectionInternal.Up
+import androidx.compose.ui.focus.FocusDirection.Companion.Down
+import androidx.compose.ui.focus.FocusDirection.Companion.In
+import androidx.compose.ui.focus.FocusDirection.Companion.Left
+import androidx.compose.ui.focus.FocusDirection.Companion.Next
+import androidx.compose.ui.focus.FocusDirection.Companion.Out
+import androidx.compose.ui.focus.FocusDirection.Companion.Previous
+import androidx.compose.ui.focus.FocusDirection.Companion.Right
+import androidx.compose.ui.focus.FocusDirection.Companion.Up
 import androidx.compose.ui.focus.FocusManager
 import androidx.compose.ui.focus.FocusManagerImpl
 import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.graphics.Canvas
 import androidx.compose.ui.graphics.CanvasHolder
+import androidx.compose.ui.graphics.Matrix
+import androidx.compose.ui.graphics.setFrom
 import androidx.compose.ui.hapticfeedback.AndroidHapticFeedback
 import androidx.compose.ui.hapticfeedback.HapticFeedback
 import androidx.compose.ui.input.key.Key.Companion.Back
@@ -76,7 +76,7 @@
 import androidx.compose.ui.input.key.Key.Companion.DirectionUp
 import androidx.compose.ui.input.key.Key.Companion.Tab
 import androidx.compose.ui.input.key.KeyEvent
-import androidx.compose.ui.input.key.KeyEventType.KeyDown
+import androidx.compose.ui.input.key.KeyEventType.Companion.KeyDown
 import androidx.compose.ui.input.key.KeyInputModifier
 import androidx.compose.ui.input.key.isShiftPressed
 import androidx.compose.ui.input.key.key
@@ -163,21 +163,8 @@
             val focusDirection = getFocusDirection(it)
             if (focusDirection == null || it.type != KeyDown) return@KeyInputModifier false
 
-            val focusMoveSuccess = with(focusManager) {
-                when (focusDirection) {
-                    Up -> moveFocus(FocusDirection.Up)
-                    Down -> moveFocus(FocusDirection.Down)
-                    Left -> moveFocus(FocusDirection.Left)
-                    Right -> moveFocus(FocusDirection.Right)
-                    In -> moveFocusIn()
-                    Out -> moveFocusOut()
-                    Next -> moveFocus(FocusDirection.Next)
-                    Previous -> moveFocus(FocusDirection.Previous)
-                }
-            }
-
             // Consume the key event if we moved focus.
-            focusMoveSuccess
+            focusManager.moveFocus(focusDirection)
         },
         onPreviewKeyEvent = null
     )
@@ -279,9 +266,9 @@
     private var globalPosition: IntOffset = IntOffset.Zero
 
     private val tmpPositionArray = intArrayOf(0, 0)
-    private val tmpOffsetArray = floatArrayOf(0f, 0f)
     private val viewToWindowMatrix = Matrix()
     private val windowToViewMatrix = Matrix()
+    private val tmpCalculationMatrix = Matrix()
     private var lastMatrixRecalculationAnimationTime = -1L
 
     /**
@@ -633,7 +620,7 @@
         accessibilityDelegate.onLayoutChange(layoutNode)
     }
 
-    override fun getFocusDirection(keyEvent: KeyEvent): FocusDirectionInternal? {
+    override fun getFocusDirection(keyEvent: KeyEvent): FocusDirection? {
         return when (keyEvent.key) {
             Tab -> if (keyEvent.isShiftPressed) Previous else Next
             DirectionRight -> Right
@@ -796,23 +783,18 @@
 
     override fun localToScreen(localPosition: Offset): Offset {
         recalculateWindowPosition()
-        val points = tmpOffsetArray
-        points[0] = localPosition.x
-        points[1] = localPosition.y
-        viewToWindowMatrix.mapPoints(points)
+        val local = viewToWindowMatrix.map(localPosition)
         return Offset(
-            points[0] + windowPosition.x,
-            points[1] + windowPosition.y
+            local.x + windowPosition.x,
+            local.y + windowPosition.y
         )
     }
 
     override fun screenToLocal(positionOnScreen: Offset): Offset {
         recalculateWindowPosition()
-        val points = tmpOffsetArray
-        points[0] = positionOnScreen.x - windowPosition.x
-        points[1] = positionOnScreen.y - windowPosition.y
-        windowToViewMatrix.mapPoints(points)
-        return Offset(points[0], points[1])
+        val x = positionOnScreen.x - windowPosition.x
+        val y = positionOnScreen.y - windowPosition.y
+        return windowToViewMatrix.map(Offset(x, y))
     }
 
     private fun recalculateWindowPosition() {
@@ -839,7 +821,7 @@
     private fun recalculateWindowViewTransforms() {
         viewToWindowMatrix.reset()
         transformMatrixToWindow(this, viewToWindowMatrix)
-        viewToWindowMatrix.invert(windowToViewMatrix)
+        viewToWindowMatrix.invertTo(windowToViewMatrix)
     }
 
     override fun onCheckIsTextEditor(): Boolean = textInputServiceAndroid.isEditorFocused()
@@ -849,20 +831,12 @@
 
     override fun calculateLocalPosition(positionInWindow: Offset): Offset {
         recalculateWindowPosition()
-        val points = tmpOffsetArray
-        points[0] = positionInWindow.x
-        points[1] = positionInWindow.y
-        windowToViewMatrix.mapPoints(points)
-        return Offset(points[0], points[1])
+        return windowToViewMatrix.map(positionInWindow)
     }
 
     override fun calculatePositionInWindow(localPosition: Offset): Offset {
         recalculateWindowPosition()
-        val points = tmpOffsetArray
-        points[0] = localPosition.x
-        points[1] = localPosition.y
-        viewToWindowMatrix.mapPoints(points)
-        return Offset(points[0], points[1])
+        return viewToWindowMatrix.map(localPosition)
     }
 
     override fun onConfigurationChanged(newConfig: Configuration) {
@@ -922,6 +896,7 @@
      * Q and later, AccessibilityInteractionController#findViewByAccessibilityId uses
      * AccessibilityNodeIdManager and findViewByAccessibilityIdTraversal is only used by autofill.
      */
+    @Suppress("unused")
     fun findViewByAccessibilityIdTraversal(accessibilityId: Int): View? {
         try {
             // AccessibilityInteractionController#findViewByAccessibilityId doesn't call this
@@ -965,6 +940,24 @@
         }
     }
 
+    /**
+     * Like [android.graphics.Matrix.preConcat], for a Compose [Matrix] that accepts an [other]
+     * [android.graphics.Matrix].
+     */
+    private fun Matrix.preConcat(other: android.graphics.Matrix) {
+        tmpCalculationMatrix.setFrom(other)
+        preTransform(tmpCalculationMatrix)
+    }
+
+    /**
+     * Like [android.graphics.Matrix.preTranslate], for a Compose [Matrix]
+     */
+    private fun Matrix.preTranslate(x: Float, y: Float) {
+        tmpCalculationMatrix.reset()
+        tmpCalculationMatrix.translate(x, y)
+        preTransform(tmpCalculationMatrix)
+    }
+
     companion object {
         private var systemPropertiesClass: Class<*>? = null
         private var getBooleanMethod: Method? = null
@@ -1041,3 +1034,105 @@
         view.defaultFocusHighlightEnabled = defaultFocusHighlightEnabled
     }
 }
+
+/**
+ * Sets this [Matrix] to be the result of this * [other]
+ */
+private fun Matrix.preTransform(other: Matrix) {
+    val v00 = dot(other, 0, this, 0)
+    val v01 = dot(other, 0, this, 1)
+    val v02 = dot(other, 0, this, 2)
+    val v03 = dot(other, 0, this, 3)
+    val v10 = dot(other, 1, this, 0)
+    val v11 = dot(other, 1, this, 1)
+    val v12 = dot(other, 1, this, 2)
+    val v13 = dot(other, 1, this, 3)
+    val v20 = dot(other, 2, this, 0)
+    val v21 = dot(other, 2, this, 1)
+    val v22 = dot(other, 2, this, 2)
+    val v23 = dot(other, 2, this, 3)
+    val v30 = dot(other, 3, this, 0)
+    val v31 = dot(other, 3, this, 1)
+    val v32 = dot(other, 3, this, 2)
+    val v33 = dot(other, 3, this, 3)
+    this[0, 0] = v00
+    this[0, 1] = v01
+    this[0, 2] = v02
+    this[0, 3] = v03
+    this[1, 0] = v10
+    this[1, 1] = v11
+    this[1, 2] = v12
+    this[1, 3] = v13
+    this[2, 0] = v20
+    this[2, 1] = v21
+    this[2, 2] = v22
+    this[2, 3] = v23
+    this[3, 0] = v30
+    this[3, 1] = v31
+    this[3, 2] = v32
+    this[3, 3] = v33
+}
+
+// Taken from Matrix.kt
+private fun dot(m1: Matrix, row: Int, m2: Matrix, column: Int): Float {
+    return m1[row, 0] * m2[0, column] +
+        m1[row, 1] * m2[1, column] +
+        m1[row, 2] * m2[2, column] +
+        m1[row, 3] * m2[3, column]
+}
+
+/**
+ * Sets [other] to be the inverse of this
+ */
+private fun Matrix.invertTo(other: Matrix) {
+    val a00 = this[0, 0]
+    val a01 = this[0, 1]
+    val a02 = this[0, 2]
+    val a03 = this[0, 3]
+    val a10 = this[1, 0]
+    val a11 = this[1, 1]
+    val a12 = this[1, 2]
+    val a13 = this[1, 3]
+    val a20 = this[2, 0]
+    val a21 = this[2, 1]
+    val a22 = this[2, 2]
+    val a23 = this[2, 3]
+    val a30 = this[3, 0]
+    val a31 = this[3, 1]
+    val a32 = this[3, 2]
+    val a33 = this[3, 3]
+    val b00 = a00 * a11 - a01 * a10
+    val b01 = a00 * a12 - a02 * a10
+    val b02 = a00 * a13 - a03 * a10
+    val b03 = a01 * a12 - a02 * a11
+    val b04 = a01 * a13 - a03 * a11
+    val b05 = a02 * a13 - a03 * a12
+    val b06 = a20 * a31 - a21 * a30
+    val b07 = a20 * a32 - a22 * a30
+    val b08 = a20 * a33 - a23 * a30
+    val b09 = a21 * a32 - a22 * a31
+    val b10 = a21 * a33 - a23 * a31
+    val b11 = a22 * a33 - a23 * a32
+    val det =
+        (b00 * b11 - b01 * b10 + b02 * b09 + b03 * b08 - b04 * b07 + b05 * b06)
+    if (det == 0.0f) {
+        return
+    }
+    val invDet = 1.0f / det
+    other[0, 0] = ((a11 * b11 - a12 * b10 + a13 * b09) * invDet)
+    other[0, 1] = ((-a01 * b11 + a02 * b10 - a03 * b09) * invDet)
+    other[0, 2] = ((a31 * b05 - a32 * b04 + a33 * b03) * invDet)
+    other[0, 3] = ((-a21 * b05 + a22 * b04 - a23 * b03) * invDet)
+    other[1, 0] = ((-a10 * b11 + a12 * b08 - a13 * b07) * invDet)
+    other[1, 1] = ((a00 * b11 - a02 * b08 + a03 * b07) * invDet)
+    other[1, 2] = ((-a30 * b05 + a32 * b02 - a33 * b01) * invDet)
+    other[1, 3] = ((a20 * b05 - a22 * b02 + a23 * b01) * invDet)
+    other[2, 0] = ((a10 * b10 - a11 * b08 + a13 * b06) * invDet)
+    other[2, 1] = ((-a00 * b10 + a01 * b08 - a03 * b06) * invDet)
+    other[2, 2] = ((a30 * b04 - a31 * b02 + a33 * b00) * invDet)
+    other[2, 3] = ((-a20 * b04 + a21 * b02 - a23 * b00) * invDet)
+    other[3, 0] = ((-a10 * b09 + a11 * b07 - a12 * b06) * invDet)
+    other[3, 1] = ((a00 * b09 - a01 * b07 + a02 * b06) * invDet)
+    other[3, 2] = ((-a30 * b03 + a31 * b01 - a32 * b00) * invDet)
+    other[3, 3] = ((a20 * b03 - a21 * b01 + a22 * b00) * invDet)
+}
diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeViewAccessibilityDelegateCompat.android.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeViewAccessibilityDelegateCompat.android.kt
index 29e2ebb..a11842b 100644
--- a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeViewAccessibilityDelegateCompat.android.kt
+++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeViewAccessibilityDelegateCompat.android.kt
@@ -1390,17 +1390,17 @@
                     // are covered and which nodes are not, so the currentSemanticsNodes is not
                     // up to date anymore.
                     // After the subtree events are sent, accessibility services will get the
-                    // current visible/invisible state. We also update our copy here so that our
-                    // incremental changes (represented by accessibility events) are consistent
+                    // current visible/invisible state. We also try to do semantics tree diffing
+                    // to send out the proper accessibility events and update our copy here so that
+                    // our incremental changes (represented by accessibility events) are consistent
                     // with accessibility services. That is: change - notify - new change -
-                    // notify, if we don't update our copy here, we will combine change and new
-                    // change, which is missing finer-grained notification.
-                    // Note that we could update our copy before this delay by posting an update
-                    // copy runnable in onLayoutChange(a code version is in aosp/1553311), similar
-                    // to semanticsChangeChecker, but I think update copy after the subtree
-                    // change events are sent is more accurate because before accessibility
-                    // services receive subtree events, they are not aware of the subtree change.
-                    updateSemanticsNodesCopyAndPanes()
+                    // notify, if we don't do the tree diffing and update our copy here, we will
+                    // combine old change and new change, which is missing finer-grained
+                    // notification.
+                    if (!checkingForSemanticsChanges) {
+                        checkingForSemanticsChanges = true
+                        handler.post(semanticsChangeChecker)
+                    }
                 }
                 subtreeChangedLayoutNodes.clear()
                 delay(SendRecurringAccessibilityEventsIntervalMillis)
diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/text/input/InputState.android.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/text/input/InputState.android.kt
index e8fc13b..aa9fbe2 100644
--- a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/text/input/InputState.android.kt
+++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/text/input/InputState.android.kt
@@ -21,6 +21,7 @@
 internal fun TextFieldValue.toExtractedText(): ExtractedText {
     val res = ExtractedText()
     res.text = text
+    res.startOffset = 0
     res.partialEndOffset = text.length
     res.partialStartOffset = -1 // -1 means full text
     res.selectionStart = selection.min
diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/text/input/RecordingInputConnection.android.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/text/input/RecordingInputConnection.android.kt
index 8d3b70f..ef70243 100644
--- a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/text/input/RecordingInputConnection.android.kt
+++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/text/input/RecordingInputConnection.android.kt
@@ -55,7 +55,7 @@
     @VisibleForTesting
     internal var mTextFieldValue: TextFieldValue = initState
         set(value) {
-            if (DEBUG) { Log.d(TAG, "New InputState has set: $value -> $mTextFieldValue") }
+            if (DEBUG) { Log.d(TAG, "RecordingInputConnection.mTextFieldValue : $field -> $value") }
             field = value
         }
 
@@ -74,6 +74,9 @@
      */
     private var extractedTextMonitorMode = false
 
+    // The recoding editing ops.
+    private val editCommands = mutableListOf<EditCommand>()
+
     /**
      * Updates the input state and tells it to the IME.
      *
@@ -81,7 +84,7 @@
      * contents has changed if needed.
      */
     fun updateInputState(state: TextFieldValue, imm: InputMethodManager, view: View) {
-        if (DEBUG) { Log.d(TAG, "updateInputState: $state") }
+        if (DEBUG) { Log.d(TAG, "RecordingInputConnection.updateInputState: $state") }
         mTextFieldValue = state
 
         if (extractedTextMonitorMode) {
@@ -94,7 +97,7 @@
         if (DEBUG) {
             Log.d(
                 TAG,
-                "updateSelection(" +
+                "RecordingInputConnection.updateSelection(" +
                     "selection = (${state.selection.min},${state.selection.max}), " +
                     "composition = ($compositionStart, $compositionEnd)"
             )
@@ -104,9 +107,6 @@
         )
     }
 
-    // The recoding editing ops.
-    private val editCommands = mutableListOf<EditCommand>()
-
     // Add edit op to internal list with wrapping batch edit.
     private fun addEditCommandWithBatch(editCommand: EditCommand) {
         beginBatchEdit()
diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/text/input/TextInputServiceAndroid.android.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/text/input/TextInputServiceAndroid.android.kt
index ccb0f0d..f58d72c 100644
--- a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/text/input/TextInputServiceAndroid.android.kt
+++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/text/input/TextInputServiceAndroid.android.kt
@@ -181,11 +181,15 @@
     }
 
     override fun updateState(oldValue: TextFieldValue?, newValue: TextFieldValue) {
-        if (DEBUG) { Log.d(TAG, "InputService.updateState: $oldValue -> $newValue") }
+        if (DEBUG) {
+            Log.d(TAG, "TextInputServiceAndroid.updateState called: $oldValue -> $newValue")
+        }
 
         // update the latest TextFieldValue in InputConnection
         ic?.mTextFieldValue = newValue
 
+        if (DEBUG) { Log.d(TAG, "TextInputServiceAndroid.updateState early return") }
+
         if (oldValue == newValue) return
 
         this.state = newValue
@@ -196,7 +200,9 @@
                 (it.selection == newValue.selection && it.composition != newValue.composition)
         } ?: false
 
-        if (DEBUG) { Log.d(TAG, "InputService.updateState: $state / $restartInput(restartInput) ") }
+        if (DEBUG) {
+            Log.d(TAG, "TextInputServiceAndroid.updateState: restart($restartInput), state: $state")
+        }
 
         if (restartInput) {
             restartInput()
@@ -279,9 +285,9 @@
                 // TextView.java#setInputTypeSingleLine
                 outInfo.inputType = outInfo.inputType or InputType.TYPE_TEXT_FLAG_MULTI_LINE
 
-                // adding this flag caused b/171598334, leaving here on purpose for future reference
-                // TextView.java#onCreateInputConnection
-                // outInfo.imeOptions = outInfo.imeOptions or EditorInfo.IME_FLAG_NO_ENTER_ACTION
+                if (imeOptions.imeAction == ImeAction.Default) {
+                    outInfo.imeOptions = outInfo.imeOptions or EditorInfo.IME_FLAG_NO_ENTER_ACTION
+                }
             }
         }
 
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusManager.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusManager.kt
index f83d3e3..5c7561e 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusManager.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusManager.kt
@@ -60,25 +60,39 @@
      */
     fun moveFocus(focusDirection: FocusDirection): Boolean
 
-    // TODO(b/184086802): In 2.0, after deleting FocusDirectionInternal and adding "In" to
-    //  FocusDirection, deprecate and remove this function.
     /**
      * Moves focus to one of the children of the currently focused item.
      *
-     * @return true if focus was moved successfully.
-     */
-    @ExperimentalComposeUiApi
-    fun moveFocusIn(): Boolean
-
-    // TODO(b/184086802): In 2.0, after deleting FocusDirectionInternal and adding "Out" to
-    //  FocusDirection, deprecate and remove this function.
-    /**
-     * Moves focus to the nearest focusable parent of the currently focused item.
+     * This function is deprecated. Use FocusManager.moveFocus(FocusDirection.In) instead.
      *
      * @return true if focus was moved successfully.
      */
     @ExperimentalComposeUiApi
-    fun moveFocusOut(): Boolean
+    @Deprecated(
+        message = "Use FocusManager.moveFocus(FocusDirection.In) instead",
+        ReplaceWith(
+            "moveFocus(In)",
+            "androidx.compose.ui.focus.FocusDirection.Companion.In"
+        )
+    )
+    fun moveFocusIn(): Boolean = false
+
+    /**
+     * Moves focus to the nearest focusable parent of the currently focused item.
+     *
+     *  This function is deprecated. Use FocusManager.moveFocus(FocusDirection.Out) instead.
+     *
+     * @return true if focus was moved successfully.
+     */
+    @ExperimentalComposeUiApi
+    @Deprecated(
+        message = "Use FocusManager.moveFocus(FocusDirection.Out) instead",
+        ReplaceWith(
+            "moveFocus(Out)",
+            "androidx.compose.ui.focus.FocusDirection.Companion.Out"
+        )
+    )
+    fun moveFocusOut(): Boolean = false
 }
 
 /**
@@ -173,35 +187,7 @@
      * @return true if focus was moved successfully. false if the focused item is unchanged.
      */
     override fun moveFocus(focusDirection: FocusDirection): Boolean {
-        val internalFocusDirection = when (focusDirection) {
-            FocusDirection.Next -> FocusDirectionInternal.Next
-            FocusDirection.Previous -> FocusDirectionInternal.Previous
-            FocusDirection.Left -> FocusDirectionInternal.Left
-            FocusDirection.Right -> FocusDirectionInternal.Right
-            FocusDirection.Up -> FocusDirectionInternal.Up
-            FocusDirection.Down -> FocusDirectionInternal.Down
-        }
-        return focusModifier.focusNode.moveFocus(internalFocusDirection)
-    }
-
-    /**
-     * Moves focus to one of the children of the currently focused item.
-     *
-     * @return true if focus was moved successfully.
-     */
-    @ExperimentalComposeUiApi
-    override fun moveFocusIn(): Boolean {
-        return focusModifier.focusNode.moveFocus(FocusDirectionInternal.In)
-    }
-
-    /**
-     * Moves focus to the nearest focusable parent of the currently focused item.
-     *
-     * @return true if focus was moved successfully.
-     */
-    @ExperimentalComposeUiApi
-    override fun moveFocusOut(): Boolean {
-        return focusModifier.focusNode.moveFocus(FocusDirectionInternal.Out)
+        return focusModifier.focusNode.moveFocus(focusDirection)
     }
 }
 
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusTraversal.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusTraversal.kt
index 8a902f0..66cd24e 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusTraversal.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusTraversal.kt
@@ -16,14 +16,15 @@
 
 package androidx.compose.ui.focus
 
-import androidx.compose.ui.focus.FocusDirectionInternal.Down
-import androidx.compose.ui.focus.FocusDirectionInternal.In
-import androidx.compose.ui.focus.FocusDirectionInternal.Left
-import androidx.compose.ui.focus.FocusDirectionInternal.Next
-import androidx.compose.ui.focus.FocusDirectionInternal.Out
-import androidx.compose.ui.focus.FocusDirectionInternal.Previous
-import androidx.compose.ui.focus.FocusDirectionInternal.Right
-import androidx.compose.ui.focus.FocusDirectionInternal.Up
+import androidx.compose.ui.ExperimentalComposeUiApi
+import androidx.compose.ui.focus.FocusDirection.Companion.Down
+import androidx.compose.ui.focus.FocusDirection.Companion.In
+import androidx.compose.ui.focus.FocusDirection.Companion.Left
+import androidx.compose.ui.focus.FocusDirection.Companion.Next
+import androidx.compose.ui.focus.FocusDirection.Companion.Out
+import androidx.compose.ui.focus.FocusDirection.Companion.Previous
+import androidx.compose.ui.focus.FocusDirection.Companion.Right
+import androidx.compose.ui.focus.FocusDirection.Companion.Up
 import androidx.compose.ui.focus.FocusRequester.Companion.Default
 import androidx.compose.ui.focus.FocusState.Active
 import androidx.compose.ui.focus.FocusState.ActiveParent
@@ -35,20 +36,84 @@
 import androidx.compose.ui.unit.LayoutDirection.Ltr
 import androidx.compose.ui.unit.LayoutDirection.Rtl
 
-/**
- * This enum specifies the direction of the requested focus change.
- */
-enum class FocusDirection { Next, Previous, Left, Right, Up, Down }
+private const val invalidFocusDirection = "Invalid FocusDirection"
 
-// TODO(b/184086802): In 2.0, delete FocusDirectionInternal and add In and Out to FocusDirection.
 /**
- * This enum specifies the direction of the requested focus change.
+ * The [FocusDirection] is used to specify direction when a focus change request is made via
+ * [moveFocus].
  *
- * This enum is similar to [FocusDirection], but it contains the additional values [In] which can
- * be used to move focus from a parent to one of its children, and [Out] which moves focus to
- * the parent.
+ * @sample androidx.compose.ui.samples.MoveFocusSample
  */
-internal enum class FocusDirectionInternal { Next, Previous, Left, Right, Up, Down, In, Out }
+inline class FocusDirection(val value: Int) {
+
+    override fun toString(): String {
+        return when (this) {
+            Next -> "Next"
+            Previous -> "Previous"
+            Left -> "Left"
+            Right -> "Right"
+            Up -> "Up"
+            Down -> "Down"
+            @OptIn(ExperimentalComposeUiApi::class)
+            In -> "In"
+            @OptIn(ExperimentalComposeUiApi::class)
+            Out -> "Out"
+            else -> invalidFocusDirection
+        }
+    }
+
+    companion object {
+        /**
+         *  Direction used in [moveFocus] to indicate that you are searching for the next
+         *  focusable item.
+         */
+        val Next: FocusDirection = FocusDirection(1)
+
+        /**
+         *  Direction used in [moveFocus] to indicate that you are searching for the previous
+         *  focusable item.
+         */
+        val Previous: FocusDirection = FocusDirection(2)
+
+        /**
+         *  Direction used in [moveFocus] to indicate that you are searching for the next
+         *  focusable item to the left of the currently focused item.
+         */
+        val Left: FocusDirection = FocusDirection(3)
+
+        /**
+         *  Direction used in [moveFocus] to indicate that you are searching for the next
+         *  focusable item to the right of the currently focused item.
+         */
+        val Right: FocusDirection = FocusDirection(4)
+
+        /**
+         *  Direction used in [moveFocus] to indicate that you are searching for the next
+         *  focusable item that is above the currently focused item.
+         */
+        val Up: FocusDirection = FocusDirection(5)
+
+        /**
+         *  Direction used in [moveFocus] to indicate that you are searching for the next
+         *  focusable item that is below the currently focused item.
+         */
+        val Down: FocusDirection = FocusDirection(6)
+
+        /**
+         *  Direction used in [moveFocus] to indicate that you are searching for the next
+         *  focusable item that is a child of the currently focused item.
+         */
+        @ExperimentalComposeUiApi
+        val In: FocusDirection = FocusDirection(7)
+
+        /**
+         *  Direction used in [moveFocus] to indicate that you want to move focus to the parent
+         *  of the currently focused item.
+         */
+        @ExperimentalComposeUiApi
+        val Out: FocusDirection = FocusDirection(8)
+    }
+}
 
 /**
  * Moves focus based on the requested focus direction.
@@ -57,7 +122,7 @@
  * @return whether a focus node was found. If a focus node was found and the focus request was
  * not granted, this function still returns true.
  */
-internal fun ModifiedFocusNode.moveFocus(focusDirection: FocusDirectionInternal): Boolean {
+internal fun ModifiedFocusNode.moveFocus(focusDirection: FocusDirection): Boolean {
     // TODO(b/176847718): Pass the layout direction as a parameter instead of this hardcoded value.
     val layoutDirection: LayoutDirection = Ltr
 
@@ -81,12 +146,15 @@
     val nextNode = when (focusDirection) {
         Next, Previous -> null // TODO(b/170155659): Perform one dimensional focus search.
         Left, Right, Up, Down -> twoDimensionalFocusSearch(focusDirection)
+        @OptIn(ExperimentalComposeUiApi::class)
         In -> {
             // we search among the children of the active item.
             val direction = when (layoutDirection) { Rtl -> Left; Ltr -> Right }
             activeNode.twoDimensionalFocusSearch(direction)
         }
+        @OptIn(ExperimentalComposeUiApi::class)
         Out -> activeNode.findParentFocusNode()
+        else -> error(invalidFocusDirection)
     } ?: return false
 
     // If we found a potential next item, call requestFocus() to move focus to it.
@@ -108,7 +176,7 @@
  * children.
  */
 private fun ModifiedFocusNode.customFocusSearch(
-    focusDirection: FocusDirectionInternal,
+    focusDirection: FocusDirection,
     layoutDirection: LayoutDirection
 ): FocusRequester {
     val focusOrder = FocusOrder()
@@ -132,6 +200,10 @@
         //  the user presses dPad center. (They can also redirect the "In" to some other item).
         //  Developers can specify a custom "Out" to specify which composable should take focus
         //  when the user presses the back button.
-        In, Out -> Default
+        @OptIn(ExperimentalComposeUiApi::class)
+        In -> Default
+        @OptIn(ExperimentalComposeUiApi::class)
+        Out -> Default
+        else -> error(invalidFocusDirection)
     }
 }
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/TwoDimensionalFocusSearch.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/TwoDimensionalFocusSearch.kt
index 1fbe574..c5d8b9e 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/TwoDimensionalFocusSearch.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/TwoDimensionalFocusSearch.kt
@@ -16,10 +16,10 @@
 
 package androidx.compose.ui.focus
 
-import androidx.compose.ui.focus.FocusDirectionInternal.Down
-import androidx.compose.ui.focus.FocusDirectionInternal.Left
-import androidx.compose.ui.focus.FocusDirectionInternal.Right
-import androidx.compose.ui.focus.FocusDirectionInternal.Up
+import androidx.compose.ui.focus.FocusDirection.Companion.Down
+import androidx.compose.ui.focus.FocusDirection.Companion.Left
+import androidx.compose.ui.focus.FocusDirection.Companion.Right
+import androidx.compose.ui.focus.FocusDirection.Companion.Up
 import androidx.compose.ui.focus.FocusState.Active
 import androidx.compose.ui.focus.FocusState.ActiveParent
 import androidx.compose.ui.focus.FocusState.Captured
@@ -36,13 +36,13 @@
 
 /**
  *  Perform a search among the immediate children of this [node][ModifiedFocusNode] in the
- *  specified [direction][FocusDirectionInternal] and return the node that is to be focused next. If one
+ *  specified [direction][FocusDirection] and return the node that is to be focused next. If one
  *  of the children is currently focused, we start from that point and search in the specified
- *  [direction][FocusDirectionInternal]. If none of the children are currently focused, we pick the
- *  top-left or bottom right based on the specified [direction][FocusDirectionInternal].
+ *  [direction][FocusDirection]. If none of the children are currently focused, we pick the
+ *  top-left or bottom right based on the specified [direction][FocusDirection].
  */
 internal fun ModifiedFocusNode.twoDimensionalFocusSearch(
-    direction: FocusDirectionInternal
+    direction: FocusDirection
 ): ModifiedFocusNode? {
     return when (focusState) {
         Inactive -> this
@@ -89,7 +89,7 @@
 //  valid candidates.
 private fun List<ModifiedFocusNode>.findBestCandidate(
     focusRect: Rect,
-    direction: FocusDirectionInternal
+    direction: FocusDirection
 ): ModifiedFocusNode? {
     // Pick an impossible rectangle as the initial best candidate Rect.
     var bestCandidate = when (direction) {
@@ -118,7 +118,7 @@
     proposedCandidate: Rect,
     currentCandidate: Rect,
     focusedRect: Rect,
-    direction: FocusDirectionInternal
+    direction: FocusDirection
 ): Boolean {
 
     // Is this Rect a candidate for the next focus given the direction? This checks whether the
@@ -191,7 +191,7 @@
     source: Rect,
     rect1: Rect,
     rect2: Rect,
-    direction: FocusDirectionInternal
+    direction: FocusDirection
 ): Boolean {
     // Do the "beams" w.r.t the given direction's axis of rect1 and rect2 overlap?
     fun Rect.inSourceBeam() = when (direction) {
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/key/KeyEvent.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/key/KeyEvent.kt
index 088b52f..c14d519 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/key/KeyEvent.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/key/KeyEvent.kt
@@ -79,19 +79,31 @@
 /**
  * The type of Key Event.
  */
-enum class KeyEventType {
-    /**
-     * Unknown key event.
-     */
-    Unknown,
+inline class KeyEventType(val value: Int) {
 
-    /**
-     * Type of KeyEvent sent when the user lifts their finger off a key on the keyboard.
-     */
-    KeyUp,
+    override fun toString(): String {
+        return when (this) {
+            KeyUp -> "KeyUp"
+            KeyDown -> "KeyDown"
+            Unknown -> "Unknown"
+            else -> "Invalid"
+        }
+    }
 
-    /**
-     * Type of KeyEvent sent when the user presses down their finger on a key on the keyboard.
-     */
-    KeyDown
+    companion object {
+        /**
+         * Unknown key event.
+         */
+        val Unknown: KeyEventType = KeyEventType(0)
+
+        /**
+         * Type of KeyEvent sent when the user lifts their finger off a key on the keyboard.
+         */
+        val KeyUp: KeyEventType = KeyEventType(1)
+
+        /**
+         * Type of KeyEvent sent when the user presses down their finger on a key on the keyboard.
+         */
+        val KeyDown: KeyEventType = KeyEventType(2)
+    }
 }
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/pointer/HitPathTracker.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/pointer/HitPathTracker.kt
index ea55590..8bf9f48 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/pointer/HitPathTracker.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/pointer/HitPathTracker.kt
@@ -16,6 +16,8 @@
 
 package androidx.compose.ui.input.pointer
 
+import androidx.compose.runtime.collection.MutableVector
+import androidx.compose.runtime.collection.mutableVectorOf
 import androidx.compose.ui.layout.LayoutCoordinates
 import androidx.compose.ui.node.InternalCoreApi
 
@@ -32,23 +34,6 @@
     /*@VisibleForTesting*/
     internal val root: NodeParent = NodeParent()
 
-    private val retainedHitPaths: MutableSet<PointerId> = mutableSetOf()
-
-    internal interface DispatchChangesRetVal {
-        operator fun component1(): InternalPointerEvent
-        operator fun component2(): Boolean
-    }
-
-    private class DispatchChangesRetValImpl : DispatchChangesRetVal {
-        lateinit var internalPointerEvent: InternalPointerEvent
-        var wasDispatchedToSomething: Boolean = false
-        override operator fun component1() = internalPointerEvent
-        override operator fun component2() = wasDispatchedToSomething
-    }
-
-    // See https://youtrack.jetbrains.com/issue/KT-39905.
-    private val dispatchChangesRetVal = DispatchChangesRetValImpl()
-
     /**
      * Associates a [pointerId] to a list of hit [pointerInputFilters] and keeps track of them.
      *
@@ -67,9 +52,11 @@
         eachPin@ for (i in pointerInputFilters.indices) {
             val pointerInputFilter = pointerInputFilters[i]
             if (merging) {
-                val node = parent.children.find { it.pointerInputFilter == pointerInputFilter }
+                val node = parent.children.firstOrNull {
+                    it.pointerInputFilter == pointerInputFilter
+                }
                 if (node != null) {
-                    node.pointerIds.add(pointerId)
+                    if (pointerId !in node.pointerIds) node.pointerIds.add(pointerId)
                     parent = node
                     continue@eachPin
                 } else {
@@ -89,26 +76,17 @@
      * therefore no longer associated with any pointer ids.
      */
     fun removeHitPath(pointerId: PointerId) {
-        removeHitPathInternal(pointerId)
-        removeHitPathInternal(pointerId)
+        root.recursivelyRemovePointerId(pointerId)
     }
 
     /**
      * Dispatches [internalPointerEvent] through the hierarchy.
      *
-     * Returns a [DispatchChangesRetVal] that should not be referenced directly, but instead
-     * should be destrutured immediately.  Each instance of [HitPathTracker] reuses a single
-     * [DispatchChangesRetVal] and mutates it for each return for performance reasons.
-     *
-     * [DispatchChangesRetVal.component1] references the resulting changes after dispatch.
-     * [DispatchChangesRetVal.component2] is true if the dispatch reached at least one
-     * [PointerInputModifier].
-     *
      * @param internalPointerEvent The change to dispatch.
      *
-     * @return The DispatchChangesRetVal that should be destructured immediately.
+     * @return whether this event was dispatched to a [PointerInputFilter]
      */
-    fun dispatchChanges(internalPointerEvent: InternalPointerEvent): DispatchChangesRetVal {
+    fun dispatchChanges(internalPointerEvent: InternalPointerEvent): Boolean {
         var dispatchHit = root.dispatchMainEventPass(
             internalPointerEvent.changes,
             rootCoordinates,
@@ -116,9 +94,7 @@
         )
         dispatchHit = root.dispatchFinalEventPass() || dispatchHit
 
-        dispatchChangesRetVal.wasDispatchedToSomething = dispatchHit
-        dispatchChangesRetVal.internalPointerEvent = internalPointerEvent
-        return dispatchChangesRetVal
+        return dispatchHit
     }
 
     /**
@@ -128,7 +104,6 @@
      * data.
      */
     fun processCancel() {
-        retainedHitPaths.clear()
         root.dispatchCancel()
         root.clear()
     }
@@ -142,13 +117,6 @@
     fun removeDetachedPointerInputFilters() {
         root.removeDetachedPointerInputFilters()
     }
-
-    /**
-     * Actually removes hit paths.
-     */
-    private fun removeHitPathInternal(pointerId: PointerId) {
-        root.removePointerId(pointerId)
-    }
 }
 
 /**
@@ -159,7 +127,7 @@
 /*@VisibleForTesting*/
 @OptIn(InternalCoreApi::class)
 internal open class NodeParent {
-    val children: MutableSet<Node> = mutableSetOf()
+    val children: MutableVector<Node> = mutableVectorOf()
 
     /**
      * Dispatches [changes] down the tree, for the initial and main pass.
@@ -221,54 +189,33 @@
      * Removes all child [Node]s that are no longer attached to the compose tree.
      */
     fun removeDetachedPointerInputFilters() {
-        children.removeAndProcess(
-            removeIf = {
-                !it.pointerInputFilter.isAttached
-            },
-            ifRemoved = {
-                it.dispatchCancel()
-            },
-            ifKept = {
-                it.removeDetachedPointerInputFilters()
+        var index = 0
+        while (index < children.size) {
+            val child = children[index]
+            if (!child.pointerInputFilter.isAttached) {
+                children.removeAt(index)
+                child.dispatchCancel()
+            } else {
+                index++
+                child.removeDetachedPointerInputFilters()
             }
-        )
+        }
     }
 
     /**
      * Removes the tracking of [pointerId] and removes all child [Node]s that are no longer
-     * tracking
-     * any [PointerId]s.
+     * tracking any [PointerId]s.
      */
-    fun removePointerId(pointerId: PointerId) {
-        children.forEach {
-            it.pointerIds.remove(pointerId)
-        }
-        children.removeAll {
-            it.pointerIds.isEmpty()
-        }
-        children.forEach {
-            it.removePointerId(pointerId)
-        }
-    }
-
-    /**
-     * With each item, if calling [removeIf] with it is true, removes the item from [this] and calls
-     * [ifRemoved] with it, otherwise calls [ifKept] with it.
-     */
-    private fun <T> MutableIterable<T>.removeAndProcess(
-        removeIf: (T) -> Boolean,
-        ifRemoved: (T) -> Unit,
-        ifKept: (T) -> Unit
-    ) {
-        with(iterator()) {
-            while (hasNext()) {
-                val next = next()
-                if (removeIf(next)) {
-                    remove()
-                    ifRemoved(next)
-                } else {
-                    ifKept(next)
-                }
+    fun recursivelyRemovePointerId(pointerId: PointerId) {
+        var index = 0
+        while (index < children.size) {
+            val child = children[index]
+            child.pointerIds.remove(pointerId)
+            if (child.pointerIds.isEmpty()) {
+                children.removeAt(index)
+            } else {
+                child.recursivelyRemovePointerId(pointerId)
+                index++
             }
         }
     }
@@ -282,7 +229,13 @@
 @OptIn(InternalCoreApi::class)
 internal class Node(val pointerInputFilter: PointerInputFilter) : NodeParent() {
 
-    val pointerIds: MutableSet<PointerId> = mutableSetOf()
+    // Note: this is essentially a set, and writes should be guarded accordingly. We use a
+    // MutableVector here instead since a set ends up being quite heavy, and calls to
+    // set.contains() show up noticeably (~1%) in traces. Since the maximum size of this vector
+    // is small (due to the limited amount of concurrent PointerIds there _could_ be), iterating
+    // through the small vector in most cases should have a lower performance impact than using a
+    // set.
+    val pointerIds: MutableVector<PointerId> = mutableVectorOf()
 
     /**
      * Cached properties that will be set before the main event pass, and reset after the final
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/pointer/PointerInputEventProcessor.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/pointer/PointerInputEventProcessor.kt
index 54db299..0278801 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/pointer/PointerInputEventProcessor.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/pointer/PointerInputEventProcessor.kt
@@ -34,6 +34,7 @@
 
     private val hitPathTracker = HitPathTracker(root.coordinates)
     private val pointerInputChangeEventProducer = PointerInputChangeEventProducer()
+    private val hitResult: MutableList<PointerInputFilter> = mutableListOf()
 
     /**
      * Receives [PointerInputEvent]s and process them through the tree rooted on [root].
@@ -54,44 +55,41 @@
         val internalPointerEvent =
             pointerInputChangeEventProducer.produce(pointerEvent, positionCalculator)
 
-        // TODO(shepshapard): Create fast forEach for maps?
-
         // Add new hit paths to the tracker due to down events.
-        internalPointerEvent
-            .changes
-            .filter { (_, pointerInputChange) -> pointerInputChange.changedToDownIgnoreConsumed() }
-            .forEach { (_, pointerInputChange) ->
-                val hitResult: MutableList<PointerInputFilter> = mutableListOf()
+        internalPointerEvent.changes.values.forEach { pointerInputChange ->
+            if (pointerInputChange.changedToDownIgnoreConsumed()) {
                 root.hitTest(
                     pointerInputChange.position,
                     hitResult
                 )
                 if (hitResult.isNotEmpty()) {
                     hitPathTracker.addHitPath(pointerInputChange.id, hitResult)
+                    hitResult.clear()
                 }
             }
+        }
 
         // Remove [PointerInputFilter]s that are no longer valid and refresh the offset information
         // for those that are.
         hitPathTracker.removeDetachedPointerInputFilters()
 
         // Dispatch to PointerInputFilters
-        val (resultingChanges, dispatchedToSomething) =
-            hitPathTracker.dispatchChanges(internalPointerEvent)
+        val dispatchedToSomething = hitPathTracker.dispatchChanges(internalPointerEvent)
 
-        // Remove hit paths from the tracker due to up events.
-        internalPointerEvent
-            .changes
-            .filter { (_, pointerInputChange) -> pointerInputChange.changedToUpIgnoreConsumed() }
-            .forEach { (_, pointerInputChange) ->
+        var anyMovementConsumed = false
+
+        // Remove hit paths from the tracker due to up events, and calculate if we have consumed
+        // any movement
+        internalPointerEvent.changes.values.forEach { pointerInputChange ->
+            if (pointerInputChange.changedToUpIgnoreConsumed()) {
                 hitPathTracker.removeHitPath(pointerInputChange.id)
             }
+            if (pointerInputChange.positionChangeConsumed()) {
+                anyMovementConsumed = true
+            }
+        }
 
-        // TODO(shepshapard): Don't allocate on every call.
-        return ProcessResult(
-            dispatchedToSomething,
-            resultingChanges.changes.any { (_, value) -> value.positionChangeConsumed() }
-        )
+        return ProcessResult(dispatchedToSomething, anyMovementConsumed)
     }
 
     /**
@@ -120,7 +118,9 @@
      */
     fun produce(pointerInputEvent: PointerInputEvent, positionCalculator: PositionCalculator):
         InternalPointerEvent {
-            val changes: MutableMap<PointerId, PointerInputChange> = mutableMapOf()
+            val changes: MutableMap<PointerId, PointerInputChange> =
+                // Set initial capacity to avoid resizing - we know the size the map will be.
+                LinkedHashMap(pointerInputEvent.pointers.size)
             pointerInputEvent.pointers.fastForEach {
                 val previousTime: Long
                 val previousPosition: Offset
@@ -154,8 +154,7 @@
                     previousPointerInputData[it.id] = PointerInputData(
                         it.uptime,
                         it.positionOnScreen,
-                        it.down,
-                        it.type
+                        it.down
                     )
                 } else {
                     previousPointerInputData.remove(it.id)
@@ -167,15 +166,14 @@
     /**
      * Clears all tracked information.
      */
-    internal fun clear() {
+    fun clear() {
         previousPointerInputData.clear()
     }
 
     private class PointerInputData(
         val uptime: Long,
         val positionOnScreen: Offset,
-        val down: Boolean,
-        val type: PointerType
+        val down: Boolean
     )
 }
 
@@ -183,7 +181,6 @@
  * The result of a call to [PointerInputEventProcessor.process].
  */
 // TODO(shepshpard): Not sure if storing these values in a int is most efficient overall.
-@Suppress("EXPERIMENTAL_FEATURE_WARNING")
 internal inline class ProcessResult(private val value: Int) {
     val dispatchedToAPointerInputModifier
         get() = (value and 1) != 0
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/pointer/SuspendingPointerInputFilter.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/pointer/SuspendingPointerInputFilter.kt
index aef6f62..53cccdb 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/pointer/SuspendingPointerInputFilter.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/pointer/SuspendingPointerInputFilter.kt
@@ -147,7 +147,8 @@
  * [pointerInput] [block]s may call [PointerInputScope.awaitPointerEventScope] to install a pointer
  * input handler that can [AwaitPointerEventScope.awaitPointerEvent] to receive and consume
  * pointer input events. Extension functions on [PointerInputScope] or [AwaitPointerEventScope]
- * may be defined to perform higher-level gesture detection.
+ * may be defined to perform higher-level gesture detection. The pointer input handling [block]
+ * will be cancelled and **re-started** when [pointerInput] is recomposed with a different [key1].
  */
 fun Modifier.pointerInput(
     key1: Any?,
@@ -174,7 +175,9 @@
  * [pointerInput] [block]s may call [PointerInputScope.awaitPointerEventScope] to install a pointer
  * input handler that can [AwaitPointerEventScope.awaitPointerEvent] to receive and consume
  * pointer input events. Extension functions on [PointerInputScope] or [AwaitPointerEventScope]
- * may be defined to perform higher-level gesture detection.
+ * may be defined to perform higher-level gesture detection. The pointer input handling [block]
+ * will be cancelled and **re-started** when [pointerInput] is recomposed with a different [key1] or
+ * [key2].
  */
 fun Modifier.pointerInput(
     key1: Any?,
@@ -203,7 +206,8 @@
  * [pointerInput] [block]s may call [PointerInputScope.awaitPointerEventScope] to install a pointer
  * input handler that can [AwaitPointerEventScope.awaitPointerEvent] to receive and consume
  * pointer input events. Extension functions on [PointerInputScope] or [AwaitPointerEventScope]
- * may be defined to perform higher-level gesture detection.
+ * may be defined to perform higher-level gesture detection. The pointer input handling [block]
+ * will be cancelled and **re-started** when [pointerInput] is recomposed with any different [keys].
  */
 fun Modifier.pointerInput(
     vararg keys: Any?,
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/Layout.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/Layout.kt
index 335f16c..b15b091 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/Layout.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/Layout.kt
@@ -20,7 +20,7 @@
 
 import androidx.compose.runtime.Applier
 import androidx.compose.runtime.Composable
-import androidx.compose.runtime.ComposeNode
+import androidx.compose.runtime.ReusableComposeNode
 import androidx.compose.runtime.SkippableUpdater
 import androidx.compose.runtime.currentComposer
 import androidx.compose.ui.Modifier
@@ -71,7 +71,7 @@
 ) {
     val density = LocalDensity.current
     val layoutDirection = LocalLayoutDirection.current
-    ComposeNode<ComposeUiNode, Applier<Any>>(
+    ReusableComposeNode<ComposeUiNode, Applier<Any>>(
         factory = ComposeUiNode.Constructor,
         update = {
             set(measurePolicy, ComposeUiNode.SetMeasurePolicy)
@@ -192,7 +192,7 @@
     val density = LocalDensity.current
     val layoutDirection = LocalLayoutDirection.current
 
-    ComposeNode<LayoutNode, Applier<Any>>(
+    ReusableComposeNode<LayoutNode, Applier<Any>>(
         factory = LayoutNode.Constructor,
         update = {
             set(materialized, ComposeUiNode.SetModifier)
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/SubcomposeLayout.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/SubcomposeLayout.kt
index 6c6820c1..92a69d7 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/SubcomposeLayout.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/SubcomposeLayout.kt
@@ -60,7 +60,39 @@
     modifier: Modifier = Modifier,
     measurePolicy: SubcomposeMeasureScope.(Constraints) -> MeasureResult
 ) {
-    val state = remember { SubcomposeLayoutState() }
+    SubcomposeLayout(
+        state = remember { SubcomposeLayoutState() },
+        modifier = modifier,
+        measurePolicy = measurePolicy
+    )
+}
+
+/**
+ * Analogue of [Layout] which allows to subcompose the actual content during the measuring stage
+ * for example to use the values calculated during the measurement as params for the composition
+ * of the children.
+ *
+ * Possible use cases:
+ * * You need to know the constraints passed by the parent during the composition and can't solve
+ * your use case with just custom [Layout] or [LayoutModifier].
+ * See [androidx.compose.foundation.layout.BoxWithConstraints].
+ * * You want to use the size of one child during the composition of the second child.
+ * * You want to compose your items lazily based on the available size. For example you have a
+ * list of 100 items and instead of composing all of them you only compose the ones which are
+ * currently visible(say 5 of them) and compose next items when the component is scrolled.
+ *
+ * @sample androidx.compose.ui.samples.SubcomposeLayoutSample
+ *
+ * @param state the state object to be used by the layout.
+ * @param modifier [Modifier] to apply for the layout.
+ * @param measurePolicy Measure policy which provides ability to subcompose during the measuring.
+ */
+@Composable
+fun SubcomposeLayout(
+    state: SubcomposeLayoutState,
+    modifier: Modifier = Modifier,
+    measurePolicy: SubcomposeMeasureScope.(Constraints) -> MeasureResult
+) {
     state.compositionContext = rememberCompositionContext()
     DisposableEffect(state) {
         onDispose {
@@ -103,34 +135,67 @@
 }
 
 /**
- * Contains the state used by [SubcomposeLayout].
+ * State used by [SubcomposeLayout].
+ *
+ * @param maxSlotsToRetainForReuse when non-zero the layout will keep active up to this count
+ * slots which we were used but not used anymore instead of disposing them. Later when you try to
+ * compose a new slot instead of creating a completely new slot the layout would reuse the
+ * previous slot which allows to do less work especially if the slot contents are similar.
  */
-internal class SubcomposeLayoutState {
+class SubcomposeLayoutState(
+    private val maxSlotsToRetainForReuse: Int
+) {
+    /**
+     * State used by [SubcomposeLayout].
+     */
+    constructor() : this(0)
+
     internal var compositionContext: CompositionContext? = null
 
     // Pre-allocated lambdas to update LayoutNode
-    internal val setRoot: LayoutNode.() -> Unit = { root = this }
+    internal val setRoot: LayoutNode.() -> Unit = { _root = this }
     internal val setMeasurePolicy:
         LayoutNode.(SubcomposeMeasureScope.(Constraints) -> MeasureResult) -> Unit =
             { measurePolicy = createMeasurePolicy(it) }
 
     // inner state
-    private var root: LayoutNode? = null
+    private var _root: LayoutNode? = null
+    private val root: LayoutNode get() = requireNotNull(_root)
     private var currentIndex = 0
     private val nodeToNodeState = mutableMapOf<LayoutNode, NodeState>()
-    private val slodIdToNode = mutableMapOf<Any?, LayoutNode>()
+    // this map contains active slotIds (without precomposed or reusable nodes)
+    private val slotIdToNode = mutableMapOf<Any?, LayoutNode>()
     private val scope = Scope()
+    private val precomposeMap = mutableMapOf<Any?, LayoutNode>()
+
+    /**
+     * `root.foldedChildren` list consist of:
+     * 1) all the active children (used during the last measure pass)
+     * 2) `reusableCount` nodes in the middle of the list which were active and stopped being
+     * used. now we keep them (up to `maxCountOfSlotsToReuse`) in order to reuse next time we
+     * will need to compose a new item
+     * 4) `precomposedCount` nodes in the end of the list which were precomposed and
+     * are waiting to be used during the next measure passes.
+     */
+    private var reusableCount = 0
+    private var precomposedCount = 0
 
     internal fun subcompose(slotId: Any?, content: @Composable () -> Unit): List<Measurable> {
-        val root = root!!
         val layoutState = root.layoutState
         check(layoutState == LayoutState.Measuring || layoutState == LayoutState.LayingOut) {
             "subcompose can only be used inside the measure or layout blocks"
         }
 
-        val node = slodIdToNode.getOrPut(slotId) {
-            LayoutNode(isVirtual = true).also {
-                root.insertAt(currentIndex, it)
+        val node = slotIdToNode.getOrPut(slotId) {
+            val precomposed = precomposeMap.remove(slotId)
+            if (precomposed != null) {
+                check(precomposedCount > 0)
+                precomposedCount--
+                precomposed
+            } else if (reusableCount > 0) {
+                takeNodeFromReusables(slotId)
+            } else {
+                createNodeAt(currentIndex)
             }
         }
 
@@ -141,10 +206,15 @@
             )
         }
         if (currentIndex != itemIndex) {
-            root.move(itemIndex, currentIndex, 1)
+            move(itemIndex, currentIndex)
         }
         currentIndex++
 
+        subcompose(node, slotId, content)
+        return node.children
+    }
+
+    private fun subcompose(node: LayoutNode, slotId: Any?, content: @Composable () -> Unit) {
         val nodeState = nodeToNodeState.getOrPut(node) {
             NodeState(slotId, {})
         }
@@ -153,21 +223,22 @@
             nodeState.content = content
             subcompose(node, nodeState)
         }
-        return node.children
     }
 
     private fun subcompose(node: LayoutNode, nodeState: NodeState) {
         node.withNoSnapshotReadObservation {
-            val content = nodeState.content
-            nodeState.composition = subcomposeInto(
-                existing = nodeState.composition,
-                container = node,
-                parent = compositionContext ?: error("parent composition reference not set"),
-                // Do not optimize this by passing nodeState.content directly; the additional
-                // composable function call from the lambda expression affects the scope of
-                // recomposition and recomposition of siblings.
-                composable = { content() }
-            )
+            ignoreRemeasureRequests {
+                val content = nodeState.content
+                nodeState.composition = subcomposeInto(
+                    existing = nodeState.composition,
+                    container = node,
+                    parent = compositionContext ?: error("parent composition reference not set"),
+                    // Do not optimize this by passing nodeState.content directly; the additional
+                    // composable function call from the lambda expression affects the scope of
+                    // recomposition and recomposition of siblings.
+                    composable = { content() }
+                )
+            }
         }
     }
 
@@ -188,14 +259,64 @@
     }
 
     private fun disposeAfterIndex(currentIndex: Int) {
-        val root = root!!
-        for (i in currentIndex until root.foldedChildren.size) {
+        val precomposedNodesSectionStart = root.foldedChildren.size - precomposedCount
+        val reusableNodesSectionStart = maxOf(
+            currentIndex,
+            precomposedNodesSectionStart - maxSlotsToRetainForReuse
+        )
+
+        // keep up to maxCountOfSlotsToReuse last nodes to be reused later
+        reusableCount = precomposedNodesSectionStart - reusableNodesSectionStart
+        for (i in reusableNodesSectionStart until reusableNodesSectionStart + reusableCount) {
             val node = root.foldedChildren[i]
-            val nodeState = nodeToNodeState.remove(node)!!
-            nodeState.composition!!.dispose()
-            slodIdToNode.remove(nodeState.slotId)
+            val state = nodeToNodeState[node]!!
+            // remove them from slotIdToNode so they are not considered active
+            slotIdToNode.remove(state.slotId)
         }
-        root.removeAt(currentIndex, root.foldedChildren.size - currentIndex)
+
+        // dispose the rest of the nodes
+        val nodesToDispose = reusableNodesSectionStart - currentIndex
+        if (nodesToDispose > 0) {
+            ignoreRemeasureRequests {
+                for (i in currentIndex until currentIndex + nodesToDispose) {
+                    disposeNode(root.foldedChildren[i])
+                }
+                root.removeAt(currentIndex, nodesToDispose)
+            }
+        }
+    }
+
+    private fun takeNodeFromReusables(slotId: Any?): LayoutNode {
+        check(reusableCount > 0)
+        val reusableNodesSectionEnd = root.foldedChildren.size - precomposedCount
+        val reusableNodesSectionStart = reusableNodesSectionEnd - reusableCount
+        var index = reusableNodesSectionStart
+        while (true) {
+            val node = root.foldedChildren[index]
+            val nodeState = nodeToNodeState.getValue(node)
+            if (nodeState.slotId == slotId) {
+                // we have a node with the same slotId
+                break
+            } else if (index == reusableNodesSectionEnd - 1) {
+                // it is the last available reusable node
+                nodeState.slotId = slotId
+                break
+            } else {
+                index++
+            }
+        }
+        if (index != reusableNodesSectionStart) {
+            // we need to rearrange the items
+            move(index, reusableNodesSectionStart, 1)
+        }
+        reusableCount--
+        return root.foldedChildren[reusableNodesSectionStart]
+    }
+
+    private fun disposeNode(node: LayoutNode) {
+        val nodeState = nodeToNodeState.remove(node)!!
+        nodeState.composition!!.dispose()
+        slotIdToNode.remove(nodeState.slotId)
     }
 
     private fun createMeasurePolicy(
@@ -235,11 +356,81 @@
             it.composition!!.dispose()
         }
         nodeToNodeState.clear()
-        slodIdToNode.clear()
+        slotIdToNode.clear()
     }
 
+    /**
+     * Composes the content for the given [slotId]. This makes the next scope.subcompose(slotId)
+     * call during the measure pass faster as the content is already composed.
+     *
+     * If the [slotId] was precomposed already but after the future calculations ended up to not be
+     * needed anymore (meaning this slotId is not going to be used during the measure pass
+     * anytime soon) you can use [PrecomposedSlotHandle.dispose] on a returned object to dispose the
+     * content.
+     *
+     * @param slotId unique id which represents the slot we are composing into.
+     * @param content the composable content which defines the slot.
+     * @return [PrecomposedSlotHandle] instance which allows you to dispose the content.
+     */
+    fun precompose(slotId: Any?, content: @Composable () -> Unit): PrecomposedSlotHandle {
+        if (!slotIdToNode.containsKey(slotId)) {
+            val node = precomposeMap.getOrPut(slotId) {
+                if (reusableCount > 0) {
+                    val node = takeNodeFromReusables(slotId)
+                    // now move this node to the end where we keep precomposed items
+                    val nodeIndex = root.foldedChildren.indexOf(node)
+                    move(nodeIndex, root.foldedChildren.size, 1)
+                    precomposedCount++
+                    node
+                } else {
+                    createNodeAt(root.foldedChildren.size).also {
+                        precomposedCount++
+                    }
+                }
+            }
+            subcompose(node, slotId, content)
+        }
+        return object : PrecomposedSlotHandle {
+            override fun dispose() {
+                val node = precomposeMap.remove(slotId)
+                if (node != null) {
+                    val itemIndex = root.foldedChildren.indexOf(node)
+                    check(itemIndex != -1)
+                    if (reusableCount < maxSlotsToRetainForReuse) {
+                        val reusableNodesSectionStart =
+                            root.foldedChildren.size - precomposedCount - reusableCount
+                        move(itemIndex, reusableNodesSectionStart, 1)
+                        reusableCount++
+                    } else {
+                        ignoreRemeasureRequests {
+                            disposeNode(node)
+                            root.removeAt(itemIndex, 1)
+                        }
+                    }
+                    check(precomposedCount > 0)
+                    precomposedCount--
+                }
+            }
+        }
+    }
+
+    private fun createNodeAt(index: Int) = LayoutNode(isVirtual = true).also {
+        ignoreRemeasureRequests {
+            root.insertAt(index, it)
+        }
+    }
+
+    private fun move(from: Int, to: Int, count: Int = 1) {
+        ignoreRemeasureRequests {
+            root.move(from, to, count)
+        }
+    }
+
+    private inline fun ignoreRemeasureRequests(block: () -> Unit) =
+        root.ignoreRemeasureRequests(block)
+
     private class NodeState(
-        val slotId: Any?,
+        var slotId: Any?,
         var content: @Composable () -> Unit,
         var composition: Composition? = null
     )
@@ -253,4 +444,22 @@
         override fun subcompose(slotId: Any?, content: @Composable () -> Unit) =
             this@SubcomposeLayoutState.subcompose(slotId, content)
     }
+
+    /**
+     * Instance of this interface is returned by [precompose] function.
+     */
+    interface PrecomposedSlotHandle {
+
+        /**
+         * This function allows to dispose the content for the slot which was precomposed
+         * previously via [precompose].
+         *
+         * If this slot was already used during the regular measure pass via
+         * [SubcomposeMeasureScope.subcompose] this function will do nothing.
+         *
+         * This could be useful if after the future calculations this item is not anymore expected to
+         * be used during the measure pass anytime soon.
+         */
+        fun dispose()
+    }
 }
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutNode.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutNode.kt
index c381939..ce522152 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutNode.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutNode.kt
@@ -145,7 +145,7 @@
 
     /**
      * The parent node in the LayoutNode hierarchy. This is `null` when the [LayoutNode]
-     * is not attached attached to a hierarchy or is the root of the hierarchy.
+     * is not attached to a hierarchy or is the root of the hierarchy.
      */
     internal var parent: LayoutNode? = null
         get() {
@@ -185,6 +185,11 @@
     private var wrapperCache = mutableVectorOf<DelegatingLayoutNodeWrapper<*>>()
 
     /**
+     * [requestRemeasure] calls will be ignored while this flag is true.
+     */
+    private var ignoreRemeasureRequests = false
+
+    /**
      * Inserts a child [LayoutNode] at a particular index. If this LayoutNode [owner] is not `null`
      * then [instance] will become [attach]ed also. [instance] must have a `null` [parent].
      */
@@ -1036,7 +1041,15 @@
      * Used to request a new measurement + layout pass from the owner.
      */
     internal fun requestRemeasure() {
-        owner?.onRequestMeasure(this)
+        if (!ignoreRemeasureRequests) {
+            owner?.onRequestMeasure(this)
+        }
+    }
+
+    internal inline fun ignoreRemeasureRequests(block: () -> Unit) {
+        ignoreRemeasureRequests = true
+        block()
+        ignoreRemeasureRequests = false
     }
 
     /**
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutNodeWrapper.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutNodeWrapper.kt
index c0a7ed7..b584d63 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutNodeWrapper.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutNodeWrapper.kt
@@ -251,6 +251,9 @@
                 it.destroy()
                 layoutNode.innerLayerWrapperIsDirty = true
                 invalidateParentLayer()
+                if (isAttached) {
+                    layoutNode.owner?.onLayoutChange(layoutNode)
+                }
             }
             layer = null
         }
@@ -285,6 +288,7 @@
         } else {
             require(layerBlock == null)
         }
+        layoutNode.owner?.onLayoutChange(layoutNode)
     }
 
     private val invalidateParentLayer: () -> Unit = {
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/Owner.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/Owner.kt
index 03306b7..5a70959 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/Owner.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/Owner.kt
@@ -18,7 +18,7 @@
 import androidx.compose.ui.ExperimentalComposeUiApi
 import androidx.compose.ui.autofill.Autofill
 import androidx.compose.ui.autofill.AutofillTree
-import androidx.compose.ui.focus.FocusDirectionInternal
+import androidx.compose.ui.focus.FocusDirection
 import androidx.compose.ui.focus.FocusManager
 import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.graphics.Canvas
@@ -180,9 +180,9 @@
     fun onLayoutChange(layoutNode: LayoutNode)
 
     /**
-     * The [FocusDirection][FocusDirectionInternal] represented by the specified keyEvent.
+     * The [FocusDirection] represented by the specified keyEvent.
      */
-    fun getFocusDirection(keyEvent: KeyEvent): FocusDirectionInternal?
+    fun getFocusDirection(keyEvent: KeyEvent): FocusDirection?
 
     val measureIteration: Long
 
diff --git a/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/desktop/ComposeLayer.desktop.kt b/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/desktop/ComposeLayer.desktop.kt
index 92cfd5c..296411b 100644
--- a/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/desktop/ComposeLayer.desktop.kt
+++ b/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/desktop/ComposeLayer.desktop.kt
@@ -175,7 +175,7 @@
         })
         wrapped.addMouseMotionListener(object : MouseMotionAdapter() {
             override fun mouseDragged(event: MouseEvent) = events.post {
-                owners.onMouseDragged(
+                owners.onMouseMoved(
                     (event.x * density.density).toInt(),
                     (event.y * density.density).toInt(),
                     event
@@ -185,7 +185,8 @@
             override fun mouseMoved(event: MouseEvent) = events.post {
                 owners.onMouseMoved(
                     (event.x * density.density).toInt(),
-                    (event.y * density.density).toInt()
+                    (event.y * density.density).toInt(),
+                    event
                 )
             }
         })
diff --git a/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/platform/DesktopOwner.desktop.kt b/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/platform/DesktopOwner.desktop.kt
index c8f41fd..2b768bc 100644
--- a/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/platform/DesktopOwner.desktop.kt
+++ b/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/platform/DesktopOwner.desktop.kt
@@ -25,15 +25,14 @@
 import androidx.compose.ui.autofill.Autofill
 import androidx.compose.ui.autofill.AutofillTree
 import androidx.compose.ui.focus.FocusDirection
-import androidx.compose.ui.focus.FocusDirectionInternal
-import androidx.compose.ui.focus.FocusDirectionInternal.Down
-import androidx.compose.ui.focus.FocusDirectionInternal.In
-import androidx.compose.ui.focus.FocusDirectionInternal.Left
-import androidx.compose.ui.focus.FocusDirectionInternal.Next
-import androidx.compose.ui.focus.FocusDirectionInternal.Out
-import androidx.compose.ui.focus.FocusDirectionInternal.Previous
-import androidx.compose.ui.focus.FocusDirectionInternal.Right
-import androidx.compose.ui.focus.FocusDirectionInternal.Up
+import androidx.compose.ui.focus.FocusDirection.Companion.Down
+import androidx.compose.ui.focus.FocusDirection.Companion.In
+import androidx.compose.ui.focus.FocusDirection.Companion.Left
+import androidx.compose.ui.focus.FocusDirection.Companion.Next
+import androidx.compose.ui.focus.FocusDirection.Companion.Out
+import androidx.compose.ui.focus.FocusDirection.Companion.Previous
+import androidx.compose.ui.focus.FocusDirection.Companion.Right
+import androidx.compose.ui.focus.FocusDirection.Companion.Up
 import androidx.compose.ui.focus.FocusManager
 import androidx.compose.ui.focus.FocusManagerImpl
 import androidx.compose.ui.geometry.Offset
@@ -48,7 +47,7 @@
 import androidx.compose.ui.input.key.Key.Companion.Tab
 import androidx.compose.ui.input.key.KeyEvent
 import androidx.compose.ui.input.key.KeyEventType
-import androidx.compose.ui.input.key.KeyEventType.KeyDown
+import androidx.compose.ui.input.key.KeyEventType.Companion.KeyDown
 import androidx.compose.ui.input.key.KeyInputModifier
 import androidx.compose.ui.input.key.isShiftPressed
 import androidx.compose.ui.input.key.key
@@ -60,6 +59,7 @@
 import androidx.compose.ui.input.pointer.PointerInputFilter
 import androidx.compose.ui.input.pointer.PointerMoveEventFilter
 import androidx.compose.ui.input.pointer.PositionCalculator
+import androidx.compose.ui.input.pointer.ProcessResult
 import androidx.compose.ui.input.pointer.TestPointerInputEventData
 import androidx.compose.ui.layout.RootMeasurePolicy
 import androidx.compose.ui.layout.boundsInWindow
@@ -118,21 +118,8 @@
             val focusDirection = getFocusDirection(it)
             if (focusDirection == null || it.type != KeyDown) return@KeyInputModifier false
 
-            val focusMoveSuccess = with(focusManager) {
-                when (focusDirection) {
-                    Up -> moveFocus(FocusDirection.Up)
-                    Down -> moveFocus(FocusDirection.Down)
-                    Left -> moveFocus(FocusDirection.Left)
-                    Right -> moveFocus(FocusDirection.Right)
-                    In -> moveFocusIn()
-                    Out -> moveFocusOut()
-                    Next -> moveFocus(FocusDirection.Next)
-                    Previous -> moveFocus(FocusDirection.Previous)
-                }
-            }
-
             // Consume the key event if we moved focus.
-            focusMoveSuccess
+            focusManager.moveFocus(focusDirection)
         },
         onPreviewKeyEvent = null
     )
@@ -286,7 +273,7 @@
 
     override fun onLayoutChange(layoutNode: LayoutNode) = Unit
 
-    override fun getFocusDirection(keyEvent: KeyEvent): FocusDirectionInternal? {
+    override fun getFocusDirection(keyEvent: KeyEvent): FocusDirection? {
         return when (keyEvent.key) {
             Tab -> if (keyEvent.isShiftPressed) Previous else Next
             DirectionRight -> Right
@@ -317,9 +304,9 @@
         root.draw(DesktopCanvas(canvas))
     }
 
-    internal fun processPointerInput(event: PointerInputEvent) {
+    internal fun processPointerInput(event: PointerInputEvent): ProcessResult {
         measureAndLayout()
-        pointerInputEventProcessor.process(event, this)
+        return pointerInputEventProcessor.process(event, this)
     }
 
     override fun processPointerInput(nanoTime: Long, pointers: List<TestPointerInputEventData>) {
diff --git a/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/platform/DesktopOwners.desktop.kt b/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/platform/DesktopOwners.desktop.kt
index 89d44f4..7a27e67 100644
--- a/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/platform/DesktopOwners.desktop.kt
+++ b/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/platform/DesktopOwners.desktop.kt
@@ -73,7 +73,7 @@
 
     private val dispatcher = FlushCoroutineDispatcher(coroutineScope)
     private val frameClock = BroadcastFrameClock(onNewAwaiters = ::invalidateIfNeeded)
-    private val coroutineContext = dispatcher + frameClock
+    private val coroutineContext = coroutineScope.coroutineContext + dispatcher + frameClock
 
     internal val recomposer = Recomposer(coroutineContext)
     internal val platformInputService: DesktopPlatformInput = DesktopPlatformInput(component)
@@ -143,8 +143,13 @@
         pointerId += 1
     }
 
-    fun onMouseDragged(x: Int, y: Int, nativeEvent: MouseEvent? = null) {
-        lastOwner?.processPointerInput(pointerInputEvent(nativeEvent, x, y, isMousePressed))
+    fun onMouseMoved(x: Int, y: Int, nativeEvent: MouseEvent? = null) {
+        val event = pointerInputEvent(nativeEvent, x, y, isMousePressed)
+        val result = lastOwner?.processPointerInput(event)
+        if (result?.anyMovementConsumed != true) {
+            val position = Offset(x.toFloat(), y.toFloat())
+            lastOwner?.onPointerMove(position)
+        }
     }
 
     fun onMouseScroll(x: Int, y: Int, event: MouseScrollEvent) {
@@ -152,11 +157,6 @@
         lastOwner?.onMouseScroll(position, event)
     }
 
-    fun onMouseMoved(x: Int, y: Int) {
-        val position = Offset(x.toFloat(), y.toFloat())
-        lastOwner?.onPointerMove(position)
-    }
-
     fun onMouseEntered(x: Int, y: Int) {
         val position = Offset(x.toFloat(), y.toFloat())
         lastOwner?.onPointerEnter(position)
diff --git a/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/platform/GlobalSnapshotManager.desktop.kt b/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/platform/GlobalSnapshotManager.desktop.kt
index 02855bf..fd87f34 100644
--- a/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/platform/GlobalSnapshotManager.desktop.kt
+++ b/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/platform/GlobalSnapshotManager.desktop.kt
@@ -17,10 +17,11 @@
 package androidx.compose.ui.platform
 
 import androidx.compose.runtime.snapshots.Snapshot
-import androidx.compose.runtime.snapshots.ObserverHandle
+import androidx.compose.ui.platform.GlobalSnapshotManager.ensureStarted
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.SupervisorJob
+import kotlinx.coroutines.channels.Channel
+import kotlinx.coroutines.channels.consumeEach
 import kotlinx.coroutines.launch
 import kotlinx.coroutines.swing.Swing
 import java.util.concurrent.atomic.AtomicBoolean
@@ -38,56 +39,17 @@
  */
 internal object GlobalSnapshotManager {
     private val started = AtomicBoolean(false)
-    private var commitPending = false
-    private var removeWriteObserver: ObserverHandle? = null
-
-    private val scheduleScope = CoroutineScope(Dispatchers.Swing + SupervisorJob())
 
     fun ensureStarted() {
         if (started.compareAndSet(false, true)) {
-            removeWriteObserver = Snapshot.registerGlobalWriteObserver(globalWriteObserver)
-        }
-    }
-
-    private val globalWriteObserver: (Any) -> Unit = {
-        // Race, but we don't care too much if we end up with multiple calls scheduled.
-        if (!commitPending) {
-            commitPending = true
-            schedule {
-                commitPending = false
-                Snapshot.sendApplyNotifications()
+            val channel = Channel<Unit>(Channel.CONFLATED)
+            CoroutineScope(Dispatchers.Swing).launch {
+                channel.consumeEach {
+                    Snapshot.sendApplyNotifications()
+                }
             }
-        }
-    }
-
-    /**
-     * List of deferred callbacks to run serially. Guarded by its own monitor lock.
-     */
-    private val scheduledCallbacks = mutableListOf<() -> Unit>()
-
-    /**
-     * Guarded by [scheduledCallbacks]'s monitor lock.
-     */
-    private var isSynchronizeScheduled = false
-
-    /**
-     * Synchronously executes any outstanding callbacks and brings snapshots into a
-     * consistent, updated state.
-     */
-    private fun synchronize() {
-        synchronized(scheduledCallbacks) {
-            scheduledCallbacks.forEach { it.invoke() }
-            scheduledCallbacks.clear()
-            isSynchronizeScheduled = false
-        }
-    }
-
-    private fun schedule(block: () -> Unit) {
-        synchronized(scheduledCallbacks) {
-            scheduledCallbacks.add(block)
-            if (!isSynchronizeScheduled) {
-                isSynchronizeScheduled = true
-                scheduleScope.launch { synchronize() }
+            Snapshot.registerGlobalWriteObserver {
+                channel.offer(Unit)
             }
         }
     }
diff --git a/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/platform/TestComposeWindow.desktop.kt b/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/platform/TestComposeWindow.desktop.kt
index 15d19ba..cddba9b 100644
--- a/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/platform/TestComposeWindow.desktop.kt
+++ b/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/platform/TestComposeWindow.desktop.kt
@@ -25,6 +25,7 @@
 import androidx.compose.ui.unit.Density
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Job
 import kotlinx.coroutines.Runnable
 import kotlinx.coroutines.cancel
 import org.jetbrains.skija.Surface
@@ -59,7 +60,7 @@
     private val canvas = surface.canvas
     private var owner: DesktopOwner? = null
 
-    private val coroutineScope = CoroutineScope(coroutineContext)
+    private val coroutineScope = CoroutineScope(coroutineContext + Job())
     private val frameDispatcher: FrameDispatcher = FrameDispatcher(
         onFrame = { onFrame() },
         context = coroutineScope.coroutineContext
diff --git a/compose/ui/ui/src/desktopTest/kotlin/androidx/compose/ui/input/key/KeyInputUtil.kt b/compose/ui/ui/src/desktopTest/kotlin/androidx/compose/ui/input/key/KeyInputUtil.kt
index 24533e3..87efdf6 100644
--- a/compose/ui/ui/src/desktopTest/kotlin/androidx/compose/ui/input/key/KeyInputUtil.kt
+++ b/compose/ui/ui/src/desktopTest/kotlin/androidx/compose/ui/input/key/KeyInputUtil.kt
@@ -32,7 +32,7 @@
     val action = when (keyEventType) {
         KeyEventType.KeyDown -> KEY_PRESSED
         KeyEventType.KeyUp -> KEY_RELEASED
-        KeyEventType.Unknown -> error("Unknown key event type")
+        else -> error("Unknown key event type")
     }
     return KeyEvent(
         KeyEventAwt(
diff --git a/compose/ui/ui/src/desktopTest/kotlin/androidx/compose/ui/platform/DesktopOwnerTest.kt b/compose/ui/ui/src/desktopTest/kotlin/androidx/compose/ui/platform/DesktopOwnerTest.kt
index d3345e2..b6073f5 100644
--- a/compose/ui/ui/src/desktopTest/kotlin/androidx/compose/ui/platform/DesktopOwnerTest.kt
+++ b/compose/ui/ui/src/desktopTest/kotlin/androidx/compose/ui/platform/DesktopOwnerTest.kt
@@ -46,8 +46,11 @@
 import androidx.compose.ui.input.mouse.MouseScrollUnit
 import androidx.compose.ui.layout.Layout
 import androidx.compose.ui.test.junit4.DesktopScreenshotTestRule
+import androidx.compose.ui.test.junit4.createComposeRule
 import androidx.compose.ui.unit.dp
 import com.google.common.truth.Truth
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.runBlocking
 import org.junit.Assert.assertFalse
 import org.junit.Rule
 import org.junit.Test
@@ -55,6 +58,8 @@
 class DesktopOwnerTest {
     @get:Rule
     val screenshotRule = DesktopScreenshotTestRule("ui/ui-desktop/ui")
+    @get:Rule
+    val composeRule = createComposeRule()
 
     @Test(timeout = 5000)
     fun `rendering of Box state change`() = renderingTest(width = 40, height = 40) {
@@ -349,4 +354,18 @@
         awaitNextRender()
         Truth.assertThat(effectIsLaunched).isTrue()
     }
+
+    @Test(expected = TestException::class)
+    fun `catch exception in LaunchedEffect`() {
+        runBlocking(Dispatchers.Main) {
+            composeRule.setContent {
+                LaunchedEffect(Unit) {
+                    throw TestException()
+                }
+            }
+            composeRule.awaitIdle()
+        }
+    }
+
+    private class TestException : RuntimeException()
 }
\ No newline at end of file
diff --git a/compose/ui/ui/src/desktopTest/kotlin/androidx/compose/ui/platform/SkijaLayerTest.kt b/compose/ui/ui/src/desktopTest/kotlin/androidx/compose/ui/platform/SkijaLayerTest.kt
index 8da0ff2..f61bb8e 100644
--- a/compose/ui/ui/src/desktopTest/kotlin/androidx/compose/ui/platform/SkijaLayerTest.kt
+++ b/compose/ui/ui/src/desktopTest/kotlin/androidx/compose/ui/platform/SkijaLayerTest.kt
@@ -156,8 +156,6 @@
         val matrix = layer.matrix
 
         val y = 10 * (1 - cos45.toFloat())
-        println(matrix.map(Offset(0f, 0f)))
-        println(matrix.map(Offset(100f, 10f)))
         assertEquals(Offset(0f, y), matrix.map(Offset(0f, 0f)))
         assertEquals(Offset(100f, 10f), matrix.map(Offset(100f, 10f)))
     }
diff --git a/compose/ui/ui/src/desktopTest/kotlin/androidx/compose/ui/platform/TestComposeWindowTest.kt b/compose/ui/ui/src/desktopTest/kotlin/androidx/compose/ui/platform/TestComposeWindowTest.kt
index a85f9da..7fd119a 100644
--- a/compose/ui/ui/src/desktopTest/kotlin/androidx/compose/ui/platform/TestComposeWindowTest.kt
+++ b/compose/ui/ui/src/desktopTest/kotlin/androidx/compose/ui/platform/TestComposeWindowTest.kt
@@ -23,6 +23,9 @@
 import androidx.compose.material.icons.Icons
 import androidx.compose.material.icons.filled.AccountBox
 import androidx.compose.ui.Modifier
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.swing.Swing
 import org.junit.Test
 
 class TestComposeWindowTest {
@@ -41,4 +44,12 @@
             }
         }
     }
+
+    @Test(timeout = 5000)
+    fun `disposing TestComposeWindow should not cancel coroutineContext's Job`() {
+        runBlocking(Dispatchers.Swing) {
+            val window = TestComposeWindow(100, 100, coroutineContext = coroutineContext)
+            window.dispose()
+        }
+    }
 }
\ No newline at end of file
diff --git a/compose/ui/ui/src/test/kotlin/androidx/compose/ui/input/TextInputServiceAndroidTest.kt b/compose/ui/ui/src/test/kotlin/androidx/compose/ui/input/TextInputServiceAndroidTest.kt
index 9ba82a1..9773822 100644
--- a/compose/ui/ui/src/test/kotlin/androidx/compose/ui/input/TextInputServiceAndroidTest.kt
+++ b/compose/ui/ui/src/test/kotlin/androidx/compose/ui/input/TextInputServiceAndroidTest.kt
@@ -418,6 +418,26 @@
     }
 
     @Test
+    fun test_fill_editor_info_multi_line_with_default_action() {
+        textInputService.startInput(
+            value = TextFieldValue(""),
+            imeOptions = ImeOptions(
+                singleLine = false,
+                keyboardType = KeyboardType.Text,
+                imeAction = ImeAction.Default
+            ),
+            onEditCommand = {},
+            onImeActionPerformed = {}
+        )
+
+        EditorInfo().let { info ->
+            textInputService.createInputConnection(info)
+            assertFalse((InputType.TYPE_TEXT_FLAG_MULTI_LINE and info.inputType) == 0)
+            assertFalse((EditorInfo.IME_FLAG_NO_ENTER_ACTION and info.imeOptions) == 0)
+        }
+    }
+
+    @Test
     fun test_fill_editor_info_single_line() {
         textInputService.startInput(
             value = TextFieldValue(""),
diff --git a/compose/ui/ui/src/test/kotlin/androidx/compose/ui/node/LayoutNodeTest.kt b/compose/ui/ui/src/test/kotlin/androidx/compose/ui/node/LayoutNodeTest.kt
index e8d13fe..bdf16a9 100644
--- a/compose/ui/ui/src/test/kotlin/androidx/compose/ui/node/LayoutNodeTest.kt
+++ b/compose/ui/ui/src/test/kotlin/androidx/compose/ui/node/LayoutNodeTest.kt
@@ -21,7 +21,7 @@
 import androidx.compose.ui.autofill.AutofillTree
 import androidx.compose.ui.draw.DrawModifier
 import androidx.compose.ui.draw.drawBehind
-import androidx.compose.ui.focus.FocusDirectionInternal
+import androidx.compose.ui.focus.FocusDirection
 import androidx.compose.ui.focus.FocusManager
 import androidx.compose.ui.geometry.MutableRect
 import androidx.compose.ui.geometry.Offset
@@ -1682,6 +1682,22 @@
     }
 
     @Test
+    fun layerParamChangeCallsOnLayoutChange() {
+        val node = LayoutNode(20, 20, 100, 100, Modifier.graphicsLayer())
+        val owner = MockOwner()
+        node.attach(owner)
+        assertEquals(0, owner.layoutChangeCount)
+        node.innerLayoutNodeWrapper.onLayerBlockUpdated { scaleX = 0.5f }
+        assertEquals(1, owner.layoutChangeCount)
+        repeat(2) {
+            node.innerLayoutNodeWrapper.onLayerBlockUpdated { scaleX = 1f }
+        }
+        assertEquals(2, owner.layoutChangeCount)
+        node.innerLayoutNodeWrapper.onLayerBlockUpdated(null)
+        assertEquals(3, owner.layoutChangeCount)
+    }
+
+    @Test
     fun reuseModifiersThatImplementMultipleModifierInterfaces() {
         val drawAndLayoutModifier: Modifier = object : DrawModifier, LayoutModifier {
             override fun MeasureScope.measure(
@@ -1868,7 +1884,7 @@
         layoutChangeCount++
     }
 
-    override fun getFocusDirection(keyEvent: KeyEvent): FocusDirectionInternal? {
+    override fun getFocusDirection(keyEvent: KeyEvent): FocusDirection? {
         TODO("Not yet implemented")
     }
 
diff --git a/concurrent/futures-ktx/lint-baseline.xml b/concurrent/futures-ktx/lint-baseline.xml
deleted file mode 100644
index 8794ae8..0000000
--- a/concurrent/futures-ktx/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" version="4.2.0-beta06">
-
-</issues>
diff --git a/concurrent/listenablefuture-callback/api/1.0.0-beta02.txt b/concurrent/listenablefuture-callback/api/1.0.0-beta02.txt
deleted file mode 100644
index d95deda..0000000
--- a/concurrent/listenablefuture-callback/api/1.0.0-beta02.txt
+++ /dev/null
@@ -1,21 +0,0 @@
-// Signature format: 3.0
-package androidx.concurrent.callback {
-
-  public final class CallbackToFutureAdapter {
-    method public static <T> androidx.concurrent.ListenableFuture<T!> getFuture(androidx.concurrent.callback.CallbackToFutureAdapter.Resolver<T!>);
-  }
-
-  public static final class CallbackToFutureAdapter.Completer<T> {
-    method public void addCancellationListener(Runnable, java.util.concurrent.Executor);
-    method protected void finalize();
-    method public boolean set(T!);
-    method public boolean setCancelled();
-    method public boolean setException(Throwable);
-  }
-
-  public static interface CallbackToFutureAdapter.Resolver<T> {
-    method public Object? attachCompleter(androidx.concurrent.callback.CallbackToFutureAdapter.Completer<T!>) throws java.lang.Exception;
-  }
-
-}
-
diff --git a/concurrent/listenablefuture-callback/api/1.0.0-rc01.txt b/concurrent/listenablefuture-callback/api/1.0.0-rc01.txt
deleted file mode 100644
index d95deda..0000000
--- a/concurrent/listenablefuture-callback/api/1.0.0-rc01.txt
+++ /dev/null
@@ -1,21 +0,0 @@
-// Signature format: 3.0
-package androidx.concurrent.callback {
-
-  public final class CallbackToFutureAdapter {
-    method public static <T> androidx.concurrent.ListenableFuture<T!> getFuture(androidx.concurrent.callback.CallbackToFutureAdapter.Resolver<T!>);
-  }
-
-  public static final class CallbackToFutureAdapter.Completer<T> {
-    method public void addCancellationListener(Runnable, java.util.concurrent.Executor);
-    method protected void finalize();
-    method public boolean set(T!);
-    method public boolean setCancelled();
-    method public boolean setException(Throwable);
-  }
-
-  public static interface CallbackToFutureAdapter.Resolver<T> {
-    method public Object? attachCompleter(androidx.concurrent.callback.CallbackToFutureAdapter.Completer<T!>) throws java.lang.Exception;
-  }
-
-}
-
diff --git a/concurrent/listenablefuture-callback/api/restricted_1.0.0-beta02.txt b/concurrent/listenablefuture-callback/api/restricted_1.0.0-beta02.txt
deleted file mode 100644
index 0f47ccd..0000000
--- a/concurrent/listenablefuture-callback/api/restricted_1.0.0-beta02.txt
+++ /dev/null
@@ -1,29 +0,0 @@
-// Signature format: 3.0
-package androidx.concurrent.callback {
-
-  @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public abstract class AbstractResolvableFuture<V> implements androidx.concurrent.ListenableFuture<V> {
-    ctor protected AbstractResolvableFuture();
-    method public final void addListener(Runnable!, java.util.concurrent.Executor!);
-    method protected void afterDone();
-    method public final boolean cancel(boolean);
-    method public final V! get(long, java.util.concurrent.TimeUnit!) throws java.util.concurrent.ExecutionException, java.lang.InterruptedException, java.util.concurrent.TimeoutException;
-    method public final V! get() throws java.util.concurrent.ExecutionException, java.lang.InterruptedException;
-    method protected void interruptTask();
-    method public final boolean isCancelled();
-    method public final boolean isDone();
-    method protected String? pendingToString();
-    method protected boolean set(V?);
-    method protected boolean setException(Throwable!);
-    method protected boolean setFuture(androidx.concurrent.ListenableFuture<? extends V>!);
-    method protected final boolean wasInterrupted();
-  }
-
-  @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public final class ResolvableFuture<V> extends androidx.concurrent.callback.AbstractResolvableFuture<V> {
-    method public static <V> androidx.concurrent.callback.ResolvableFuture<V!>! create();
-    method public boolean set(V?);
-    method public boolean setException(Throwable!);
-    method public boolean setFuture(androidx.concurrent.ListenableFuture<? extends V>!);
-  }
-
-}
-
diff --git a/concurrent/listenablefuture-callback/api/restricted_1.0.0-rc01.txt b/concurrent/listenablefuture-callback/api/restricted_1.0.0-rc01.txt
deleted file mode 100644
index 0f47ccd..0000000
--- a/concurrent/listenablefuture-callback/api/restricted_1.0.0-rc01.txt
+++ /dev/null
@@ -1,29 +0,0 @@
-// Signature format: 3.0
-package androidx.concurrent.callback {
-
-  @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public abstract class AbstractResolvableFuture<V> implements androidx.concurrent.ListenableFuture<V> {
-    ctor protected AbstractResolvableFuture();
-    method public final void addListener(Runnable!, java.util.concurrent.Executor!);
-    method protected void afterDone();
-    method public final boolean cancel(boolean);
-    method public final V! get(long, java.util.concurrent.TimeUnit!) throws java.util.concurrent.ExecutionException, java.lang.InterruptedException, java.util.concurrent.TimeoutException;
-    method public final V! get() throws java.util.concurrent.ExecutionException, java.lang.InterruptedException;
-    method protected void interruptTask();
-    method public final boolean isCancelled();
-    method public final boolean isDone();
-    method protected String? pendingToString();
-    method protected boolean set(V?);
-    method protected boolean setException(Throwable!);
-    method protected boolean setFuture(androidx.concurrent.ListenableFuture<? extends V>!);
-    method protected final boolean wasInterrupted();
-  }
-
-  @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public final class ResolvableFuture<V> extends androidx.concurrent.callback.AbstractResolvableFuture<V> {
-    method public static <V> androidx.concurrent.callback.ResolvableFuture<V!>! create();
-    method public boolean set(V?);
-    method public boolean setException(Throwable!);
-    method public boolean setFuture(androidx.concurrent.ListenableFuture<? extends V>!);
-  }
-
-}
-
diff --git a/concurrent/listenablefuture-callback/lint-baseline.xml b/concurrent/listenablefuture-callback/lint-baseline.xml
deleted file mode 100644
index bdd761e..0000000
--- a/concurrent/listenablefuture-callback/lint-baseline.xml
+++ /dev/null
@@ -1,125 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 3.5.0-beta04" client="gradle" version="3.5.0-beta04">
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
-        errorLine1="    public final V get(long timeout, TimeUnit unit)"
-        errorLine2="                 ~">
-        <location
-            file="src/main/java/androidx/concurrent/callback/AbstractResolvableFuture.java"
-            line="353"
-            column="18"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
-        errorLine1="    public final V get(long timeout, TimeUnit unit)"
-        errorLine2="                                     ~~~~~~~~">
-        <location
-            file="src/main/java/androidx/concurrent/callback/AbstractResolvableFuture.java"
-            line="353"
-            column="38"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
-        errorLine1="    public final V get() throws InterruptedException, ExecutionException {"
-        errorLine2="                 ~">
-        <location
-            file="src/main/java/androidx/concurrent/callback/AbstractResolvableFuture.java"
-            line="468"
-            column="18"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
-        errorLine1="    public final void addListener(Runnable listener, Executor executor) {"
-        errorLine2="                                  ~~~~~~~~">
-        <location
-            file="src/main/java/androidx/concurrent/callback/AbstractResolvableFuture.java"
-            line="651"
-            column="35"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
-        errorLine1="    public final void addListener(Runnable listener, Executor executor) {"
-        errorLine2="                                                     ~~~~~~~~">
-        <location
-            file="src/main/java/androidx/concurrent/callback/AbstractResolvableFuture.java"
-            line="651"
-            column="54"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
-        errorLine1="    protected boolean setException(Throwable throwable) {"
-        errorLine2="                                   ~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/concurrent/callback/AbstractResolvableFuture.java"
-            line="708"
-            column="36"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
-        errorLine1="    protected boolean setFuture(ListenableFuture&lt;? extends V> future) {"
-        errorLine2="                                ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/concurrent/callback/AbstractResolvableFuture.java"
-            line="744"
-            column="33"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
-        errorLine1="        public boolean set(T value) {"
-        errorLine2="                           ~">
-        <location
-            file="src/main/java/androidx/concurrent/callback/CallbackToFutureAdapter.java"
-            line="245"
-            column="28"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
-        errorLine1="    public static &lt;V> ResolvableFuture&lt;V> create() {"
-        errorLine2="                      ~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/concurrent/callback/ResolvableFuture.java"
-            line="43"
-            column="23"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
-        errorLine1="    public boolean setException(Throwable throwable) {"
-        errorLine2="                                ~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/concurrent/callback/ResolvableFuture.java"
-            line="53"
-            column="33"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
-        errorLine1="    public boolean setFuture(ListenableFuture&lt;? extends V> future) {"
-        errorLine2="                             ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/concurrent/callback/ResolvableFuture.java"
-            line="58"
-            column="30"/>
-    </issue>
-
-</issues>
diff --git a/concurrent/listenablefuture/api/1.0.0-beta02.txt b/concurrent/listenablefuture/api/1.0.0-beta02.txt
deleted file mode 100644
index 54c6f60..0000000
--- a/concurrent/listenablefuture/api/1.0.0-beta02.txt
+++ /dev/null
@@ -1,9 +0,0 @@
-// Signature format: 3.0
-package androidx.concurrent {
-
-  public interface ListenableFuture<V> extends java.util.concurrent.Future<V> {
-    method public void addListener(Runnable, java.util.concurrent.Executor);
-  }
-
-}
-
diff --git a/concurrent/listenablefuture/api/1.0.0-rc01.txt b/concurrent/listenablefuture/api/1.0.0-rc01.txt
deleted file mode 100644
index 54c6f60..0000000
--- a/concurrent/listenablefuture/api/1.0.0-rc01.txt
+++ /dev/null
@@ -1,9 +0,0 @@
-// Signature format: 3.0
-package androidx.concurrent {
-
-  public interface ListenableFuture<V> extends java.util.concurrent.Future<V> {
-    method public void addListener(Runnable, java.util.concurrent.Executor);
-  }
-
-}
-
diff --git a/concurrent/listenablefuture/api/restricted_1.0.0-beta02.txt b/concurrent/listenablefuture/api/restricted_1.0.0-beta02.txt
deleted file mode 100644
index da4f6cc..0000000
--- a/concurrent/listenablefuture/api/restricted_1.0.0-beta02.txt
+++ /dev/null
@@ -1 +0,0 @@
-// Signature format: 3.0
diff --git a/concurrent/listenablefuture/api/restricted_1.0.0-rc01.txt b/concurrent/listenablefuture/api/restricted_1.0.0-rc01.txt
deleted file mode 100644
index da4f6cc..0000000
--- a/concurrent/listenablefuture/api/restricted_1.0.0-rc01.txt
+++ /dev/null
@@ -1 +0,0 @@
-// Signature format: 3.0
diff --git a/core/core-animation-integration-tests/testapp/lint-baseline.xml b/core/core-animation-integration-tests/testapp/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/core/core-animation-integration-tests/testapp/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/core/core-animation-testing/lint-baseline.xml b/core/core-animation-testing/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/core/core-animation-testing/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/core/core-appdigest/lint-baseline.xml b/core/core-appdigest/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/core/core-appdigest/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/core/core-google-shortcuts/api/1.0.0-beta01.txt b/core/core-google-shortcuts/api/1.0.0-beta01.txt
new file mode 100644
index 0000000..e6f50d0
--- /dev/null
+++ b/core/core-google-shortcuts/api/1.0.0-beta01.txt
@@ -0,0 +1 @@
+// Signature format: 4.0
diff --git a/core/core-google-shortcuts/api/public_plus_experimental_1.0.0-beta01.txt b/core/core-google-shortcuts/api/public_plus_experimental_1.0.0-beta01.txt
new file mode 100644
index 0000000..e6f50d0
--- /dev/null
+++ b/core/core-google-shortcuts/api/public_plus_experimental_1.0.0-beta01.txt
@@ -0,0 +1 @@
+// Signature format: 4.0
diff --git a/core/core-google-shortcuts/api/res-1.0.0-beta01.txt b/core/core-google-shortcuts/api/res-1.0.0-beta01.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/core/core-google-shortcuts/api/res-1.0.0-beta01.txt
diff --git a/core/core-google-shortcuts/api/restricted_1.0.0-beta01.txt b/core/core-google-shortcuts/api/restricted_1.0.0-beta01.txt
new file mode 100644
index 0000000..bdb2746
--- /dev/null
+++ b/core/core-google-shortcuts/api/restricted_1.0.0-beta01.txt
@@ -0,0 +1,9 @@
+// Signature format: 4.0
+package @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) androidx.core.google.shortcuts {
+
+  @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public class ShortcutInfoChangeListenerImpl extends androidx.core.content.pm.ShortcutInfoChangeListener {
+    method public static androidx.core.google.shortcuts.ShortcutInfoChangeListenerImpl getInstance(android.content.Context);
+  }
+
+}
+
diff --git a/core/core-google-shortcuts/build.gradle b/core/core-google-shortcuts/build.gradle
index e89eb00..ea77000 100644
--- a/core/core-google-shortcuts/build.gradle
+++ b/core/core-google-shortcuts/build.gradle
@@ -27,17 +27,15 @@
 
 android {
     defaultConfig {
-        minSdkVersion 23
+        multiDexEnabled = true
     }
 }
 
 dependencies {
-    api(KOTLIN_STDLIB)
-    api("androidx.core:core:1.6.0-alpha02")
+    api(project(":core:core"))
 
     implementation("com.google.firebase:firebase-appindexing:19.2.0")
     implementation("com.google.crypto.tink:tink-android:1.5.0")
-    implementation("androidx.security:security-crypto:1.0.0")
 
     androidTestImplementation(ANDROIDX_TEST_EXT_JUNIT)
     androidTestImplementation(ANDROIDX_TEST_CORE)
@@ -48,6 +46,7 @@
     androidTestImplementation(MOCKITO_CORE, excludes.bytebuddy)
     androidTestImplementation(DEXMAKER_MOCKITO, excludes.bytebuddy)
     androidTestImplementation(TRUTH)
+    androidTestImplementation(MULTIDEX)
 }
 
 androidx {
diff --git a/core/core-google-shortcuts/src/androidTest/AndroidManifest.xml b/core/core-google-shortcuts/src/androidTest/AndroidManifest.xml
index 8a5a29d..f5f3686 100644
--- a/core/core-google-shortcuts/src/androidTest/AndroidManifest.xml
+++ b/core/core-google-shortcuts/src/androidTest/AndroidManifest.xml
@@ -15,8 +15,10 @@
   limitations under the License.
   -->
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
     package="androidx.core.google.shortcuts.test">
-    <application>
+    <uses-sdk tools:overrideLibrary="com.google.firebase.icing"/>
+    <application android:name="androidx.multidex.MultiDexApplication">
         <activity android:name=".TestActivity"/>
     </application>
 </manifest>
diff --git a/core/core-google-shortcuts/src/androidTest/java/androidx/core/google/shortcuts/ShortcutInfoChangeListenerImplTest.java b/core/core-google-shortcuts/src/androidTest/java/androidx/core/google/shortcuts/ShortcutInfoChangeListenerImplTest.java
index 834246e..97b77a7 100644
--- a/core/core-google-shortcuts/src/androidTest/java/androidx/core/google/shortcuts/ShortcutInfoChangeListenerImplTest.java
+++ b/core/core-google-shortcuts/src/androidTest/java/androidx/core/google/shortcuts/ShortcutInfoChangeListenerImplTest.java
@@ -16,11 +16,6 @@
 
 package androidx.core.google.shortcuts;
 
-import static androidx.core.google.shortcuts.ShortcutUtils.SHORTCUT_DESCRIPTION_KEY;
-import static androidx.core.google.shortcuts.ShortcutUtils.SHORTCUT_LABEL_KEY;
-import static androidx.core.google.shortcuts.ShortcutUtils.SHORTCUT_TAG_KEY;
-import static androidx.core.google.shortcuts.ShortcutUtils.SHORTCUT_URL_KEY;
-
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.Mockito.mock;
@@ -30,17 +25,18 @@
 
 import android.content.Context;
 import android.content.Intent;
-import android.util.Base64;
 
 import androidx.core.content.pm.ShortcutInfoCompat;
+import androidx.core.google.shortcuts.builders.CapabilityBuilder;
+import androidx.core.google.shortcuts.builders.ParameterBuilder;
+import androidx.core.google.shortcuts.builders.ShortcutBuilder;
 import androidx.core.graphics.drawable.IconCompat;
 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.collect.ImmutableList;
-import com.google.crypto.tink.KeysetHandle;
-import com.google.crypto.tink.Mac;
 import com.google.firebase.appindexing.Action;
 import com.google.firebase.appindexing.FirebaseAppIndex;
 import com.google.firebase.appindexing.FirebaseUserActions;
@@ -51,13 +47,13 @@
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
 
-import java.nio.charset.StandardCharsets;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
 import java.util.stream.Collectors;
 
 @RunWith(AndroidJUnit4.class)
+@SdkSuppress(minSdkVersion = 21) // This module should only be called for version 21+.
 public class ShortcutInfoChangeListenerImplTest {
     private FirebaseAppIndex mFirebaseAppIndex;
     private FirebaseUserActions mFirebaseUserActions;
@@ -78,7 +74,7 @@
     public void onShortcutUpdated_publicIntent_savesToAppIndex() throws Exception {
         ArgumentCaptor<Indexable> indexableCaptor = ArgumentCaptor.forClass(Indexable.class);
 
-        Intent intent = Intent.parseUri("http://www.google.com", 0);
+        Intent intent = Intent.parseUri("app://shortcut", 0);
         ShortcutInfoCompat shortcut = new ShortcutInfoCompat.Builder(mContext, "publicIntent")
                 .setShortLabel("short label")
                 .setLongLabel("long label")
@@ -90,15 +86,12 @@
 
         verify(mFirebaseAppIndex, only()).update(indexableCaptor.capture());
         List<Indexable> allValues = indexableCaptor.getAllValues();
-        Indexable expected = new Indexable.Builder()
-                .setName("short label")
+        Indexable expected = new ShortcutBuilder()
                 .setId("publicIntent")
+                .setShortcutLabel("short label")
+                .setShortcutDescription("long label")
                 .setUrl(ShortcutUtils.getIndexableUrl(mContext, "publicIntent"))
-                .put(SHORTCUT_LABEL_KEY, "short label")
-                .put(SHORTCUT_DESCRIPTION_KEY, "long label")
-                .put(SHORTCUT_URL_KEY, ShortcutUtils.getIndexableShortcutUrl(mContext, intent,
-                        null))
-                .setImage("content://abc")
+                .setShortcutUrl(ShortcutUtils.getIndexableShortcutUrl(mContext, intent, null))
                 .build();
         assertThat(allValues).containsExactly(expected);
     }
@@ -108,7 +101,7 @@
     public void onShortcutUpdated_withCapabilityBinding_savesToAppIndex() throws Exception {
         ArgumentCaptor<Indexable> indexableCaptor = ArgumentCaptor.forClass(Indexable.class);
 
-        Intent intent = Intent.parseUri("http://www.google.com", 0);
+        Intent intent = Intent.parseUri("app://shortcut", 0);
         ShortcutInfoCompat shortcut = new ShortcutInfoCompat.Builder(mContext, "publicIntent")
                 .setShortLabel("short label")
                 .setLongLabel("long label")
@@ -123,38 +116,40 @@
 
         verify(mFirebaseAppIndex, only()).update(indexableCaptor.capture());
         List<Indexable> allValues = indexableCaptor.getAllValues();
-        Indexable.Builder expectedBuilder = new Indexable.Builder()
-                .setName("short label")
+        ShortcutBuilder expectedBuilder = new ShortcutBuilder()
                 .setId("publicIntent")
+                .setShortcutLabel("short label")
+                .setShortcutDescription("long label")
                 .setUrl(ShortcutUtils.getIndexableUrl(mContext, "publicIntent"))
-                .put(SHORTCUT_LABEL_KEY, "short label")
-                .put(SHORTCUT_DESCRIPTION_KEY, "long label")
-                .put(SHORTCUT_URL_KEY, ShortcutUtils.getIndexableShortcutUrl(mContext, intent,
-                        null));
-        // The order of isPartOf field matters during comparison. However since the order is not
+                .setShortcutUrl(ShortcutUtils.getIndexableShortcutUrl(mContext, intent, null));
+        // The order of capability field matters during comparison. However since the order is not
         // deterministic because the data is stored in maps and sets in ShortcutInfoCompat, we
         // check for all possible orderings to make the test more reliable.
         Indexable expected1 = expectedBuilder
-                .setIsPartOf(
-                        new Indexable.Builder()
-                                .setId("actions.intent.STOP_EXERCISE/exercise.name")
-                                .setName("stop running")
-                                .setAlternateName("stop jogging"),
-                        new Indexable.Builder()
-                                .setId("actions.intent.START_EXERCISE/exercise.name")
-                                .setName("start running")
-                                .setAlternateName("start jogging"))
+                .setCapability(
+                        new CapabilityBuilder()
+                                .setName("actions.intent.STOP_EXERCISE")
+                                .setParameter(new ParameterBuilder()
+                                        .setName("exercise.name")
+                                        .setValue("stop running", "stop jogging")),
+                        new CapabilityBuilder()
+                                .setName("actions.intent.START_EXERCISE")
+                                .setParameter(new ParameterBuilder()
+                                        .setName("exercise.name")
+                                        .setValue("start running", "start jogging")))
                 .build();
         Indexable expected2 = expectedBuilder
-                .setIsPartOf(
-                        new Indexable.Builder()
-                                .setId("actions.intent.START_EXERCISE/exercise.name")
-                                .setName("start running")
-                                .setAlternateName("start jogging"),
-                        new Indexable.Builder()
-                                .setId("actions.intent.STOP_EXERCISE/exercise.name")
-                                .setName("stop running")
-                                .setAlternateName("stop jogging"))
+                .setCapability(
+                        new CapabilityBuilder()
+                                .setName("actions.intent.START_EXERCISE")
+                                .setParameter(new ParameterBuilder()
+                                        .setName("exercise.name")
+                                        .setValue("start running", "start jogging")),
+                        new CapabilityBuilder()
+                                .setName("actions.intent.STOP_EXERCISE")
+                                .setParameter(new ParameterBuilder()
+                                        .setName("exercise.name")
+                                        .setValue("stop running", "stop jogging")))
                 .build();
         assertThat(allValues).hasSize(1);
         assertThat(allValues).containsAnyOf(expected1, expected2);
@@ -165,7 +160,7 @@
     public void onShortcutUpdated_withCapabilityBindingNoParams_savesToAppIndex() throws Exception {
         ArgumentCaptor<Indexable> indexableCaptor = ArgumentCaptor.forClass(Indexable.class);
 
-        Intent intent = Intent.parseUri("http://www.google.com", 0);
+        Intent intent = Intent.parseUri("app://shortcut", 0);
         ShortcutInfoCompat shortcut = new ShortcutInfoCompat.Builder(mContext, "publicIntent")
                 .setShortLabel("short label")
                 .setLongLabel("long label")
@@ -177,15 +172,13 @@
 
         verify(mFirebaseAppIndex, only()).update(indexableCaptor.capture());
         List<Indexable> allValues = indexableCaptor.getAllValues();
-        Indexable expected = new Indexable.Builder()
-                .setName("short label")
+        Indexable expected = new ShortcutBuilder()
                 .setId("publicIntent")
+                .setShortcutLabel("short label")
+                .setShortcutDescription("long label")
                 .setUrl(ShortcutUtils.getIndexableUrl(mContext, "publicIntent"))
-                .put(SHORTCUT_LABEL_KEY, "short label")
-                .put(SHORTCUT_DESCRIPTION_KEY, "long label")
-                .put(SHORTCUT_URL_KEY, ShortcutUtils.getIndexableShortcutUrl(mContext, intent,
-                        null))
-                .setIsPartOf(new Indexable.Builder().setId("actions.intent.TWEET"))
+                .setShortcutUrl(ShortcutUtils.getIndexableShortcutUrl(mContext, intent, null))
+                .setCapability(new CapabilityBuilder().setName("actions.intent.TWEET"))
                 .build();
         assertThat(allValues).containsExactly(expected);
     }
@@ -208,13 +201,12 @@
 
         verify(mFirebaseAppIndex, only()).update(indexableCaptor.capture());
         List<Indexable> allValues = indexableCaptor.getAllValues();
-        Indexable expected = new Indexable.Builder()
+        Indexable expected = new ShortcutBuilder()
                 .setName("short label")
-                .setUrl(ShortcutUtils.getIndexableUrl(mContext, "privateIntent"))
                 .setId("privateIntent")
-                .put("shortcutLabel", "short label")
-                .put("shortcutUrl", ShortcutUtils.getIndexableShortcutUrl(mContext, intent,
-                        null))
+                .setShortcutLabel("short label")
+                .setUrl(ShortcutUtils.getIndexableUrl(mContext, "privateIntent"))
+                .setShortcutUrl(ShortcutUtils.getIndexableShortcutUrl(mContext, intent, null))
                 .build();
         assertThat(allValues).containsExactly(expected);
     }
@@ -224,7 +216,7 @@
     public void onShortcutAdded_savesToAppIndex() throws Exception {
         ArgumentCaptor<Indexable> indexableCaptor = ArgumentCaptor.forClass(Indexable.class);
 
-        Intent intent = Intent.parseUri("http://www.google.com", 0);
+        Intent intent = Intent.parseUri("app://shortcut", 0);
         ShortcutInfoCompat shortcut = new ShortcutInfoCompat.Builder(mContext, "intent")
                 .setShortLabel("short label")
                 .setLongLabel("long label")
@@ -236,55 +228,18 @@
 
         verify(mFirebaseAppIndex, only()).update(indexableCaptor.capture());
         List<Indexable> allValues = indexableCaptor.getAllValues();
-        Indexable expected = new Indexable.Builder()
+        Indexable expected = new ShortcutBuilder()
                 .setName("short label")
                 .setId("intent")
+                .setShortcutLabel("short label")
+                .setDescription("long label")
+                .setShortcutDescription("long label")
                 .setUrl(ShortcutUtils.getIndexableUrl(mContext, "intent"))
-                .put(SHORTCUT_LABEL_KEY, "short label")
-                .put(SHORTCUT_DESCRIPTION_KEY, "long label")
-                .put(SHORTCUT_URL_KEY, ShortcutUtils.getIndexableShortcutUrl(mContext, intent,
-                        null))
-                .setImage("content://abc")
+                .setShortcutUrl(ShortcutUtils.getIndexableShortcutUrl(mContext, intent, null))
                 .build();
         assertThat(allValues).containsExactly(expected);
     }
 
-    @SmallTest
-    @Test
-    public void onShortcutAdded_withMacSignature_canVerifySignature() throws Exception {
-        ArgumentCaptor<Indexable> indexableCaptor = ArgumentCaptor.forClass(Indexable.class);
-
-        KeysetHandle keysetHandle = ShortcutUtils.getOrCreateShortcutKeysetHandle(mContext);
-        // Make sure keyset can be created.
-        assertThat(keysetHandle).isNotNull();
-
-        mShortcutInfoChangeListener = new ShortcutInfoChangeListenerImpl(
-                mContext, mFirebaseAppIndex, mFirebaseUserActions, keysetHandle);
-
-        Intent intent = Intent.parseUri("http://www.google.com", 0);
-        ShortcutInfoCompat shortcut = new ShortcutInfoCompat.Builder(mContext, "intent")
-                .setShortLabel("short label")
-                .setIntent(intent)
-                .build();
-        mShortcutInfoChangeListener.onShortcutAdded(Collections.singletonList(shortcut));
-
-        verify(mFirebaseAppIndex, only()).update(indexableCaptor.capture());
-        String trampolineIntentString = ShortcutUtils.getIndexableShortcutUrl(mContext, intent,
-                keysetHandle);
-
-        Intent trampolineIntent = Intent.parseUri(trampolineIntentString, 0);
-        // Make sure the trampoline activity is package restricted.
-        assertThat(trampolineIntent.getPackage()).isEqualTo(mContext.getPackageName());
-
-        String tag = trampolineIntent.getStringExtra(SHORTCUT_TAG_KEY);
-        String shortcutUrl = trampolineIntent.getStringExtra(SHORTCUT_URL_KEY);
-        Mac mac = keysetHandle.getPrimitive(Mac.class);
-
-        // Will throw exception if verification fails.
-        mac.verifyMac(Base64.decode(tag, Base64.DEFAULT),
-                shortcutUrl.getBytes(StandardCharsets.UTF_8));
-    }
-
     @Test
     @SmallTest
     public void onShortcutRemoved_removeFromAppIndex() {
diff --git a/core/core-google-shortcuts/src/androidTest/java/androidx/core/google/shortcuts/ShortcutUtilsTest.java b/core/core-google-shortcuts/src/androidTest/java/androidx/core/google/shortcuts/ShortcutUtilsTest.java
index f731f1d..f30f720 100644
--- a/core/core-google-shortcuts/src/androidTest/java/androidx/core/google/shortcuts/ShortcutUtilsTest.java
+++ b/core/core-google-shortcuts/src/androidTest/java/androidx/core/google/shortcuts/ShortcutUtilsTest.java
@@ -30,6 +30,7 @@
 
 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.crypto.tink.KeysetHandle;
@@ -38,6 +39,7 @@
 import org.junit.runner.RunWith;
 
 @RunWith(AndroidJUnit4.class)
+@SdkSuppress(minSdkVersion = 21) // This module should only be called for version 21+.
 public class ShortcutUtilsTest {
     private static final String TEST_PACKAGE = "com.test.package";
 
@@ -65,7 +67,7 @@
 
         String shortcutUrl = ShortcutUtils.getIndexableShortcutUrl(context, intent, null);
 
-        String expectedShortcutUrl = "http://www.google.com";
+        String expectedShortcutUrl = "intent://www.google.com#Intent;scheme=http;end";
         assertThat(shortcutUrl).isEqualTo(expectedShortcutUrl);
     }
 
@@ -84,7 +86,7 @@
         assertThat(trampolineIntent.getPackage()).isEqualTo(context.getPackageName());
         assertThat(trampolineIntent.getAction()).isEqualTo(SHORTCUT_LISTENER_INTENT_FILTER_ACTION);
         assertThat(trampolineIntent.getStringExtra(SHORTCUT_TAG_KEY)).isNotEmpty();
-        String expectedShortcutUrl = "http://www.google.com";
+        String expectedShortcutUrl = "intent://www.google.com#Intent;scheme=http;end";
         assertThat(trampolineIntent.getStringExtra(SHORTCUT_URL_KEY))
                 .isEqualTo(expectedShortcutUrl);
     }
diff --git a/core/core-google-shortcuts/src/androidTest/java/androidx/core/google/shortcuts/TrampolineActivityTest.java b/core/core-google-shortcuts/src/androidTest/java/androidx/core/google/shortcuts/TrampolineActivityTest.java
index 6a82d85..8705d6f 100644
--- a/core/core-google-shortcuts/src/androidTest/java/androidx/core/google/shortcuts/TrampolineActivityTest.java
+++ b/core/core-google-shortcuts/src/androidTest/java/androidx/core/google/shortcuts/TrampolineActivityTest.java
@@ -35,6 +35,7 @@
 import androidx.test.core.app.ApplicationProvider;
 import androidx.test.espresso.intent.Intents;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SdkSuppress;
 import androidx.test.filters.SmallTest;
 
 import com.google.crypto.tink.KeysetHandle;
@@ -47,6 +48,7 @@
 import java.util.List;
 
 @RunWith(AndroidJUnit4.class)
+@SdkSuppress(minSdkVersion = 21) // This module should only be called for version 21+.
 public class TrampolineActivityTest {
     private static final String SHORTCUT_LISTENER_INTENT_FILTER_ACTION = "androidx.core.content.pm"
             + ".SHORTCUT_LISTENER";
diff --git a/core/core-google-shortcuts/src/androidTest/java/androidx/core/google/shortcuts/builders/ShortcutBuilderTest.java b/core/core-google-shortcuts/src/androidTest/java/androidx/core/google/shortcuts/builders/ShortcutBuilderTest.java
new file mode 100644
index 0000000..6b4763f
--- /dev/null
+++ b/core/core-google-shortcuts/src/androidTest/java/androidx/core/google/shortcuts/builders/ShortcutBuilderTest.java
@@ -0,0 +1,102 @@
+/*
+ * 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.core.google.shortcuts.builders;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SdkSuppress;
+import androidx.test.filters.SmallTest;
+
+import com.google.firebase.appindexing.Indexable;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@SdkSuppress(minSdkVersion = 21) // This module should only be called for version 21+.
+public class ShortcutBuilderTest {
+
+    @SmallTest
+    @Test
+    public void testBuildShortcut() {
+        Indexable shortcutIndexable = new ShortcutBuilder()
+                .setId("id")
+                .setShortcutLabel("shortcut label")
+                .setShortcutDescription("shortcut description")
+                .setUrl("url")
+                .setShortcutUrl("shortcut url")
+                .build();
+
+        assertThat(shortcutIndexable).isEqualTo(new Indexable.Builder("Shortcut")
+                .setId("id")
+                .setName("shortcut label")
+                .setDescription("shortcut description")
+                .setUrl("url")
+                .put("shortcutLabel", "shortcut label")
+                .put("shortcutDescription", "shortcut description")
+                .put("shortcutUrl", "shortcut url")
+                .build());
+    }
+
+    @SmallTest
+    @Test
+    public void testBuildShortcutWithCapability() throws Exception {
+        Indexable shortcutIndexable = new ShortcutBuilder()
+                .setCapability(
+                        new CapabilityBuilder().setName("capability1"),
+                        new CapabilityBuilder().setName("capability2"))
+                .build();
+
+        assertThat(shortcutIndexable).isEqualTo(new Indexable.Builder("Shortcut")
+                .put("capability",
+                        new Indexable.Builder("Capability").setName("capability1").build(),
+                        new Indexable.Builder("Capability").setName("capability2").build())
+                .build());
+    }
+
+    @SmallTest
+    @Test
+    public void testBuildShortcutWithCapabilityAndParameter() throws Exception {
+        Indexable shortcutIndexable = new ShortcutBuilder()
+                .setCapability(new CapabilityBuilder()
+                        .setName("capability")
+                        .setParameter(
+                                new ParameterBuilder()
+                                        .setName("parameter1")
+                                        .setValue("value1", "value2"),
+                                new ParameterBuilder()
+                                        .setName("parameter2")
+                                        .setValue("value3", "value4")))
+                .build();
+
+        assertThat(shortcutIndexable).isEqualTo(new Indexable.Builder("Shortcut")
+                .put("capability", new Indexable.Builder("Capability")
+                        .setName("capability")
+                        .put("parameter",
+                                new Indexable.Builder("Parameter")
+                                        .setName("parameter1")
+                                        .put("value", "value1", "value2")
+                                        .build(),
+                                new Indexable.Builder("Parameter")
+                                        .setName("parameter2")
+                                        .put("value", "value3", "value4")
+                                        .build())
+                        .build())
+                .build());
+    }
+}
diff --git a/core/core-google-shortcuts/src/main/java/androidx/core/google/shortcuts/ShortcutInfoChangeListenerImpl.java b/core/core-google-shortcuts/src/main/java/androidx/core/google/shortcuts/ShortcutInfoChangeListenerImpl.java
index 826b04b..88e9a59 100644
--- a/core/core-google-shortcuts/src/main/java/androidx/core/google/shortcuts/ShortcutInfoChangeListenerImpl.java
+++ b/core/core-google-shortcuts/src/main/java/androidx/core/google/shortcuts/ShortcutInfoChangeListenerImpl.java
@@ -19,29 +19,29 @@
 
 import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP;
 import static androidx.core.google.shortcuts.ShortcutUtils.CAPABILITY_PARAM_SEPARATOR;
-import static androidx.core.google.shortcuts.ShortcutUtils.SHORTCUT_DESCRIPTION_KEY;
-import static androidx.core.google.shortcuts.ShortcutUtils.SHORTCUT_LABEL_KEY;
-import static androidx.core.google.shortcuts.ShortcutUtils.SHORTCUT_URL_KEY;
 
 import android.content.Context;
+import android.os.Build;
+import android.os.PersistableBundle;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
+import androidx.annotation.RequiresApi;
 import androidx.annotation.RestrictTo;
 import androidx.annotation.VisibleForTesting;
 import androidx.core.content.pm.ShortcutInfoChangeListener;
 import androidx.core.content.pm.ShortcutInfoCompat;
-import androidx.core.graphics.drawable.IconCompat;
+import androidx.core.google.shortcuts.builders.CapabilityBuilder;
+import androidx.core.google.shortcuts.builders.ParameterBuilder;
+import androidx.core.google.shortcuts.builders.ShortcutBuilder;
 
 import com.google.crypto.tink.KeysetHandle;
 import com.google.firebase.appindexing.Action;
 import com.google.firebase.appindexing.FirebaseAppIndex;
 import com.google.firebase.appindexing.FirebaseUserActions;
 import com.google.firebase.appindexing.Indexable;
-import com.google.firebase.appindexing.builders.IndexableBuilder;
 
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.List;
 
 /**
@@ -161,69 +161,79 @@
         String url = ShortcutUtils.getIndexableUrl(mContext, shortcut.getId());
         String shortcutUrl = ShortcutUtils.getIndexableShortcutUrl(mContext, shortcut.getIntent(),
                 mKeysetHandle);
+        String name = shortcut.getShortLabel().toString();
 
-        Indexable.Builder builder = new Indexable.Builder()
+        ShortcutBuilder shortcutBuilder = new ShortcutBuilder()
                 .setId(shortcut.getId())
                 .setUrl(url)
-                .setName(shortcut.getShortLabel().toString())
-                .put(SHORTCUT_URL_KEY, shortcutUrl)
-                .put(SHORTCUT_LABEL_KEY, shortcut.getShortLabel().toString());
-
+                .setShortcutLabel(name)
+                .setShortcutUrl(shortcutUrl);
         if (shortcut.getLongLabel() != null) {
-            builder.put(SHORTCUT_DESCRIPTION_KEY, shortcut.getLongLabel().toString());
-        }
-
-        if (shortcut.getIcon() != null && shortcut.getIcon().getType() == IconCompat.TYPE_URI) {
-            builder.setImage(shortcut.getIcon().getUri().toString());
+            shortcutBuilder.setShortcutDescription(shortcut.getLongLabel().toString());
         }
 
         // Add capability binding
-        if (shortcut.getCategories() != null) {
-            List<Indexable.Builder> partOfList = new ArrayList<>();
-            for (String capability : shortcut.getCategories()) {
-                if (!ShortcutUtils.isAppActionCapability(capability)) {
-                    continue;
-                }
-
-                if (shortcut.getExtras() == null
-                        || shortcut.getExtras().getStringArray(capability) == null
-                        || shortcut.getExtras().getStringArray(capability).length == 0) {
-                    // Shortcut has a capability binding without any parameter binding.
-                    partOfList.add(buildPartOfIndexable(capability, null));
-                } else {
-                    String[] params = shortcut.getExtras().getStringArray(capability);
-                    for (String param : params) {
-                        String capabilityParam = capability + CAPABILITY_PARAM_SEPARATOR + param;
-                        partOfList.add(buildPartOfIndexable(capabilityParam,
-                                shortcut.getExtras().getStringArray(capabilityParam)));
+        if (Build.VERSION.SDK_INT >= 21) {
+            if (shortcut.getCategories() != null) {
+                List<CapabilityBuilder> capabilityList = new ArrayList<>();
+                for (String capability : shortcut.getCategories()) {
+                    if (!ShortcutUtils.isAppActionCapability(capability)) {
+                        continue;
                     }
-                }
-            }
 
-            if (!partOfList.isEmpty()) {
-                builder.setIsPartOf(partOfList.toArray(new IndexableBuilder[0]));
+                    capabilityList.add(Api21Impl.buildCapability(capability, shortcut.getExtras()));
+                }
+
+                if (!capabilityList.isEmpty()) {
+                    shortcutBuilder
+                            .setCapability(capabilityList.toArray(new CapabilityBuilder[0]));
+                }
             }
         }
 
         // By default, the indexable will be saved only on-device.
-        return builder.build();
+        return shortcutBuilder.build();
     }
 
-    @NonNull
-    private Indexable.Builder buildPartOfIndexable(@NonNull String capabilityParam,
-            @Nullable String[] values) {
-        Indexable.Builder partOfBuilder = new Indexable.Builder()
-                .setId(capabilityParam);
-        if (values == null) {
-            return partOfBuilder;
+    @RequiresApi(21)
+    private static class Api21Impl {
+        @NonNull
+        static CapabilityBuilder buildCapability(@NonNull String capability,
+                @Nullable PersistableBundle shortcutInfoExtras) {
+            CapabilityBuilder capabilityBuilder = new CapabilityBuilder()
+                    .setName(capability);
+            if (shortcutInfoExtras == null) {
+                return capabilityBuilder;
+            }
+
+            String[] params = shortcutInfoExtras.getStringArray(capability);
+            if (params == null) {
+                return capabilityBuilder;
+            }
+
+            List<ParameterBuilder> parameterBuilders = new ArrayList<>();
+            for (String param : params) {
+                ParameterBuilder parameterBuilder =
+                        new ParameterBuilder()
+                                .setName(param);
+                String capabilityParamKey = capability + CAPABILITY_PARAM_SEPARATOR + param;
+                String[] values = shortcutInfoExtras.getStringArray(capabilityParamKey);
+                if (values == null || values.length == 0) {
+                    // ignore this parameter since no values were given
+                    continue;
+                }
+
+                parameterBuilder.setValue(values);
+                parameterBuilders.add(parameterBuilder);
+            }
+
+            if (parameterBuilders.size() > 0) {
+                capabilityBuilder
+                        .setParameter(parameterBuilders.toArray(new ParameterBuilder[0]));
+            }
+            return capabilityBuilder;
         }
 
-        if (values.length > 0) {
-            partOfBuilder.setName(values[0]);
-        }
-        if (values.length > 1) {
-            partOfBuilder.setAlternateName(Arrays.copyOfRange(values, 1, values.length));
-        }
-        return partOfBuilder;
+        private Api21Impl() {}
     }
 }
diff --git a/core/core-google-shortcuts/src/main/java/androidx/core/google/shortcuts/ShortcutUtils.java b/core/core-google-shortcuts/src/main/java/androidx/core/google/shortcuts/ShortcutUtils.java
index 5b11811..7f457d9 100644
--- a/core/core-google-shortcuts/src/main/java/androidx/core/google/shortcuts/ShortcutUtils.java
+++ b/core/core-google-shortcuts/src/main/java/androidx/core/google/shortcuts/ShortcutUtils.java
@@ -34,20 +34,18 @@
 import com.google.crypto.tink.mac.MacConfig;
 
 import java.io.IOException;
-import java.nio.charset.StandardCharsets;
+import java.nio.charset.Charset;
 import java.security.GeneralSecurityException;
 
 /**
- * Utility methods and constants used by the google shortcuts library.
+ * Utility methods and constants used by the Google shortcuts library.
  *
  * @hide
  */
 @RestrictTo(LIBRARY)
 class ShortcutUtils {
-    public static final String SHORTCUT_LABEL_KEY = "shortcutLabel";
-    public static final String SHORTCUT_DESCRIPTION_KEY = "shortcutDescription";
-    public static final String SHORTCUT_URL_KEY = "shortcutUrl";
     public static final String ID_KEY = "id";
+    public static final String SHORTCUT_URL_KEY = "shortcutUrl";
     public static final String CAPABILITY_PARAM_SEPARATOR = "/";
     public static final String SHORTCUT_TAG_KEY = "shortcutTag";
     public static final String SHORTCUT_LISTENER_INTENT_FILTER_ACTION = "androidx.core.content.pm"
@@ -88,7 +86,7 @@
      */
     public static String getIndexableShortcutUrl(@NonNull Context context,
             @NonNull Intent shortcutIntent, @Nullable KeysetHandle keysetHandle) {
-        String shortcutUrl = shortcutIntent.toUri(0);
+        String shortcutUrl = shortcutIntent.toUri(Intent.URI_INTENT_SCHEME);
         if (keysetHandle == null) {
             // If keyset handle is null, then create the shortcut without using the Trampoline
             // Activity. This means that only shortcuts with exported intent will work. Those with
@@ -104,7 +102,7 @@
             // 2. only the shortcut that was indexed using this library can be opened. You cannot
             // use the Trampoline Activity to open arbitrary shortcuts.
             Mac mac = keysetHandle.getPrimitive(Mac.class);
-            byte[] tag = mac.computeMac(shortcutUrl.getBytes(StandardCharsets.UTF_8));
+            byte[] tag = mac.computeMac(shortcutUrl.getBytes(Charset.forName("UTF-8")));
             String tagString = Base64.encodeToString(tag, Base64.DEFAULT);
 
             Intent trampolineIntent = new Intent(context, TrampolineActivity.class);
diff --git a/core/core-google-shortcuts/src/main/java/androidx/core/google/shortcuts/TrampolineActivity.java b/core/core-google-shortcuts/src/main/java/androidx/core/google/shortcuts/TrampolineActivity.java
index 3dfe866..adf51d0 100644
--- a/core/core-google-shortcuts/src/main/java/androidx/core/google/shortcuts/TrampolineActivity.java
+++ b/core/core-google-shortcuts/src/main/java/androidx/core/google/shortcuts/TrampolineActivity.java
@@ -33,7 +33,7 @@
 import com.google.crypto.tink.Mac;
 
 import java.net.URISyntaxException;
-import java.nio.charset.StandardCharsets;
+import java.nio.charset.Charset;
 import java.security.GeneralSecurityException;
 
 
@@ -67,9 +67,9 @@
                     Mac mac = sKeysetHandle.getPrimitive(Mac.class);
                     // Will throw GeneralSecurityException when verifyMac fails.
                     mac.verifyMac(Base64.decode(tag, Base64.DEFAULT),
-                            shortcutUrl.getBytes(StandardCharsets.UTF_8));
+                            shortcutUrl.getBytes(Charset.forName("UTF-8")));
 
-                    Intent shortcutIntent = Intent.parseUri(shortcutUrl, 0);
+                    Intent shortcutIntent = Intent.parseUri(shortcutUrl, Intent.URI_INTENT_SCHEME);
                     startActivity(shortcutIntent);
                 } catch (GeneralSecurityException | URISyntaxException e) {
                     Log.w(TAG, "failed to open shortcut url", e);
diff --git a/core/core-google-shortcuts/src/main/java/androidx/core/google/shortcuts/builders/CapabilityBuilder.java b/core/core-google-shortcuts/src/main/java/androidx/core/google/shortcuts/builders/CapabilityBuilder.java
new file mode 100644
index 0000000..d18753f
--- /dev/null
+++ b/core/core-google-shortcuts/src/main/java/androidx/core/google/shortcuts/builders/CapabilityBuilder.java
@@ -0,0 +1,45 @@
+/*
+ * 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.core.google.shortcuts.builders;
+
+import static androidx.annotation.RestrictTo.Scope.LIBRARY;
+import static androidx.core.google.shortcuts.builders.Constants.CAPABILITY_PARAMETER_KEY;
+import static androidx.core.google.shortcuts.builders.Constants.CAPABILITY_TYPE;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.RestrictTo;
+
+import com.google.firebase.appindexing.builders.IndexableBuilder;
+
+/**
+ * Builder for the Capability section in the Shortcut Corpus.
+ *
+ * @hide
+ */
+@RestrictTo(LIBRARY)
+public class CapabilityBuilder extends IndexableBuilder<CapabilityBuilder> {
+    public CapabilityBuilder() {
+        super(CAPABILITY_TYPE);
+    }
+
+    /** Sets one or more parameters for the given capability. */
+    @NonNull
+    public CapabilityBuilder setParameter(
+            @NonNull ParameterBuilder... parameter) {
+        return put(CAPABILITY_PARAMETER_KEY, parameter);
+    }
+}
diff --git a/core/core-google-shortcuts/src/main/java/androidx/core/google/shortcuts/builders/Constants.java b/core/core-google-shortcuts/src/main/java/androidx/core/google/shortcuts/builders/Constants.java
new file mode 100644
index 0000000..35f8981
--- /dev/null
+++ b/core/core-google-shortcuts/src/main/java/androidx/core/google/shortcuts/builders/Constants.java
@@ -0,0 +1,44 @@
+/*
+ * 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.core.google.shortcuts.builders;
+
+import static androidx.annotation.RestrictTo.Scope.LIBRARY;
+
+import androidx.annotation.RestrictTo;
+
+/**
+ * Constants for the Shortcut Corpus.
+ *
+ * @hide
+ */
+@RestrictTo(LIBRARY)
+public class Constants {
+    public static final String SHORTCUT_TYPE = "Shortcut";
+    public static final String CAPABILITY_TYPE = "Capability";
+    public static final String PARAMETER_TYPE = "Parameter";
+
+    public static final String SHORTCUT_LABEL_KEY = "shortcutLabel";
+    public static final String SHORTCUT_DESCRIPTION_KEY = "shortcutDescription";
+    public static final String SHORTCUT_URL_KEY = "shortcutUrl";
+    public static final String SHORTCUT_CAPABILITY_KEY = "capability";
+
+    public static final String CAPABILITY_PARAMETER_KEY = "parameter";
+
+    public static final String PARAMETER_VALUE_KEY = "value";
+
+    private Constants() {}
+}
diff --git a/core/core-google-shortcuts/src/main/java/androidx/core/google/shortcuts/builders/ParameterBuilder.java b/core/core-google-shortcuts/src/main/java/androidx/core/google/shortcuts/builders/ParameterBuilder.java
new file mode 100644
index 0000000..4ea41e04
--- /dev/null
+++ b/core/core-google-shortcuts/src/main/java/androidx/core/google/shortcuts/builders/ParameterBuilder.java
@@ -0,0 +1,45 @@
+/*
+ * 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.core.google.shortcuts.builders;
+
+import static androidx.annotation.RestrictTo.Scope.LIBRARY;
+import static androidx.core.google.shortcuts.builders.Constants.PARAMETER_TYPE;
+import static androidx.core.google.shortcuts.builders.Constants.PARAMETER_VALUE_KEY;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.RestrictTo;
+
+import com.google.firebase.appindexing.builders.IndexableBuilder;
+
+/**
+ * Builder for the Parameter section in the Shortcut Corpus.
+ *
+ * @hide
+ */
+@RestrictTo(LIBRARY)
+public class ParameterBuilder
+        extends IndexableBuilder<ParameterBuilder> {
+    public ParameterBuilder() {
+        super(PARAMETER_TYPE);
+    }
+
+    /** Sets one or more values for given parameter. */
+    @NonNull
+    public ParameterBuilder setValue(@NonNull String... value) {
+        return put(PARAMETER_VALUE_KEY, value);
+    }
+}
diff --git a/core/core-google-shortcuts/src/main/java/androidx/core/google/shortcuts/builders/ShortcutBuilder.java b/core/core-google-shortcuts/src/main/java/androidx/core/google/shortcuts/builders/ShortcutBuilder.java
new file mode 100644
index 0000000..37758b4
--- /dev/null
+++ b/core/core-google-shortcuts/src/main/java/androidx/core/google/shortcuts/builders/ShortcutBuilder.java
@@ -0,0 +1,67 @@
+/*
+ * 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.core.google.shortcuts.builders;
+
+import static androidx.annotation.RestrictTo.Scope.LIBRARY;
+import static androidx.core.google.shortcuts.builders.Constants.SHORTCUT_CAPABILITY_KEY;
+import static androidx.core.google.shortcuts.builders.Constants.SHORTCUT_DESCRIPTION_KEY;
+import static androidx.core.google.shortcuts.builders.Constants.SHORTCUT_LABEL_KEY;
+import static androidx.core.google.shortcuts.builders.Constants.SHORTCUT_TYPE;
+import static androidx.core.google.shortcuts.builders.Constants.SHORTCUT_URL_KEY;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.RestrictTo;
+
+import com.google.firebase.appindexing.builders.IndexableBuilder;
+
+/**
+ * Builder for the Shortcut Corpus.
+ *
+ * @hide
+ */
+@RestrictTo(LIBRARY)
+public class ShortcutBuilder extends IndexableBuilder<ShortcutBuilder> {
+    public ShortcutBuilder() {
+        super(SHORTCUT_TYPE);
+    }
+
+    /** Sets the label for the shortcut. */
+    @NonNull
+    public ShortcutBuilder setShortcutLabel(@NonNull String shortcutLabel) {
+        setName(shortcutLabel);
+        return put(SHORTCUT_LABEL_KEY, shortcutLabel);
+    }
+
+    /** Sets the description for the shortcut. */
+    @NonNull
+    public ShortcutBuilder setShortcutDescription(@NonNull String shortcutDescription) {
+        setDescription(shortcutDescription);
+        return put(SHORTCUT_DESCRIPTION_KEY, shortcutDescription);
+    }
+
+    /** Sets the {@link android.content.Intent} url for the shortcut. */
+    @NonNull
+    public ShortcutBuilder setShortcutUrl(@NonNull String shortcutUrl) {
+        return put(SHORTCUT_URL_KEY, shortcutUrl);
+    }
+
+    /** Sets one or more capabilities for the shortcut. */
+    @NonNull
+    public ShortcutBuilder setCapability(@NonNull CapabilityBuilder... capability) {
+        return put(SHORTCUT_CAPABILITY_KEY, capability);
+    }
+}
diff --git a/core/core-role/lint-baseline.xml b/core/core-role/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/core/core-role/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/core/core/src/androidTest/java/androidx/core/view/AccessibilityDelegateCompatTest.java b/core/core/src/androidTest/java/androidx/core/view/AccessibilityDelegateCompatTest.java
index b6767d5..9ecc7281 100644
--- a/core/core/src/androidTest/java/androidx/core/view/AccessibilityDelegateCompatTest.java
+++ b/core/core/src/androidTest/java/androidx/core/view/AccessibilityDelegateCompatTest.java
@@ -169,7 +169,7 @@
 
     @Test
     @SdkSuppress(minSdkVersion = 19, maxSdkVersion = 27)
-    @FlakyTest
+    @FlakyTest(bugId = 187190911)
     public void testAccessibilityPaneTitle_isntTrackedAsPaneWithoutTitle() {
         // This test isn't to test the propagation up, just that the event is sent correctly
         ViewCompat.setAccessibilityLiveRegion(mView,
diff --git a/core/core/src/main/java/androidx/core/content/pm/ShortcutManagerCompat.java b/core/core/src/main/java/androidx/core/content/pm/ShortcutManagerCompat.java
index 899e90e..a6db56b 100644
--- a/core/core/src/main/java/androidx/core/content/pm/ShortcutManagerCompat.java
+++ b/core/core/src/main/java/androidx/core/content/pm/ShortcutManagerCompat.java
@@ -831,7 +831,7 @@
     private static List<ShortcutInfoChangeListener> getShortcutInfoListeners(Context context) {
         if (sShortcutInfoChangeListeners == null) {
             List<ShortcutInfoChangeListener> result = new ArrayList<>();
-            if (Build.VERSION.SDK_INT >= 23) {
+            if (Build.VERSION.SDK_INT >= 21) {
                 PackageManager packageManager = context.getPackageManager();
                 Intent activityIntent = new Intent(SHORTCUT_LISTENER_INTENT_FILTER_ACTION);
                 activityIntent.setPackage(context.getPackageName());
diff --git a/datastore/datastore-core/lint-baseline.xml b/datastore/datastore-core/lint-baseline.xml
deleted file mode 100644
index 8794ae8..0000000
--- a/datastore/datastore-core/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" version="4.2.0-beta06">
-
-</issues>
diff --git a/datastore/datastore-preferences-core/datastore-preferences-proto/lint-baseline.xml b/datastore/datastore-preferences-core/datastore-preferences-proto/lint-baseline.xml
deleted file mode 100644
index 8794ae8..0000000
--- a/datastore/datastore-preferences-core/datastore-preferences-proto/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" version="4.2.0-beta06">
-
-</issues>
diff --git a/datastore/datastore-preferences-core/lint-baseline.xml b/datastore/datastore-preferences-core/lint-baseline.xml
deleted file mode 100644
index 8794ae8..0000000
--- a/datastore/datastore-preferences-core/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" version="4.2.0-beta06">
-
-</issues>
diff --git a/datastore/datastore-preferences-rxjava2/lint-baseline.xml b/datastore/datastore-preferences-rxjava2/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/datastore/datastore-preferences-rxjava2/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/datastore/datastore-preferences-rxjava3/lint-baseline.xml b/datastore/datastore-preferences-rxjava3/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/datastore/datastore-preferences-rxjava3/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/datastore/datastore-preferences/lint-baseline.xml b/datastore/datastore-preferences/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/datastore/datastore-preferences/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/datastore/datastore-proto/lint-baseline.xml b/datastore/datastore-proto/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/datastore/datastore-proto/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/datastore/datastore-rxjava2/lint-baseline.xml b/datastore/datastore-rxjava2/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/datastore/datastore-rxjava2/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/datastore/datastore-rxjava3/lint-baseline.xml b/datastore/datastore-rxjava3/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/datastore/datastore-rxjava3/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/development/build_log_simplifier/messages.ignore b/development/build_log_simplifier/messages.ignore
index 5d9bd20..3fe9a9d 100644
--- a/development/build_log_simplifier/messages.ignore
+++ b/development/build_log_simplifier/messages.ignore
@@ -633,6 +633,8 @@
 Transforming pages\: *[0-9]+
 Rendering\: *[0-9]+
 WARNING\: unable to find what is referred to by
+@param maxSlotsToRetainForReuse
+in DClass SubcomposeLayoutState
 @param initVal
 in DClass AnimationVector[0-9]+D
 @param a,
@@ -966,4 +968,4 @@
 Execution optimizations have been disabled for task ':benchmark:benchmark\-macro:.*' to ensure correctness due to the following reasons:
 \- Gradle detected a problem with the following location: '\$OUT_DIR/androidx/benchmark/benchmark\-macro/build/generated/source/wire'\. Reason: Task ':benchmark:benchmark\-macro:.*' uses this output of task ':benchmark:benchmark\-macro:.*' without declaring an explicit or implicit dependency\. This can lead to incorrect results being produced, depending on what order the tasks are executed\. Please refer to https://docs\.gradle\.org/[0-9]+\.[0-9]+/userguide/validation_problems\.html\#implicit_dependency for more details about this problem\.
 # > Task :support-emoji-demos:lintDebug
-Error processing .* broken class file\? \(This feature requires ASM[0-9]+\)
+Error processing .* broken class file\? \(This feature requires ASM[0-9]+\)
\ No newline at end of file
diff --git a/development/checkInvalidSuppress.py b/development/checkInvalidSuppress.py
deleted file mode 100755
index b4484fa7..0000000
--- a/development/checkInvalidSuppress.py
+++ /dev/null
@@ -1,75 +0,0 @@
-#!/usr/bin/env python3
-
-#
-# Copyright 2019, 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.
-#
-
-"""Script used to find IDEA warning suppression that doesn't work for command line builds."""
-
-import os
-import sys
-
-MAIN_DIRECTORY = os.path.normpath(os.path.join(os.path.dirname(__file__), ".."))
-
-# first level line filter - we ignore lines that don't contain the following for speed
-ROOT_MATCH = "noinspection"
-
-# Map of invalid pattern to correct replacement
-# These could be regex for fancy whitespace matching, but don't seem to need in practice
-MATCHERS = {
-    '//noinspection deprecation' : '@SuppressWarnings("deprecation")',
-    '//noinspection unchecked' : '@SuppressWarnings("unchecked")',
-}
-
-## Check a line for any invalid suppressions, and return it if found
-def getReportForLine(filename, i, line, lines):
-  for bad, good in MATCHERS.items():
-    if bad in line:
-      context = "".join(lines[i:i+3])
-      return "\n{}:{}:\nError: unsupported comment suppression\n{}Instead, use: {}\n".format(
-          filename, i, context, good)
-  return ""
-
-def main():
-  report = ""
-
-  for filename in sys.argv[1:]:
-    # suppress comments ignored in kotlin, but may as well block there too
-    if not filename.endswith(".java") and not filename.endswith(".kt"):
-      continue
-
-    filename = os.path.join(MAIN_DIRECTORY, filename)
-
-    # check file exists
-    if not os.path.isfile(filename):
-      continue
-
-    with open(filename, "r") as f:
-      lines = f.readlines()
-
-      linetuples = map(lambda i: [i, lines[i]], range(len(lines)))
-      for i, line in linetuples:
-        if ROOT_MATCH in line:
-          report += getReportForLine(filename, i, line, lines)
-
-  if not report:
-    sys.exit(0)
-
-  print("Invalid, IDEA-specific warning suppression found. These cause warnings during compilation.")
-  print(report)
-  sys.exit(1)
-
-if __name__ == '__main__':
-  main()
diff --git a/development/check_os_prebuilts.py b/development/check_os_prebuilts.py
index 04977565..9b21fa0 100755
--- a/development/check_os_prebuilts.py
+++ b/development/check_os_prebuilts.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/env python3
 
 #
 # Copyright 2019, The Android Open Source Project
@@ -41,14 +41,14 @@
   if not os_specific_files:
     sys.exit(0)
 
-  print "The following operating system specific files were found in your commit:\n\033[91m"
+  print("The following operating system specific files were found in your commit:\n\033[91m")
   for filename in os_specific_files.values():
-    print filename
-  print """\033[0m\nPlease make sure to import the corresponding prebuilts for missing platforms.
+    print(filename)
+  print ("""\033[0m\nPlease make sure to import the corresponding prebuilts for missing platforms.
 If you imported a prebuilt similar to foo:bar:linux, try foo:bar:osx and vice versa.
 If there is no corresponding prebuilt, or only adding a prebuilt for one platform is intended, run:
 \033[92mrepo upload --no-verify\033[0m
-to skip this warning."""
+to skip this warning.""")
   sys.exit(1)
 
 
diff --git a/development/diagnose-build-failure/diagnose-build-failure.sh b/development/diagnose-build-failure/diagnose-build-failure.sh
index 89430cf..44b0cea 100755
--- a/development/diagnose-build-failure/diagnose-build-failure.sh
+++ b/development/diagnose-build-failure/diagnose-build-failure.sh
@@ -19,7 +19,7 @@
   echo "  Replaces the requirement for "'`'"./gradlew <tasks>"'`'" to fail with the requirement that it produces the given message"
   echo
   echo "SAMPLE USAGE"
-  echo "  $0 assembleDebug # or any other arguments you would normally give to ./gradlew"
+  echo "  $0 assembleRelease # or any other arguments you would normally give to ./gradlew"
   echo
   echo "OUTPUT"
   echo "  diagnose-build-failure will conclude one of the following:"
@@ -122,7 +122,7 @@
 function backupState() {
   cd "$scriptPath"
   backupDir="$1"
-  ./impl/backup-state.sh "$backupDir" "$workingDir" "$@"
+  ./impl/backup-state.sh "$backupDir" "$workingDir" "$2"
 }
 
 function restoreState() {
diff --git a/development/diagnose-build-failure/impl/backup-state.sh b/development/diagnose-build-failure/impl/backup-state.sh
index 05708a2..df7ffa5 100755
--- a/development/diagnose-build-failure/impl/backup-state.sh
+++ b/development/diagnose-build-failure/impl/backup-state.sh
@@ -26,6 +26,12 @@
 move=false
 if [ "$moveArg" == "--move" ]; then
   move=true
+  shift
+fi
+
+if [ "$3" != "" ]; then
+  echo "Unrecognized argument $3"
+  usage
 fi
 
 rm -rf "$stateDir"
diff --git a/docs-public/build.gradle b/docs-public/build.gradle
index 1568593..b09e0a3 100644
--- a/docs-public/build.gradle
+++ b/docs-public/build.gradle
@@ -108,9 +108,9 @@
     docs("androidx.enterprise:enterprise-feedback:1.1.0")
     docs("androidx.enterprise:enterprise-feedback-testing:1.1.0")
     docs("androidx.exifinterface:exifinterface:1.3.2")
-    docs("androidx.fragment:fragment:1.3.3")
-    docs("androidx.fragment:fragment-ktx:1.3.3")
-    docs("androidx.fragment:fragment-testing:1.3.3")
+    docs("androidx.fragment:fragment:1.3.4")
+    docs("androidx.fragment:fragment-ktx:1.3.4")
+    docs("androidx.fragment:fragment-testing:1.3.4")
     docs("androidx.gridlayout:gridlayout:1.0.0")
     docs("androidx.heifwriter:heifwriter:1.1.0-alpha01")
     docs("androidx.hilt:hilt-common:1.0.0-beta01")
diff --git a/docs-tip-of-tree/build.gradle b/docs-tip-of-tree/build.gradle
index 4953d95..21ac59b 100644
--- a/docs-tip-of-tree/build.gradle
+++ b/docs-tip-of-tree/build.gradle
@@ -129,6 +129,7 @@
     docs(project(":fragment:fragment-ktx"))
     docs(project(":fragment:fragment-testing"))
     docs(project(":gridlayout:gridlayout"))
+    docs(project(":health:health-services-client"))
     docs(project(":heifwriter:heifwriter"))
     docs(project(":hilt:hilt-common"))
     docs(project(":hilt:hilt-navigation"))
diff --git a/docs/api_guidelines.md b/docs/api_guidelines.md
index f04be61..e2662a5 100644
--- a/docs/api_guidelines.md
+++ b/docs/api_guidelines.md
@@ -41,9 +41,8 @@
 *   `-core` for a low-level artifact that *may* contain public APIs but is
     primarily intended for use by other libraries in the group
 *   `-ktx` for an Kotlin artifact that exposes idiomatic Kotlin APIs as an
-    extension to a Java-only library
-*   `-java8` for a Java 8 artifact that exposes idiomatic Java 8 APIs as an
-    extension to a Java 7 library
+    extension to a Java-only library (see
+    [additional -ktx guidance](#module-ktx))
 *   `-<third-party>` for an artifact that integrates an optional third-party API
     surface, e.g. `-proto` or `-rxjava2`. Note that a major version is included
     in the sub-feature name for third-party API surfaces where the major version
@@ -177,6 +176,25 @@
 external developers and should be considered a last-resort when backporting
 behavior is not feasible.
 
+### Kotlin extension `-ktx` libraries {#module-ktx}
+
+New libraries should prefer Kotlin sources with built-in Java compatibility via
+`@JvmName` and other affordances of the Kotlin language; however, existing Java
+sourced libraries may benefit from extending their API surface with
+Kotlin-friendly APIs in a `-ktx` library.
+
+A Kotlin extension library **may only** provide extensions for a single base
+library's API surface and its name **must** match the base library exactly. For
+example, `work:work-ktx` may only provide extensions for APIs exposed by
+`work:work`.
+
+Additionally, an extension library **must** specify an `api`-type dependency on
+the base library and **must** be versioned and released identically to the base
+library.
+
+Kotlin extension libraries _should not_ expose new functionality; they should
+only provide Kotlin-friendly versions of existing Java-facing functionality.
+
 ## Platform compatibility API patterns {#platform-compatibility-apis}
 
 NOTE For all library APIs that wrap or provide parity with platform APIs,
@@ -1767,7 +1785,7 @@
 
 For library groups with strongly related samples that want to share code.
 
-Gradle project name: `:foo-library:samples`
+Gradle project name: `:foo-library:foo-library-samples`
 
 ```
 foo-library/
@@ -1780,10 +1798,18 @@
 
 For library groups with complex, relatively independent sub-libraries
 
-Gradle project name: `:foo-library:foo-module:samples`
+Gradle project name: `:foo-library:foo-module:foo-module-samples`
 
 ```
 foo-library/
   foo-module/
     samples/
 ```
+
+**Samples module configuration**
+
+Samples modules are published to GMaven so that they are available to Android
+Studio, which displays code in @Sample annotations as hover-over pop-ups.
+
+To achieve this, samples modules must declare the same MavenGroup and `publish`
+as the library(s) they are samples for.
diff --git a/docs/benchmarking.md b/docs/benchmarking.md
index 5fc0276..a4fabb1 100644
--- a/docs/benchmarking.md
+++ b/docs/benchmarking.md
@@ -133,11 +133,11 @@
 
 #### Device
 
-Get an API 28+ device (Or a rooted API 27 device). The rest of this section is
-about *why* those constraints exist, skip if not interested.
+Get an API 29+ device. The rest of this section is about *why* those constraints
+exist, skip if not interested.
 
 Simpleperf has restrictions about where it can be used - Jetpack Benchmark will
-only support API 28+ for now, due to
+only support API 29+ for now, due to
 [platform/simpleperf constraints](https://android.googlesource.com/platform/system/extras/+/master/simpleperf/doc/android_application_profiling.md#prepare-an-android-application)
 (see last subsection titled "If you want to profile Java code"). Summary is:
 
@@ -148,9 +148,13 @@
 -   26 (O): Requires compiled Java code, and wrapper script. We haven't
     investigated support.
 
--   27 (P): Can profile all Java code, but requires `userdebug`/rooted device
+-   27 (O.1): Can profile all Java code, but requires `userdebug`/rooted device
 
--   \>=28 (Q): Can profile all Java code, requires profileable (or
+-   28 (P): Can profile all Java code, requires debuggable (or
+    `userdebug`/rooted device, but this appears to not be supported by scripts
+    currently)
+
+-   \>=29 (Q): Can profile all Java code, requires profileable or debuggable (or
     `userdebug`/rooted device)
 
 We aren't planning to support profiling debuggable APK builds, since they're
diff --git a/docs/branching.md b/docs/branching.md
index 074a54c..dd23ec5 100644
--- a/docs/branching.md
+++ b/docs/branching.md
@@ -1,15 +1,20 @@
-# AndroidX Branch Workflow
+# Branch workflow
 
 [TOC]
 
-## Single Development Branch [androidx-main]
+## Single Development Branch [`aosp/androidx-main`]
 
-All feature development occurs in the public AndroidX master dev branch of the
+All feature development occurs in the public main development branch of the
 Android Open Source Project: `androidx-main`. This branch serves as the central
-location and source of truth for all AndroidX library source code. All alpha and
-beta version development, builds, and releases will be done ONLY in this branch.
+location and source of truth for all AndroidX library source code. All `alpha`
+and `beta` version work -- development, builds, and releases -- will be done
+ONLY in this branch.
 
-## Release Branches [androidx-\<feature\>-release]
+## Release Branches [`aosp/androidx-\<feature\>-release`]
+
+Release branches are used for stabilitization of a library and support of a
+previous stable release. With one development branch, this is how AndroidX
+provides support for the previous `rc` or stable version.
 
 When a library updates to rc (release-candidate) or stable, that library version
 will be snapped over to that library’s release branch. If that release branch
@@ -19,21 +24,13 @@
 
 Release branches have the following properties:
 
-*   A release branch will contain rc or stable versions of libraries.
-*   Release branches are internal branches.
-*   Release branches can **ONLY** be changed through
-    cherry-picks
-*   Bug-fixes and updates to that rc or stable version will need to be
-    individually cherry-picked
-*   No alpha or beta versions will exist in a release branch.
-*   Toolchain and other library wide changes to androidx-main will be synced to
-    each release branch.
+*   A release branch will contain `rc` or `stable` versions of libraries
+*   Release branches can **ONLY** be changed through cherry-picks
+*   Bug fixes and updates to `rc` or stable versions must be cherry-picked
+*   No `alpha` or `beta` versions will exist in a release branch.
+*   Toolchain and other library wide changes to `androidx-main` will be synced
+    to each release branch.
 *   Release branches will have the naming format
     `androidx-<feature-name>-release`
 *   Release branches will be re-snapped from `androidx-main` for each new minor
-    version release (for example, releasing 2.2.0-rc01 after 2.1.0)
-
-## Platform Developement and AndroidX [androidx-platform-dev]
-
-Platform specific development is done using our INTERNAL platform development
-branch `androidx-platform-dev`.
+    version release (for example, releasing `2.2.0-rc01` after `2.1.0`)
diff --git a/docs/kdoc_guidelines.md b/docs/kdoc_guidelines.md
index ef33b04..72f91eb 100644
--- a/docs/kdoc_guidelines.md
+++ b/docs/kdoc_guidelines.md
@@ -179,6 +179,27 @@
  */
 ```
 
+### Don't use angle brackets for `@param`
+
+Instead of:
+
+```kotlin {.bad}
+/**
+ * @param <T> my cool param
+ */
+```
+
+Do this:
+
+```kotlin {.good}
+/**
+ * * @param T my cool param
+ */
+```
+
+This syntax is correct is Javadoc, but angle brackets aren't used in KDoc
+([@param reference guide](https://kotlinlang.org/docs/kotlin-doc.html#param-name)).
+
 ## Javadoc - KDoc differences
 
 Some tags are shared between Javadoc and KDoc, such as `@param`, but there are
diff --git a/docs/onboarding.md b/docs/onboarding.md
index 5715972..f8dcafa 100644
--- a/docs/onboarding.md
+++ b/docs/onboarding.md
@@ -13,9 +13,10 @@
 
 ## Workstation setup {#setup}
 
-You will need to install the `repo` tool, which is used for Git branch and
-commit management. If you want to learn more about `repo`, see the
-[Repo Command Reference](https://source.android.com/setup/develop/repo).
+You will need to install the
+[`repo`](https://source.android.com/setup/develop#repo) tool, which is used for
+Git branch and commit management. If you want to learn more about `repo`, see
+the [Repo Command Reference](https://source.android.com/setup/develop/repo).
 
 ### Linux and MacOS {#setup-linux-mac}
 
@@ -210,7 +211,10 @@
 ```
 
 The `--cbr` switch automatically picks the current repo branch for upload. The
-`-t` switch sets the Gerrit topic to the branch name, e.g. `my-branch-name`.
+`-t` switch sets the Gerrit topic to the branch name, e.g. `my-branch-name`. You
+can refer to the
+[Android documentation](https://source.android.com/setup/create/coding-tasks#workflow)
+for a high level overview of this basic workflow.
 
 NOTE If you encounter issues with `repo upload`, consider running upload with
 trace enabled, e.g. `GIT_DAPPER_TRACE=1 repo --trace upload . --cbr -y`. These
diff --git a/docs/policies.md b/docs/policies.md
index 35b7aaa..ffe656f 100644
--- a/docs/policies.md
+++ b/docs/policies.md
@@ -15,7 +15,7 @@
 Libraries developed in AndroidX follow a consistent project naming and directory
 structure.
 
-Library groups should organize their modules into directories and module names
+Library groups should organize their projects into directories and project names
 (in brackets) as:
 
 ```
@@ -39,6 +39,27 @@
     testapp/ [navigation:integration-tests:testapp]
 ```
 
+### Project creator script {#project-creator}
+
+Note: The terms _project_, _module_, and _library_ are often used
+interchangeably within AndroidX, with project being the technical term used by
+Gradle to describe a build target, e.g. a library that maps to a single AAR.
+
+New projects can be created using our
+[project creation script](https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:development/project-creator/?q=project-creator&ss=androidx%2Fplatform%2Fframeworks%2Fsupport)
+available in our repo.
+
+It will create a new project with the proper structure and configuration based
+on your project needs!
+
+To use it:
+
+```sh
+cd ~/androidx-main/framework/support && \
+cd development/project-creator && \
+./create_project.py androidx.foo foo-bar
+```
+
 ## Terminology {#terminology}
 
 **Artifact**
@@ -91,24 +112,6 @@
 }
 ```
 
-#### Finalizing for release {#finalizing-for-release}
-
-When the artifact is ready for release, its versioned API file should be
-finalized to ensure that the subsequent versioned release conforms to the
-versioning policies.
-
-```
-./gradlew <module>:finalizeApi
-```
-
-This will prevent any subsequent changes to the API surface until the artifact
-version is updated. To update the artifact version and allow changes within the
-semantic versioning contract, simply update the version string in the artifact's
-`build.gradle` (see [Workflow](#workflow) introduction).
-
-To avoid breaking the development workflow, we recommend that API finalization
-and version string updates be submitted as a single CL.
-
 ## Dependencies {#dependencies}
 
 Artifacts may depend on other artifacts within AndroidX as well as sanctioned
@@ -130,6 +133,9 @@
 only have implementation-type dependencies, your artifact may carry either the
 `alpha` or `beta` suffix.
 
+Note: This does not apply to test dependencies: suffixes of test dependencies do
+_not_ carry over to your artifact.
+
 #### Pinned versions {#pinned-versions}
 
 To avoid issues with dependency versioning, consider pinning your artifact's
diff --git a/docs/versioning.md b/docs/versioning.md
index 27efde7..3c1424a 100644
--- a/docs/versioning.md
+++ b/docs/versioning.md
@@ -239,13 +239,16 @@
 granted for new features, non-trivial API changes, significant refactorings, or
 any changes likely to introduce additional functional instability. Requests for
 exceptions **must** be accompanied by a justification explaining why the change
-cannot be made in a future minor version.
+cannot be made in a future minor version. This policy does not apply to
+additions of `@Experimental` APIs or changes to `@Experimental` APIs.
 
 #### Checklist for moving to `beta01` {#beta-checklist}
 
 *   API surface
     *   Entire API surface has been reviewed by API Council
     *   All APIs from alpha undergoing deprecate/remove cycle must be removed
+        *   The final removal of a `@Deprecated` API must occur in alpha, not in
+            Beta.
 *   Testing
     *   All public APIs are tested
     *   All pre-submit and post-submit tests are enabled (e.g. all suppressions
@@ -264,10 +267,13 @@
 *   API surface
     *   May not add, remove, or change APIs unless granted an exception by API
         Council following the beta API change exception request process
-    *   Must go through the full `@Deprecate` and hard-removal cycle in separate
-        `beta` releases for any exception-approved API removals or changes
-    *   May not remove `@Experimental` from experimental APIs, see previous item
-        regarding new APIs
+        *   Must go through the full `@Deprecate` and hard-removal cycle in
+            separate `beta` releases for any exception-approved API removals or
+            changes
+    *   May not remove `@Experimental` from experimental APIs, as this would
+        amount to an API addition
+    *   **May** add new `@Experimental` APIs and change existing `@Experimental`
+        APIs
 
 ### RC {#rc}
 
diff --git a/dynamic-animation/dynamic-animation-ktx/lint-baseline.xml b/dynamic-animation/dynamic-animation-ktx/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/dynamic-animation/dynamic-animation-ktx/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/emoji/bundled/lint-baseline.xml b/emoji/bundled/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/emoji/bundled/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/emoji2/emoji2-benchmark/lint-baseline.xml b/emoji2/emoji2-benchmark/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/emoji2/emoji2-benchmark/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/emoji2/emoji2/api/current.txt b/emoji2/emoji2/api/current.txt
index f69bb70..f723bb9 100644
--- a/emoji2/emoji2/api/current.txt
+++ b/emoji2/emoji2/api/current.txt
@@ -2,7 +2,7 @@
 package androidx.emoji2.text {
 
   public final class DefaultEmojiCompatConfig {
-    method public static androidx.emoji2.text.EmojiCompat.Config? create(android.content.Context);
+    method public static androidx.emoji2.text.FontRequestEmojiCompatConfig? create(android.content.Context);
   }
 
   @AnyThread public class EmojiCompat {
diff --git a/emoji2/emoji2/api/public_plus_experimental_current.txt b/emoji2/emoji2/api/public_plus_experimental_current.txt
index f69bb70..f723bb9 100644
--- a/emoji2/emoji2/api/public_plus_experimental_current.txt
+++ b/emoji2/emoji2/api/public_plus_experimental_current.txt
@@ -2,7 +2,7 @@
 package androidx.emoji2.text {
 
   public final class DefaultEmojiCompatConfig {
-    method public static androidx.emoji2.text.EmojiCompat.Config? create(android.content.Context);
+    method public static androidx.emoji2.text.FontRequestEmojiCompatConfig? create(android.content.Context);
   }
 
   @AnyThread public class EmojiCompat {
diff --git a/emoji2/emoji2/api/restricted_current.txt b/emoji2/emoji2/api/restricted_current.txt
index f69bb70..f723bb9 100644
--- a/emoji2/emoji2/api/restricted_current.txt
+++ b/emoji2/emoji2/api/restricted_current.txt
@@ -2,7 +2,7 @@
 package androidx.emoji2.text {
 
   public final class DefaultEmojiCompatConfig {
-    method public static androidx.emoji2.text.EmojiCompat.Config? create(android.content.Context);
+    method public static androidx.emoji2.text.FontRequestEmojiCompatConfig? create(android.content.Context);
   }
 
   @AnyThread public class EmojiCompat {
diff --git a/emoji2/emoji2/src/main/java/androidx/emoji2/text/DefaultEmojiCompatConfig.java b/emoji2/emoji2/src/main/java/androidx/emoji2/text/DefaultEmojiCompatConfig.java
index a03db5a..1c9db25 100644
--- a/emoji2/emoji2/src/main/java/androidx/emoji2/text/DefaultEmojiCompatConfig.java
+++ b/emoji2/emoji2/src/main/java/androidx/emoji2/text/DefaultEmojiCompatConfig.java
@@ -93,8 +93,9 @@
      * could be found.
      */
     @Nullable
-    public static EmojiCompat.Config create(@NonNull Context context) {
-        return new DefaultEmojiCompatConfigFactory(null).create(context);
+    public static FontRequestEmojiCompatConfig create(@NonNull Context context) {
+        return (FontRequestEmojiCompatConfig) new DefaultEmojiCompatConfigFactory(null)
+                .create(context);
     }
 
     /**
diff --git a/emoji2/emoji2/src/main/java/androidx/emoji2/text/EmojiCompatInitializer.java b/emoji2/emoji2/src/main/java/androidx/emoji2/text/EmojiCompatInitializer.java
index 462d1d4..69fd6904 100644
--- a/emoji2/emoji2/src/main/java/androidx/emoji2/text/EmojiCompatInitializer.java
+++ b/emoji2/emoji2/src/main/java/androidx/emoji2/text/EmojiCompatInitializer.java
@@ -17,8 +17,17 @@
 package androidx.emoji2.text;
 
 import android.content.Context;
+import android.os.Build;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.os.Process;
 
 import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.RequiresApi;
+import androidx.annotation.WorkerThread;
+import androidx.core.os.TraceCompat;
 import androidx.startup.Initializer;
 
 import java.util.Collections;
@@ -27,16 +36,21 @@
 /**
  * Initializer for configuring EmojiCompat with the system installed downloadable font provider.
  *
+ * <p>This initializer will initialize EmojiCompat immediately then defer loading the font for a
+ * short delay to avoid delaying application startup. Typically, the font will be loaded shortly
+ * after the first screen of your application loads, which means users may see system emoji
+ * briefly prior to the compat font loading.</p>
+ *
  * <p>This is the recommended configuration for all apps that don't need specialized configuration,
- * and don't need to control the thread that initialization runs on. For more information see
- * {@link androidx.emoji2.text.DefaultEmojiCompatConfig}.</p>
+ * and don't need to control the background thread that initialization runs on. For more information
+ * see {@link androidx.emoji2.text.DefaultEmojiCompatConfig}.</p>
  *
  * <p>In addition to the reasons listed in {@code DefaultEmojiCompatConfig} you may wish to disable
  * this automatic configuration if you intend to call initialization from an existing background
  * thread pool in your application.</p>
  *
- * <p></p>This is enabled by default by including the `:emoji2:emoji2` gradle artifact. To disable
- * the default configuration (and allow manual configuration) add this to your manifest:</p>
+ * <p>This is enabled by default by including the {@code :emoji2:emoji2} gradle artifact. To
+ * disable the default configuration (and allow manual configuration) add this to your manifest:</p>
  *
  * <pre>
  *     <provider
@@ -52,6 +66,8 @@
  * @see androidx.emoji2.text.DefaultEmojiCompatConfig
  */
 public class EmojiCompatInitializer implements Initializer<Boolean> {
+    private static final long STARTUP_THREAD_CREATION_DELAY_MS = 500L;
+    private static final String S_INITIALIZER_THREAD_NAME = "EmojiCompatInitializer";
 
     /**
      * Initialize EmojiCompat with the app's context.
@@ -62,8 +78,19 @@
     @NonNull
     @Override
     public Boolean create(@NonNull Context context) {
-        // note: super create requires this be non-null, share if the configuration was successful
-        return EmojiCompat.init(context) != null;
+        if (Build.VERSION.SDK_INT >= 19) {
+            final Handler mainHandler;
+            if (Build.VERSION.SDK_INT >= 28) {
+                mainHandler = Handler28Impl.createAsync(Looper.getMainLooper());
+            } else {
+                mainHandler = new Handler(Looper.getMainLooper());
+            }
+            EmojiCompat.init(new BackgroundDefaultConfig(context));
+            mainHandler.postDelayed(new LoadEmojiCompatRunnable(),
+                    STARTUP_THREAD_CREATION_DELAY_MS);
+            return true;
+        }
+        return false;
     }
 
     /**
@@ -74,4 +101,109 @@
     public List<Class<? extends Initializer<?>>> dependencies() {
         return Collections.emptyList();
     }
+
+    static class LoadEmojiCompatRunnable implements Runnable {
+        @Override
+        public void run() {
+            try {
+                // this is main thread, so mark what we're doing (this trace includes thread
+                // start time in BackgroundLoadingLoader.load
+                TraceCompat.beginSection("EmojiCompat.EmojiCompatInitializer.run");
+                if (EmojiCompat.isConfigured()) {
+                    EmojiCompat.get().load();
+                }
+            } finally {
+                TraceCompat.endSection();
+            }
+        }
+    }
+
+    @RequiresApi(19)
+    static class BackgroundDefaultConfig extends EmojiCompat.Config {
+        protected BackgroundDefaultConfig(Context context) {
+            super(new BackgroundDefaultLoader(context));
+            setMetadataLoadStrategy(EmojiCompat.LOAD_STRATEGY_MANUAL);
+        }
+    }
+
+    @RequiresApi(19)
+    static class BackgroundDefaultLoader implements EmojiCompat.MetadataRepoLoader {
+        private final Context mContext;
+
+        BackgroundDefaultLoader(Context context) {
+            mContext = context.getApplicationContext();
+        }
+
+        @Nullable
+        private HandlerThread mThread;
+
+        @Override
+        public void load(@NonNull EmojiCompat.MetadataRepoLoaderCallback loaderCallback) {
+            Handler handler = getThreadHandler();
+            handler.post(() -> doLoad(loaderCallback, handler));
+        }
+
+        @WorkerThread
+        void doLoad(@NonNull EmojiCompat.MetadataRepoLoaderCallback loaderCallback,
+                @NonNull Handler handler) {
+            try {
+                FontRequestEmojiCompatConfig config = DefaultEmojiCompatConfig.create(mContext);
+                if (config == null) {
+                    throw new RuntimeException("EmojiCompat font provider not available on this "
+                            + "device.");
+                }
+                config.setHandler(handler);
+                config.getMetadataRepoLoader().load(new EmojiCompat.MetadataRepoLoaderCallback() {
+                    @Override
+                    public void onLoaded(@NonNull MetadataRepo metadataRepo) {
+                        try {
+                            // main thread is notified before returning, so we can quit now
+                            loaderCallback.onLoaded(metadataRepo);
+                        } finally {
+                            quitHandlerThread();
+                        }
+                    }
+
+                    @Override
+                    public void onFailed(@Nullable Throwable throwable) {
+                        try {
+                            // main thread is notified before returning, so we can quit now
+                            loaderCallback.onFailed(throwable);
+                        } finally {
+                            quitHandlerThread();
+                        }
+                    }
+                });
+            } catch (Throwable t) {
+                loaderCallback.onFailed(t);
+                quitHandlerThread();
+            }
+        }
+
+        void quitHandlerThread() {
+            if (mThread != null) {
+                mThread.quitSafely();
+            }
+        }
+
+        @NonNull
+        private Handler getThreadHandler() {
+            mThread = new HandlerThread(S_INITIALIZER_THREAD_NAME,
+                    Process.THREAD_PRIORITY_BACKGROUND);
+            mThread.start();
+            return new Handler(mThread.getLooper());
+        }
+    }
+
+    @RequiresApi(28)
+    private static class Handler28Impl {
+        private Handler28Impl() {
+            // Non-instantiable.
+        }
+
+        // avoid aligning with vsync when available (API 28+)
+        public static Handler createAsync(Looper looper) {
+            return Handler.createAsync(looper);
+        }
+    }
 }
diff --git a/emoji2/emoji2/src/main/java/androidx/emoji2/text/FontRequestEmojiCompatConfig.java b/emoji2/emoji2/src/main/java/androidx/emoji2/text/FontRequestEmojiCompatConfig.java
index 1e3006e..c261675 100644
--- a/emoji2/emoji2/src/main/java/androidx/emoji2/text/FontRequestEmojiCompatConfig.java
+++ b/emoji2/emoji2/src/main/java/androidx/emoji2/text/FontRequestEmojiCompatConfig.java
@@ -32,6 +32,7 @@
 import androidx.annotation.RequiresApi;
 import androidx.annotation.RestrictTo;
 import androidx.core.graphics.TypefaceCompatUtil;
+import androidx.core.os.TraceCompat;
 import androidx.core.provider.FontRequest;
 import androidx.core.provider.FontsContractCompat;
 import androidx.core.provider.FontsContractCompat.FontFamilyResult;
@@ -165,6 +166,10 @@
      * given FontRequest.
      */
     private static class FontRequestMetadataLoader implements EmojiCompat.MetadataRepoLoader {
+        private static final String S_TRACE_BUILD_TYPEFACE =
+                "EmojiCompat.FontRequestEmojiCompatConfig.buildTypeface";
+        private static final String S_TRACE_THREAD_CREATION =
+                "EmojiCompat.FontRequestEmojiCompatConfig.threadCreation";
         private final @NonNull Context mContext;
         private final @NonNull FontRequest mRequest;
         private final @NonNull FontProviderHelper mFontProviderHelper;
@@ -209,11 +214,17 @@
         public void load(@NonNull final EmojiCompat.MetadataRepoLoaderCallback loaderCallback) {
             Preconditions.checkNotNull(loaderCallback, "LoaderCallback cannot be null");
             synchronized (mLock) {
-                if (mHandler == null) {
-                    // Developer didn't give a thread for fetching. Create our own one.
-                    mThread = new HandlerThread("emojiCompat", Process.THREAD_PRIORITY_BACKGROUND);
-                    mThread.start();
-                    mHandler = new Handler(mThread.getLooper());
+                try {
+                    TraceCompat.beginSection(S_TRACE_THREAD_CREATION);
+                    if (mHandler == null) {
+                        // Developer didn't give a thread for fetching. Create our own one.
+                        mThread = new HandlerThread("emojiCompat",
+                                Process.THREAD_PRIORITY_BACKGROUND);
+                        mThread.start();
+                        mHandler = new Handler(mThread.getLooper());
+                    }
+                } finally {
+                    TraceCompat.endSection();
                 }
                 mHandler.post(new Runnable() {
                     @Override
@@ -312,13 +323,21 @@
                     throw new RuntimeException("fetchFonts result is not OK. (" + resultCode + ")");
                 }
 
-                // TODO: Good to add new API to create Typeface from FD not to open FD twice.
-                final Typeface typeface = mFontProviderHelper.buildTypeface(mContext, font);
-                final ByteBuffer buffer = TypefaceCompatUtil.mmap(mContext, null, font.getUri());
-                if (buffer == null) {
-                    throw new RuntimeException("Unable to open file.");
+                final MetadataRepo metadataRepo;
+                try {
+                    TraceCompat.beginSection(S_TRACE_BUILD_TYPEFACE);
+                    // TODO: Good to add new API to create Typeface from FD not to open FD twice.
+                    final Typeface typeface = mFontProviderHelper.buildTypeface(mContext, font);
+                    final ByteBuffer buffer = TypefaceCompatUtil.mmap(mContext, null,
+                            font.getUri());
+                    if (buffer == null) {
+                        throw new RuntimeException("Unable to open file.");
+                    }
+                    metadataRepo = MetadataRepo.create(typeface, buffer);
+                } finally {
+                    TraceCompat.endSection();
                 }
-                mCallback.onLoaded(MetadataRepo.create(typeface, buffer));
+                mCallback.onLoaded(metadataRepo);
                 cleanUp();
             } catch (Throwable t) {
                 mCallback.onFailed(t);
diff --git a/emoji2/emoji2/src/main/java/androidx/emoji2/text/MetadataRepo.java b/emoji2/emoji2/src/main/java/androidx/emoji2/text/MetadataRepo.java
index bc31e26..a168667 100644
--- a/emoji2/emoji2/src/main/java/androidx/emoji2/text/MetadataRepo.java
+++ b/emoji2/emoji2/src/main/java/androidx/emoji2/text/MetadataRepo.java
@@ -24,6 +24,7 @@
 import androidx.annotation.RequiresApi;
 import androidx.annotation.RestrictTo;
 import androidx.annotation.VisibleForTesting;
+import androidx.core.os.TraceCompat;
 import androidx.core.util.Preconditions;
 import androidx.emoji2.text.flatbuffer.MetadataList;
 
@@ -41,6 +42,7 @@
      * The default children size of the root node.
      */
     private static final int DEFAULT_ROOT_SIZE = 1024;
+    private static final String S_TRACE_CREATE_REPO = "EmojiCompat.MetadataRepo.create";
 
     /**
      * MetadataList that contains the emoji metadata.
@@ -87,7 +89,12 @@
     @NonNull
     @RestrictTo(RestrictTo.Scope.TESTS)
     public static MetadataRepo create(@NonNull final Typeface typeface) {
-        return new MetadataRepo(typeface, new MetadataList());
+        try {
+            TraceCompat.beginSection(S_TRACE_CREATE_REPO);
+            return new MetadataRepo(typeface, new MetadataList());
+        } finally {
+            TraceCompat.endSection();
+        }
     }
 
     /**
@@ -100,7 +107,12 @@
     @NonNull
     public static MetadataRepo create(@NonNull final Typeface typeface,
             @NonNull final InputStream inputStream) throws IOException {
-        return new MetadataRepo(typeface, MetadataListReader.read(inputStream));
+        try {
+            TraceCompat.beginSection(S_TRACE_CREATE_REPO);
+            return new MetadataRepo(typeface, MetadataListReader.read(inputStream));
+        } finally {
+            TraceCompat.endSection();
+        }
     }
 
     /**
@@ -113,7 +125,12 @@
     @NonNull
     public static MetadataRepo create(@NonNull final Typeface typeface,
             @NonNull final ByteBuffer byteBuffer) throws IOException {
-        return new MetadataRepo(typeface, MetadataListReader.read(byteBuffer));
+        try {
+            TraceCompat.beginSection(S_TRACE_CREATE_REPO);
+            return new MetadataRepo(typeface, MetadataListReader.read(byteBuffer));
+        } finally {
+            TraceCompat.endSection();
+        }
     }
 
     /**
@@ -126,8 +143,14 @@
     @NonNull
     public static MetadataRepo create(@NonNull final AssetManager assetManager,
             @NonNull final String assetPath) throws IOException {
-        final Typeface typeface = Typeface.createFromAsset(assetManager, assetPath);
-        return new MetadataRepo(typeface, MetadataListReader.read(assetManager, assetPath));
+        try {
+            TraceCompat.beginSection(S_TRACE_CREATE_REPO);
+            final Typeface typeface = Typeface.createFromAsset(assetManager, assetPath);
+            return new MetadataRepo(typeface,
+                    MetadataListReader.read(assetManager, assetPath));
+        } finally {
+            TraceCompat.endSection();
+        }
     }
 
     /**
diff --git a/emoji2/integration-tests/init-disabled-macrobenchmark-target/README.md b/emoji2/integration-tests/init-disabled-macrobenchmark-target/README.md
new file mode 100644
index 0000000..95567d1
--- /dev/null
+++ b/emoji2/integration-tests/init-disabled-macrobenchmark-target/README.md
@@ -0,0 +1,2 @@
+A simple app with emoji2 startup initializer removed.
+
diff --git a/emoji2/integration-tests/init-disabled-macrobenchmark-target/build.gradle b/emoji2/integration-tests/init-disabled-macrobenchmark-target/build.gradle
new file mode 100644
index 0000000..5f1098a
--- /dev/null
+++ b/emoji2/integration-tests/init-disabled-macrobenchmark-target/build.gradle
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2020 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.
+ */
+
+import static androidx.build.dependencies.DependenciesKt.*
+
+plugins {
+    id("AndroidXPlugin")
+    id("com.android.application")
+    id("kotlin-android")
+}
+
+android {
+    buildTypes {
+        release {
+            minifyEnabled true
+            shrinkResources true
+            proguardFiles getDefaultProguardFile("proguard-android-optimize.txt")
+        }
+    }
+}
+
+dependencies {
+    implementation(KOTLIN_STDLIB)
+    implementation(CONSTRAINT_LAYOUT, { transitive = true })
+    implementation(project(":arch:core:core-runtime"))
+    implementation(project(":appcompat:appcompat"))
+    implementation(project(":startup:startup-runtime"))
+    implementation(MATERIAL)
+}
diff --git a/emoji2/integration-tests/init-disabled-macrobenchmark-target/src/main/AndroidManifest.xml b/emoji2/integration-tests/init-disabled-macrobenchmark-target/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..cffda4d
--- /dev/null
+++ b/emoji2/integration-tests/init-disabled-macrobenchmark-target/src/main/AndroidManifest.xml
@@ -0,0 +1,57 @@
+<!--
+  ~ Copyright 2020 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.
+  -->
+<manifest
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    package="androidx.emoji2.integration.macrobenchmark.disabled.target">
+
+    <application
+        android:label="Emoji2 Init Enabled Macrobenchmark Target"
+        android:allowBackup="false"
+        android:supportsRtl="true"
+        android:theme="@style/Theme.AppCompat"
+        tools:ignore="MissingApplicationIcon">
+
+        <!-- Profileable to enable macrobenchmark profiling -->
+        <!--suppress AndroidElementNotAllowed -->
+        <profileable android:shell="true"/>
+
+        <!--
+        Activities need to be exported so the macrobenchmark can discover them
+        under the new package visibility changes for Android 11.
+         -->
+        <activity
+            android:name=".SimpleTextActivity"
+            android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+            <intent-filter>
+                <action android:name="androidx.emoji2.integration.macrobenchmark.disabled.target.MAIN" />
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
+        </activity>
+        <provider
+            android:name="androidx.startup.InitializationProvider"
+            android:authorities="${applicationId}.androidx-startup"
+            android:exported="false"
+            tools:node="merge">
+            <meta-data android:name="androidx.emoji2.text.EmojiCompatInitializer"
+                tools:node="remove" />
+        </provider>
+    </application>
+</manifest>
diff --git a/emoji2/integration-tests/init-disabled-macrobenchmark-target/src/main/java/androidx/emoji2/integration/macrobenchmark/disabled/target/SimpleTextActivity.kt b/emoji2/integration-tests/init-disabled-macrobenchmark-target/src/main/java/androidx/emoji2/integration/macrobenchmark/disabled/target/SimpleTextActivity.kt
new file mode 100644
index 0000000..a4ae6f5
--- /dev/null
+++ b/emoji2/integration-tests/init-disabled-macrobenchmark-target/src/main/java/androidx/emoji2/integration/macrobenchmark/disabled/target/SimpleTextActivity.kt
@@ -0,0 +1,31 @@
+/*
+ * 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.integration.macrobenchmark.disabled.target
+
+import android.os.Bundle
+import android.widget.TextView
+import androidx.appcompat.app.AppCompatActivity
+
+class SimpleTextActivity : AppCompatActivity() {
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+        setContentView(R.layout.activity_main)
+
+        val notice = findViewById<TextView>(R.id.txtNotice)
+        notice.setText(R.string.app_notice)
+    }
+}
diff --git a/emoji2/integration-tests/init-disabled-macrobenchmark-target/src/main/res/layout/activity_main.xml b/emoji2/integration-tests/init-disabled-macrobenchmark-target/src/main/res/layout/activity_main.xml
new file mode 100644
index 0000000..6ec3889
--- /dev/null
+++ b/emoji2/integration-tests/init-disabled-macrobenchmark-target/src/main/res/layout/activity_main.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+  Copyright 2020 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.
+-->
+
+<androidx.constraintlayout.widget.ConstraintLayout 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">
+
+    <TextView
+        android:id="@+id/txtNotice"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent"
+        android:textSize="50sp"
+        tools:text="🐻‍❄️ (disabled)" />
+</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/emoji2/integration-tests/init-disabled-macrobenchmark-target/src/main/res/values/donottranslate-strings.xml b/emoji2/integration-tests/init-disabled-macrobenchmark-target/src/main/res/values/donottranslate-strings.xml
new file mode 100644
index 0000000..207eaaa
--- /dev/null
+++ b/emoji2/integration-tests/init-disabled-macrobenchmark-target/src/main/res/values/donottranslate-strings.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  Copyright 2020 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.
+  -->
+
+<resources>
+    <string name="app_notice">"🐻‍❄️" Appcompat emoji2 (disabled) test app.</string>
+</resources>
diff --git a/emoji2/integration-tests/init-disabled-macrobenchmark/build.gradle b/emoji2/integration-tests/init-disabled-macrobenchmark/build.gradle
new file mode 100644
index 0000000..1ef079d
--- /dev/null
+++ b/emoji2/integration-tests/init-disabled-macrobenchmark/build.gradle
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2020 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.
+ */
+
+
+import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
+
+import static androidx.build.dependencies.DependenciesKt.*
+import androidx.build.LibraryGroups
+import androidx.build.Publish
+
+plugins {
+    id("AndroidXPlugin")
+    id("com.android.library")
+    id("kotlin-android")
+}
+
+android {
+    defaultConfig {
+        minSdkVersion 28
+    }
+}
+
+dependencies {
+    androidTestImplementation(project(":benchmark:benchmark-macro-junit4"))
+    androidTestImplementation(project(":internal-testutils-macrobenchmark"))
+    androidTestImplementation(ANDROIDX_TEST_RULES)
+    androidTestImplementation(ANDROIDX_TEST_EXT_JUNIT)
+    androidTestImplementation(ANDROIDX_TEST_CORE)
+    androidTestImplementation(ANDROIDX_TEST_RUNNER)
+}
+
+def installReleaseTarget = tasks.getByPath(
+    ":emoji2:integration-tests:init-disabled-macrobenchmark-target:installRelease"
+)
+// Define a task dependency so the app is installed before we run macro benchmarks.
+tasks.getByPath(":emoji2:integration-tests:init-disabled-macrobenchmark:connectedCheck")
+    .dependsOn(installReleaseTarget)
diff --git a/emoji2/integration-tests/init-disabled-macrobenchmark/src/androidTest/AndroidManifest.xml b/emoji2/integration-tests/init-disabled-macrobenchmark/src/androidTest/AndroidManifest.xml
new file mode 100644
index 0000000..67818b1
--- /dev/null
+++ b/emoji2/integration-tests/init-disabled-macrobenchmark/src/androidTest/AndroidManifest.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2020 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.
+  -->
+<manifest package="androidx.emoji2.integration.macrobenchmark.disabled.test"/>
diff --git a/emoji2/integration-tests/init-disabled-macrobenchmark/src/androidTest/java/androidx/emoji2/integration/macrobenchmark/disabled/EmojiStartupBenchmark.kt b/emoji2/integration-tests/init-disabled-macrobenchmark/src/androidTest/java/androidx/emoji2/integration/macrobenchmark/disabled/EmojiStartupBenchmark.kt
new file mode 100644
index 0000000..488b9d8
--- /dev/null
+++ b/emoji2/integration-tests/init-disabled-macrobenchmark/src/androidTest/java/androidx/emoji2/integration/macrobenchmark/disabled/EmojiStartupBenchmark.kt
@@ -0,0 +1,45 @@
+/*
+ * 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.integration.macrobenchmark.disabled
+
+import androidx.benchmark.macro.CompilationMode
+import androidx.benchmark.macro.StartupMode
+import androidx.benchmark.macro.junit4.MacrobenchmarkRule
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.LargeTest
+import androidx.testutils.measureStartup
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@LargeTest
+@RunWith(AndroidJUnit4::class)
+class EmojiStartupBenchmark {
+    @get:Rule
+    val benchmarkRule = MacrobenchmarkRule()
+
+    @Test
+    fun disabledstartup() {
+        benchmarkRule.measureStartup(
+            compilationMode = CompilationMode.None,
+            startupMode = StartupMode.COLD,
+            packageName = "androidx.emoji2.integration.macrobenchmark.disabled.target"
+        ) {
+            action = "androidx.emoji2.integration.macrobenchmark.disabled.target.MAIN"
+        }
+    }
+}
diff --git a/emoji2/integration-tests/init-disabled-macrobenchmark/src/main/AndroidManifest.xml b/emoji2/integration-tests/init-disabled-macrobenchmark/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..0ca6a87
--- /dev/null
+++ b/emoji2/integration-tests/init-disabled-macrobenchmark/src/main/AndroidManifest.xml
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  ~ Copyright (C) 2020 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.
+  -->
+<manifest package="androidx.emoji2.integration.macrobenchmark.disabled" />
diff --git a/emoji2/integration-tests/init-enabled-macrobenchmark-target/README.md b/emoji2/integration-tests/init-enabled-macrobenchmark-target/README.md
new file mode 100644
index 0000000..890837d
--- /dev/null
+++ b/emoji2/integration-tests/init-enabled-macrobenchmark-target/README.md
@@ -0,0 +1 @@
+A simple app with emoji2 startup initializer enabled.
\ No newline at end of file
diff --git a/emoji2/integration-tests/init-enabled-macrobenchmark-target/build.gradle b/emoji2/integration-tests/init-enabled-macrobenchmark-target/build.gradle
new file mode 100644
index 0000000..9c0aa54
--- /dev/null
+++ b/emoji2/integration-tests/init-enabled-macrobenchmark-target/build.gradle
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2020 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.
+ */
+
+import static androidx.build.dependencies.DependenciesKt.*
+
+plugins {
+    id("AndroidXPlugin")
+    id("com.android.application")
+    id("kotlin-android")
+}
+
+android {
+    buildTypes {
+        release {
+            minifyEnabled true
+            shrinkResources true
+            proguardFiles getDefaultProguardFile("proguard-android-optimize.txt")
+        }
+    }
+}
+
+dependencies {
+    implementation(KOTLIN_STDLIB)
+    implementation(CONSTRAINT_LAYOUT, { transitive = true })
+    implementation(project(":arch:core:core-runtime"))
+    implementation(project(":appcompat:appcompat"))
+    implementation(MATERIAL)
+}
diff --git a/emoji2/integration-tests/init-enabled-macrobenchmark-target/src/main/AndroidManifest.xml b/emoji2/integration-tests/init-enabled-macrobenchmark-target/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..eff5f4c
--- /dev/null
+++ b/emoji2/integration-tests/init-enabled-macrobenchmark-target/src/main/AndroidManifest.xml
@@ -0,0 +1,49 @@
+<!--
+  ~ Copyright 2020 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.
+  -->
+<manifest
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    package="androidx.emoji2.integration.macrobenchmark.enabled.target">
+
+    <application
+        android:label="Emoji2 Init Enabled Macrobenchmark Target"
+        android:allowBackup="false"
+        android:supportsRtl="true"
+        android:theme="@style/Theme.AppCompat"
+        tools:ignore="MissingApplicationIcon">
+
+        <!-- Profileable to enable macrobenchmark profiling -->
+        <!--suppress AndroidElementNotAllowed -->
+        <profileable android:shell="true"/>
+
+        <!--
+        Activities need to be exported so the macrobenchmark can discover them
+        under the new package visibility changes for Android 11.
+         -->
+        <activity
+            android:name=".SimpleTextActivity"
+            android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+            <intent-filter>
+                <action android:name="androidx.emoji2.integration.macrobenchmark.enabled.target.MAIN" />
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
+        </activity>
+    </application>
+</manifest>
diff --git a/emoji2/integration-tests/init-enabled-macrobenchmark-target/src/main/java/androidx/emoji2/integration/macrobenchmark/enabled/target/SimpleTextActivity.kt b/emoji2/integration-tests/init-enabled-macrobenchmark-target/src/main/java/androidx/emoji2/integration/macrobenchmark/enabled/target/SimpleTextActivity.kt
new file mode 100644
index 0000000..e46d648
--- /dev/null
+++ b/emoji2/integration-tests/init-enabled-macrobenchmark-target/src/main/java/androidx/emoji2/integration/macrobenchmark/enabled/target/SimpleTextActivity.kt
@@ -0,0 +1,31 @@
+/*
+ * 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.integration.macrobenchmark.enabled.target
+
+import android.os.Bundle
+import android.widget.TextView
+import androidx.appcompat.app.AppCompatActivity
+
+class SimpleTextActivity : AppCompatActivity() {
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+        setContentView(R.layout.activity_main)
+
+        val notice = findViewById<TextView>(R.id.txtNotice)
+        notice.setText(R.string.app_notice)
+    }
+}
diff --git a/emoji2/integration-tests/init-enabled-macrobenchmark-target/src/main/res/layout/activity_main.xml b/emoji2/integration-tests/init-enabled-macrobenchmark-target/src/main/res/layout/activity_main.xml
new file mode 100644
index 0000000..c81807d
--- /dev/null
+++ b/emoji2/integration-tests/init-enabled-macrobenchmark-target/src/main/res/layout/activity_main.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+  Copyright 2020 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.
+-->
+
+<androidx.constraintlayout.widget.ConstraintLayout 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">
+
+    <TextView
+        android:id="@+id/txtNotice"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent"
+        android:textSize="50sp"
+        tools:text="🐻‍❄️" />
+
+</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/emoji2/integration-tests/init-enabled-macrobenchmark-target/src/main/res/values/donottranslate-strings.xml b/emoji2/integration-tests/init-enabled-macrobenchmark-target/src/main/res/values/donottranslate-strings.xml
new file mode 100644
index 0000000..11715d5
--- /dev/null
+++ b/emoji2/integration-tests/init-enabled-macrobenchmark-target/src/main/res/values/donottranslate-strings.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  Copyright 2020 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.
+  -->
+
+<resources>
+    <string name="app_notice">"🐻‍❄️" Appcompat emoji2 (enabled) test app.</string>
+</resources>
diff --git a/emoji2/integration-tests/init-enabled-macrobenchmark/build.gradle b/emoji2/integration-tests/init-enabled-macrobenchmark/build.gradle
new file mode 100644
index 0000000..c9ebc39
--- /dev/null
+++ b/emoji2/integration-tests/init-enabled-macrobenchmark/build.gradle
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2020 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.
+ */
+
+
+import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
+
+import static androidx.build.dependencies.DependenciesKt.*
+import androidx.build.LibraryGroups
+import androidx.build.Publish
+
+plugins {
+    id("AndroidXPlugin")
+    id("com.android.library")
+    id("kotlin-android")
+}
+
+android {
+    defaultConfig {
+        minSdkVersion 28
+    }
+}
+
+dependencies {
+    androidTestImplementation(project(":emoji2:emoji2"))
+    androidTestImplementation(project(":benchmark:benchmark-macro-junit4"))
+    androidTestImplementation(project(":internal-testutils-macrobenchmark"))
+    androidTestImplementation(ANDROIDX_TEST_RULES)
+    androidTestImplementation(ANDROIDX_TEST_EXT_JUNIT)
+    androidTestImplementation(ANDROIDX_TEST_CORE)
+    androidTestImplementation(ANDROIDX_TEST_RUNNER)
+}
+
+def installReleaseTarget = tasks.getByPath(
+        ":emoji2:integration-tests:init-enabled-macrobenchmark-target:installRelease"
+)
+
+// Define a task dependency so the app is installed before we run macro benchmarks.
+tasks.getByPath(":emoji2:integration-tests:init-enabled-macrobenchmark:connectedCheck")
+    .dependsOn(installReleaseTarget)
diff --git a/emoji2/integration-tests/init-enabled-macrobenchmark/src/androidTest/AndroidManifest.xml b/emoji2/integration-tests/init-enabled-macrobenchmark/src/androidTest/AndroidManifest.xml
new file mode 100644
index 0000000..dbacc9c
--- /dev/null
+++ b/emoji2/integration-tests/init-enabled-macrobenchmark/src/androidTest/AndroidManifest.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2020 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.
+  -->
+<manifest package="androidx.emoji2.integration.macrobenchmark.enabled.test"/>
diff --git a/emoji2/integration-tests/init-enabled-macrobenchmark/src/androidTest/java/androidx/emoji2/integration/macrobenchmark/enabled/EmojiStartupBenchmark.kt b/emoji2/integration-tests/init-enabled-macrobenchmark/src/androidTest/java/androidx/emoji2/integration/macrobenchmark/enabled/EmojiStartupBenchmark.kt
new file mode 100644
index 0000000..61e06cb
--- /dev/null
+++ b/emoji2/integration-tests/init-enabled-macrobenchmark/src/androidTest/java/androidx/emoji2/integration/macrobenchmark/enabled/EmojiStartupBenchmark.kt
@@ -0,0 +1,55 @@
+/*
+ * 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.integration.macrobenchmark.enabled
+
+import androidx.benchmark.macro.CompilationMode
+import androidx.benchmark.macro.StartupMode
+import androidx.benchmark.macro.junit4.MacrobenchmarkRule
+import androidx.emoji2.text.DefaultEmojiCompatConfig
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.LargeTest
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.testutils.measureStartup
+import org.junit.Assume.assumeTrue
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@LargeTest
+@RunWith(AndroidJUnit4::class)
+class EmojiStartupBenchmark {
+    @get:Rule
+    val benchmarkRule = MacrobenchmarkRule()
+
+    @Test
+    fun enabledstartup() {
+        // only run this test if the device can configure emoji2
+        assumeTrue(hasDiscoverableFontProviderOnDevice())
+        benchmarkRule.measureStartup(
+            compilationMode = CompilationMode.None,
+            startupMode = StartupMode.COLD,
+            packageName = "androidx.emoji2.integration.macrobenchmark.enabled.target"
+        ) {
+            action = "androidx.emoji2.integration.macrobenchmark.enabled.target.MAIN"
+        }
+    }
+
+    private fun hasDiscoverableFontProviderOnDevice(): Boolean {
+        val context = InstrumentationRegistry.getInstrumentation().targetContext
+        return DefaultEmojiCompatConfig.create(context) != null
+    }
+}
\ No newline at end of file
diff --git a/emoji2/integration-tests/init-enabled-macrobenchmark/src/main/AndroidManifest.xml b/emoji2/integration-tests/init-enabled-macrobenchmark/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..040c742
--- /dev/null
+++ b/emoji2/integration-tests/init-enabled-macrobenchmark/src/main/AndroidManifest.xml
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  ~ Copyright (C) 2020 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.
+  -->
+<manifest package="androidx.emoji2.integration.macrobenchmark.enabled" />
diff --git a/enterprise/feedback/lint-baseline.xml b/enterprise/feedback/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/enterprise/feedback/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/enterprise/feedback/testing/lint-baseline.xml b/enterprise/feedback/testing/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/enterprise/feedback/testing/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/fragment/fragment-ktx/lint-baseline.xml b/fragment/fragment-ktx/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/fragment/fragment-ktx/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/fragment/fragment-lint/lint-baseline.xml b/fragment/fragment-lint/lint-baseline.xml
deleted file mode 100644
index 8794ae8..0000000
--- a/fragment/fragment-lint/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" version="4.2.0-beta06">
-
-</issues>
diff --git a/fragment/fragment-lint/src/main/java/androidx/fragment/lint/FragmentIssueRegistry.kt b/fragment/fragment-lint/src/main/java/androidx/fragment/lint/FragmentIssueRegistry.kt
index 5384ce0..c0dc727 100644
--- a/fragment/fragment-lint/src/main/java/androidx/fragment/lint/FragmentIssueRegistry.kt
+++ b/fragment/fragment-lint/src/main/java/androidx/fragment/lint/FragmentIssueRegistry.kt
@@ -31,6 +31,7 @@
         UnsafeFragmentLifecycleObserverDetector.BACK_PRESSED_ISSUE,
         UnsafeFragmentLifecycleObserverDetector.LIVEDATA_ISSUE,
         UseRequireInsteadOfGet.ISSUE,
-        UseGetLayoutInflater.ISSUE
+        UseGetLayoutInflater.ISSUE,
+        OnCreateDialogIncorrectCallbackDetector.ISSUE
     )
 }
diff --git a/fragment/fragment-lint/src/main/java/androidx/fragment/lint/OnCreateDialogIncorrectCallbackDetector.kt b/fragment/fragment-lint/src/main/java/androidx/fragment/lint/OnCreateDialogIncorrectCallbackDetector.kt
new file mode 100644
index 0000000..b462e88
--- /dev/null
+++ b/fragment/fragment-lint/src/main/java/androidx/fragment/lint/OnCreateDialogIncorrectCallbackDetector.kt
@@ -0,0 +1,156 @@
+/*
+ * 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.
+ */
+
+@file:Suppress("UnstableApiUsage")
+
+package androidx.fragment.lint
+
+import com.android.tools.lint.client.api.UElementHandler
+import com.android.tools.lint.detector.api.Category
+import com.android.tools.lint.detector.api.Detector
+import com.android.tools.lint.detector.api.Implementation
+import com.android.tools.lint.detector.api.Issue
+import com.android.tools.lint.detector.api.JavaContext
+import com.android.tools.lint.detector.api.Scope
+import com.android.tools.lint.detector.api.Severity
+import com.android.tools.lint.detector.api.SourceCodeScanner
+import com.android.tools.lint.detector.api.isKotlin
+import com.intellij.psi.impl.source.PsiClassReferenceType
+import org.jetbrains.kotlin.psi.psiUtil.getSuperNames
+import org.jetbrains.uast.UCallExpression
+import org.jetbrains.uast.UClass
+import org.jetbrains.uast.UElement
+import org.jetbrains.uast.kotlin.KotlinUClass
+import org.jetbrains.uast.visitor.AbstractUastVisitor
+
+/**
+ * When using a `DialogFragment`, the `setOnCancelListener` and `setOnDismissListener` callback
+ * functions within the `onCreateDialog` function __must not be used__
+ * because the `DialogFragment` owns these callbacks. Instead the respective `onCancel` and
+ * `onDismiss` functions can be used to achieve the desired effect.
+ */
+class OnCreateDialogIncorrectCallbackDetector : Detector(), SourceCodeScanner {
+
+    companion object Issues {
+        val ISSUE = Issue.create(
+            id = "DialogFragmentCallbacksDetector",
+            briefDescription = "Use onCancel() and onDismiss() instead of calling " +
+                "setOnCancelListener() and setOnDismissListener() from onCreateDialog()",
+            explanation = """When using a `DialogFragment`, the `setOnCancelListener` and \
+                `setOnDismissListener` callback functions within the `onCreateDialog` function \
+                 __must not be used__ because the `DialogFragment` owns these callbacks. \
+                 Instead the respective `onCancel` and `onDismiss` functions can be used to \
+                 achieve the desired effect.""",
+            category = Category.CORRECTNESS,
+            severity = Severity.WARNING,
+            implementation = Implementation(
+                OnCreateDialogIncorrectCallbackDetector::class.java,
+                Scope.JAVA_FILE_SCOPE
+            ),
+            androidSpecific = true
+        )
+    }
+
+    override fun getApplicableUastTypes(): List<Class<out UElement>>? {
+        return listOf(UClass::class.java)
+    }
+
+    override fun createUastHandler(context: JavaContext): UElementHandler? {
+        return UastHandler(context)
+    }
+
+    private inner class UastHandler(val context: JavaContext) : UElementHandler() {
+        override fun visitClass(node: UClass) {
+            if (isKotlin(context.psiFile) && (node as KotlinUClass).ktClass!!.getSuperNames()[0] !=
+                DIALOG_FRAGMENT_CLASS
+            ) {
+                return
+            }
+
+            if (!isKotlin(context.psiFile) &&
+                (node.uastSuperTypes[0].type as PsiClassReferenceType)
+                    .className != DIALOG_FRAGMENT_CLASS
+            ) {
+                return
+            }
+
+            node.methods.forEach {
+                if (it.name == ENTRY_METHOD) {
+                    val visitor = UastMethodsVisitor(context, it.name)
+                    it.uastBody?.accept(visitor)
+                }
+            }
+        }
+    }
+
+    /**
+     * A UAST Visitor that explores all method calls within a
+     * [androidx.fragment.app.DialogFragment] callback to check for an incorrect method call.
+     *
+     * @param context The context of the lint request.
+     * @param containingMethodName The name of the originating Fragment lifecycle method.
+     */
+    private class UastMethodsVisitor(
+        private val context: JavaContext,
+        private val containingMethodName: String
+    ) : AbstractUastVisitor() {
+        private val visitedMethods = mutableSetOf<UCallExpression>()
+
+        override fun visitCallExpression(node: UCallExpression): Boolean {
+            if (visitedMethods.contains(node)) {
+                return super.visitCallExpression(node)
+            }
+
+            val methodName = node.methodIdentifier?.name ?: return super.visitCallExpression(node)
+
+            when (methodName) {
+                SET_ON_CANCEL_LISTENER -> {
+                    report(
+                        context = context,
+                        node = node,
+                        message = "Use onCancel() instead of calling setOnCancelListener() " +
+                            "from onCreateDialog()"
+                    )
+                    visitedMethods.add(node)
+                }
+                SET_ON_DISMISS_LISTENER -> {
+                    report(
+                        context = context,
+                        node = node,
+                        message = "Use onDismiss() instead of calling setOnDismissListener() " +
+                            "from onCreateDialog()"
+                    )
+                    visitedMethods.add(node)
+                }
+            }
+            return super.visitCallExpression(node)
+        }
+
+        private fun report(context: JavaContext, node: UCallExpression, message: String) {
+            context.report(
+                issue = ISSUE,
+                location = context.getLocation(node),
+                message = message,
+                quickfixData = null
+            )
+        }
+    }
+}
+
+private const val ENTRY_METHOD = "onCreateDialog"
+private const val DIALOG_FRAGMENT_CLASS = "DialogFragment"
+private const val SET_ON_CANCEL_LISTENER = "setOnCancelListener"
+private const val SET_ON_DISMISS_LISTENER = "setOnDismissListener"
diff --git a/fragment/fragment-lint/src/test/java/androidx/fragment/lint/OnCreateDialogIncorrectCallbackDetectorTest.kt b/fragment/fragment-lint/src/test/java/androidx/fragment/lint/OnCreateDialogIncorrectCallbackDetectorTest.kt
new file mode 100644
index 0000000..c67cd41
--- /dev/null
+++ b/fragment/fragment-lint/src/test/java/androidx/fragment/lint/OnCreateDialogIncorrectCallbackDetectorTest.kt
@@ -0,0 +1,271 @@
+/*
+ * 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.fragment.lint
+
+import com.android.tools.lint.checks.infrastructure.LintDetectorTest
+import com.android.tools.lint.detector.api.Detector
+import com.android.tools.lint.detector.api.Issue
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+/* ktlint-disable max-line-length */
+@RunWith(JUnit4::class)
+class OnCreateDialogIncorrectCallbackDetectorTest : LintDetectorTest() {
+
+    override fun getDetector(): Detector = OnCreateDialogIncorrectCallbackDetector()
+
+    override fun getIssues(): MutableList<Issue> {
+        return mutableListOf(OnCreateDialogIncorrectCallbackDetector.ISSUE)
+    }
+
+    private val dialogFragmentCorrectImplementationStubJava = java(
+        """
+            package foo;
+            import android.app.Dialog;
+            import android.content.DialogInterface;
+            import android.os.Bundle;
+            import androidx.appcompat.app.AlertDialog;
+            import androidx.fragment.app.DialogFragment;
+            public class TestFragment extends DialogFragment {
+                @NonNull
+                @Override
+                public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
+                    Dialog dialog = AlertDialog.Builder(requireActivity());
+                    return dialog.create();
+                }
+            }
+            """
+    ).indented()
+
+    private val dialogFragmentCorrectImplementationStubKotlin = kotlin(
+        """
+            package foo
+            import android.app.Dialog
+            import android.content.DialogInterface
+            import android.os.Bundle
+            import androidx.appcompat.app.AlertDialog
+            import androidx.fragment.app.DialogFragment
+            class TestDialog : DialogFragment() {
+                override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
+                    val dialog = AlertDialog.Builder(requireActivity())
+                    return dialog.create()
+                }
+                override fun onCancel(dialog: DialogInterface) {
+                    super.onCancel(dialog)
+                }
+
+                override fun onDismiss(dialog: DialogInterface) {
+                    super.onDismiss(dialog)
+                }
+            }
+            """
+    ).indented()
+
+    private val dialogFragmentStubJavaWithCancelListener = java(
+        """
+            package foo;
+            import android.app.Dialog;
+            import android.content.DialogInterface;
+            import android.os.Bundle;
+            import androidx.appcompat.app.AlertDialog;
+            import androidx.fragment.app.DialogFragment;
+            public class TestFragment extends DialogFragment {
+                @Override
+                public Dialog onCreateDialog(Bundle savedInstanceState) {
+                    Dialog dialog = AlertDialog.Builder(requireActivity());
+                    dialog.setOnCancelListener({ });
+                    return dialog.create();
+                }
+            }
+            """
+    ).indented()
+
+    private val dialogFragmentStubJavaWithDismissListener = java(
+        """
+            package foo;
+            import android.app.Dialog;
+            import android.content.DialogInterface;
+            import android.os.Bundle;
+            import androidx.appcompat.app.AlertDialog;
+            import androidx.fragment.app.DialogFragment;
+            public class TestFragment extends DialogFragment {
+                @Override
+                public Dialog onCreateDialog(Bundle savedInstanceState) {
+                    Dialog dialog = AlertDialog.Builder(requireActivity());
+                    dialog.setOnDismissListener({ });
+                    return dialog.create();
+                }
+            }
+            """
+    ).indented()
+
+    private val dialogFragmentStubKotlinWithCancelListener = kotlin(
+        """
+            package foo
+            import android.app.Dialog
+            import android.content.DialogInterface
+            import android.os.Bundle
+            import androidx.appcompat.app.AlertDialog
+            import androidx.fragment.app.DialogFragment
+            class TestDialog : DialogFragment() {
+                override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
+                    val dialog = AlertDialog.Builder(requireActivity())
+                    dialog.setOnCancelListener { }
+                    return dialog.create()
+                }
+
+                override fun onDismiss(dialog: DialogInterface) {
+                    super.onDismiss(dialog)
+                }
+            }
+            """
+    ).indented()
+
+    private val dialogFragmentStubKotlinWithDismissListener = kotlin(
+        """
+            package foo
+            import android.app.Dialog
+            import android.content.DialogInterface
+            import android.os.Bundle
+            import androidx.appcompat.app.AlertDialog
+            import androidx.fragment.app.DialogFragment
+            class TestDialog : DialogFragment() {
+                override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
+                    val dialog = AlertDialog.Builder(requireActivity())
+                    dialog.setOnDismissListener { }
+                    return dialog.create()
+                }
+
+                override fun onCancel(dialog: DialogInterface) {
+                    super.onCancel(dialog)
+                }
+            }
+            """
+    ).indented()
+
+    private val dialogFragmentStubKotlinWithDismissAndCancelListeners = kotlin(
+        """
+            package foo
+            import android.app.Dialog
+            import android.content.DialogInterface
+            import android.os.Bundle
+            import androidx.appcompat.app.AlertDialog
+            import androidx.fragment.app.DialogFragment
+            class TestDialog : DialogFragment() {
+                override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
+                    val dialog = AlertDialog.Builder(requireActivity())
+                    dialog.setOnDismissListener { }
+                    dialog.setOnCancelListener { }
+                    return dialog.create()
+                }
+            }
+            """
+    ).indented()
+
+    @Test
+    fun `java expect fail dialog fragment with cancel listener`() {
+        lint().files(dialogFragmentStubJavaWithCancelListener)
+            .run()
+            .expect(
+                """
+src/foo/TestFragment.java:11: Warning: Use onCancel() instead of calling setOnCancelListener() from onCreateDialog() [DialogFragmentCallbacksDetector]
+        dialog.setOnCancelListener({ });
+        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+0 errors, 1 warnings
+            """
+            )
+            .expectWarningCount(1)
+    }
+
+    @Test
+    fun `java expect fail dialog fragment with dismiss listener`() {
+        lint().files(dialogFragmentStubJavaWithDismissListener)
+            .run()
+            .expect(
+                """
+src/foo/TestFragment.java:11: Warning: Use onDismiss() instead of calling setOnDismissListener() from onCreateDialog() [DialogFragmentCallbacksDetector]
+        dialog.setOnDismissListener({ });
+        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+0 errors, 1 warnings
+            """
+            )
+            .expectWarningCount(1)
+    }
+
+    @Test
+    fun `java expect clean dialog fragment`() {
+        lint().files(dialogFragmentCorrectImplementationStubJava)
+            .run()
+            .expectClean()
+    }
+
+    @Test
+    fun `kotlin expect fail dialog fragment with cancel listener`() {
+        lint().files(dialogFragmentStubKotlinWithCancelListener)
+            .run()
+            .expect(
+                """
+src/foo/TestDialog.kt:10: Warning: Use onCancel() instead of calling setOnCancelListener() from onCreateDialog() [DialogFragmentCallbacksDetector]
+        dialog.setOnCancelListener { }
+        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+0 errors, 1 warnings
+            """
+            )
+            .expectWarningCount(1)
+    }
+
+    @Test
+    fun `kotlin expect fail dialog fragment with dismiss listener`() {
+        lint().files(dialogFragmentStubKotlinWithDismissListener)
+            .run()
+            .expect(
+                """
+src/foo/TestDialog.kt:10: Warning: Use onDismiss() instead of calling setOnDismissListener() from onCreateDialog() [DialogFragmentCallbacksDetector]
+        dialog.setOnDismissListener { }
+        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+0 errors, 1 warnings
+            """
+            )
+            .expectWarningCount(1)
+    }
+
+    @Test
+    fun `kotlin expect fail dialog fragment with dismiss and cancel listeners`() {
+        lint().files(dialogFragmentStubKotlinWithDismissAndCancelListeners)
+            .run()
+            .expect(
+                """
+src/foo/TestDialog.kt:10: Warning: Use onDismiss() instead of calling setOnDismissListener() from onCreateDialog() [DialogFragmentCallbacksDetector]
+        dialog.setOnDismissListener { }
+        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+src/foo/TestDialog.kt:11: Warning: Use onCancel() instead of calling setOnCancelListener() from onCreateDialog() [DialogFragmentCallbacksDetector]
+        dialog.setOnCancelListener { }
+        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+0 errors, 2 warnings
+            """
+            )
+            .expectWarningCount(2)
+    }
+
+    @Test
+    fun `kotlin expect clean dialog fragment`() {
+        lint().files(dialogFragmentCorrectImplementationStubKotlin)
+            .run()
+            .expectClean()
+    }
+}
diff --git a/fragment/fragment-testing-lint/lint-baseline.xml b/fragment/fragment-testing-lint/lint-baseline.xml
deleted file mode 100644
index 8794ae8..0000000
--- a/fragment/fragment-testing-lint/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" version="4.2.0-beta06">
-
-</issues>
diff --git a/fragment/fragment-testing/lint-baseline.xml b/fragment/fragment-testing/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/fragment/fragment-testing/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/fragment/fragment-truth/lint-baseline.xml b/fragment/fragment-truth/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/fragment/fragment-truth/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/fragment/fragment/api/current.txt b/fragment/fragment/api/current.txt
index 38049f1..83ddaab 100644
--- a/fragment/fragment/api/current.txt
+++ b/fragment/fragment/api/current.txt
@@ -196,6 +196,7 @@
     ctor public FragmentContainerView(android.content.Context);
     ctor public FragmentContainerView(android.content.Context, android.util.AttributeSet?);
     ctor public FragmentContainerView(android.content.Context, android.util.AttributeSet?, int);
+    method public <F extends androidx.fragment.app.Fragment> F? getFragment();
   }
 
   public class FragmentController {
diff --git a/fragment/fragment/api/public_plus_experimental_current.txt b/fragment/fragment/api/public_plus_experimental_current.txt
index c4a2d4d..6cb05d1 100644
--- a/fragment/fragment/api/public_plus_experimental_current.txt
+++ b/fragment/fragment/api/public_plus_experimental_current.txt
@@ -196,6 +196,7 @@
     ctor public FragmentContainerView(android.content.Context);
     ctor public FragmentContainerView(android.content.Context, android.util.AttributeSet?);
     ctor public FragmentContainerView(android.content.Context, android.util.AttributeSet?, int);
+    method public <F extends androidx.fragment.app.Fragment> F? getFragment();
   }
 
   public class FragmentController {
diff --git a/fragment/fragment/api/restricted_current.txt b/fragment/fragment/api/restricted_current.txt
index 800e0f1..680d7b8 100644
--- a/fragment/fragment/api/restricted_current.txt
+++ b/fragment/fragment/api/restricted_current.txt
@@ -201,6 +201,7 @@
     ctor public FragmentContainerView(android.content.Context);
     ctor public FragmentContainerView(android.content.Context, android.util.AttributeSet?);
     ctor public FragmentContainerView(android.content.Context, android.util.AttributeSet?, int);
+    method public <F extends androidx.fragment.app.Fragment> F? getFragment();
   }
 
   public class FragmentController {
diff --git a/fragment/fragment/src/androidTest/java/androidx/fragment/app/FragmentContainerViewTest.kt b/fragment/fragment/src/androidTest/java/androidx/fragment/app/FragmentContainerViewTest.kt
index 775580f..5bc6cdf 100644
--- a/fragment/fragment/src/androidTest/java/androidx/fragment/app/FragmentContainerViewTest.kt
+++ b/fragment/fragment/src/androidTest/java/androidx/fragment/app/FragmentContainerViewTest.kt
@@ -27,6 +27,7 @@
 import android.view.ViewGroup
 import android.view.WindowInsets
 import android.view.animation.Animation
+import androidx.core.app.ActivityCompat
 import androidx.core.view.ViewCompat
 import androidx.fragment.app.test.FragmentTestActivity
 import androidx.fragment.test.R
@@ -696,6 +697,66 @@
         assertThat(drawnFirst!!).isEqualTo(frag2View)
     }
 
+    @Test
+    fun getFragmentNoneAdded() {
+        val fragmentContainerView = ActivityCompat.requireViewById<FragmentContainerView>(
+            activityRule.activity,
+            R.id.fragment_container_view
+        )
+
+        assertThat(fragmentContainerView.getFragment<StrictViewFragment>()).isNull()
+    }
+
+    @Test
+    fun getFragmentTwoAdds() {
+        val fragmentContainerView = ActivityCompat.requireViewById<FragmentContainerView>(
+            activityRule.activity,
+            R.id.fragment_container_view
+        )
+        val fm = activityRule.activity.supportFragmentManager
+
+        val fragment1 = StrictViewFragment()
+        val fragment2 = StrictViewFragment()
+
+        fm.beginTransaction()
+            .add(R.id.fragment_container_view, fragment1)
+            .commit()
+        activityRule.waitForExecution()
+
+        fm.beginTransaction()
+            .add(R.id.fragment_container_view, fragment2)
+            .commit()
+        activityRule.waitForExecution()
+
+        val topFragment = fragmentContainerView.getFragment<StrictViewFragment>()
+        assertThat(topFragment).isSameInstanceAs(fragment2)
+    }
+
+    @Test
+    fun getFragmentAddAndReplace() {
+        val fragmentContainerView = ActivityCompat.requireViewById<FragmentContainerView>(
+            activityRule.activity,
+            R.id.fragment_container_view
+        )
+        val fm = activityRule.activity.supportFragmentManager
+
+        val fragment1 = StrictViewFragment(R.layout.fragment_container_view)
+        val fragment2 = StrictViewFragment(R.layout.fragment_container_view)
+
+        fm.beginTransaction()
+            .add(R.id.fragment_container_view, fragment1)
+            .commit()
+        activityRule.waitForExecution()
+
+        fm.beginTransaction()
+            .replace(R.id.fragment_container_view, fragment2)
+            .commit()
+        activityRule.waitForExecution()
+
+        val topFragment = fragmentContainerView.getFragment<StrictViewFragment>()
+        assertThat(topFragment).isSameInstanceAs(fragment2)
+    }
+
     class ChildViewFragment(val viewTag: String? = null) : StrictViewFragment() {
         override fun onCreateView(
             inflater: LayoutInflater,
diff --git a/fragment/fragment/src/androidTest/java/androidx/fragment/app/FragmentResultTest.kt b/fragment/fragment/src/androidTest/java/androidx/fragment/app/FragmentResultTest.kt
index 4635777..3d6b8e2 100644
--- a/fragment/fragment/src/androidTest/java/androidx/fragment/app/FragmentResultTest.kt
+++ b/fragment/fragment/src/androidTest/java/androidx/fragment/app/FragmentResultTest.kt
@@ -16,7 +16,10 @@
 
 package androidx.fragment.app
 
+import android.app.Activity
 import android.os.Bundle
+import android.os.Parcelable
+import androidx.activity.result.ActivityResult
 import androidx.fragment.app.test.FragmentTestActivity
 import androidx.fragment.test.R
 import androidx.test.core.app.ActivityScenario
@@ -380,6 +383,54 @@
                 .isEqualTo("resultGood")
         }
     }
+
+    @Test
+    fun testReplaceResultWithParcelableOnRecreation() {
+        with(ActivityScenario.launch(FragmentTestActivity::class.java)) {
+            var fm = withActivity {
+                setContentView(R.layout.simple_container)
+                supportFragmentManager
+            }
+            var fragment1 = ParcelableResultFragment()
+
+            fm.beginTransaction()
+                .add(R.id.fragmentContainer, fragment1, "fragment1")
+                .commit()
+            executePendingTransactions()
+
+            val fragment2 = StrictFragment()
+
+            fm.beginTransaction()
+                .replace(R.id.fragmentContainer, fragment2)
+                .addToBackStack(null)
+                .commit()
+            executePendingTransactions()
+
+            val resultBundle = Bundle()
+            val expectedResult = ActivityResult(Activity.RESULT_OK, null)
+            resultBundle.putParcelable("bundleKey", expectedResult)
+
+            fm.setFragmentResult("requestKey", resultBundle)
+
+            assertWithMessage("The result is not set")
+                .that(fragment1.actualResult)
+                .isNull()
+
+            recreate()
+
+            fm = withActivity { supportFragmentManager }
+
+            withActivity {
+                fm.popBackStackImmediate()
+            }
+
+            fragment1 = fm.findFragmentByTag("fragment1") as ParcelableResultFragment
+
+            assertWithMessage("The result is incorrect")
+                .that(fragment1.actualResult)
+                .isEqualTo(expectedResult)
+        }
+    }
 }
 
 class ResultFragment : StrictFragment() {
@@ -434,4 +485,18 @@
         }
         parentFragmentManager.setFragmentResult("requestKey", resultBundle)
     }
-}
\ No newline at end of file
+}
+
+class ParcelableResultFragment : StrictFragment() {
+    var actualResult: Parcelable? = null
+
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+
+        parentFragmentManager.setFragmentResultListener(
+            "requestKey", this,
+            FragmentResultListener
+            { _, bundle -> actualResult = bundle.getParcelable<ActivityResult>("bundleKey") }
+        )
+    }
+}
diff --git a/fragment/fragment/src/androidTest/java/androidx/fragment/app/FragmentViewLifecycleOwnerTest.kt b/fragment/fragment/src/androidTest/java/androidx/fragment/app/FragmentViewLifecycleOwnerTest.kt
new file mode 100644
index 0000000..67ae872
--- /dev/null
+++ b/fragment/fragment/src/androidTest/java/androidx/fragment/app/FragmentViewLifecycleOwnerTest.kt
@@ -0,0 +1,111 @@
+/*
+ * 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.fragment.app
+
+import androidx.fragment.app.test.FragmentTestActivity
+import androidx.fragment.test.R
+import androidx.lifecycle.HasDefaultViewModelProviderFactory
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.ViewModelProvider
+import androidx.test.core.app.ActivityScenario
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import androidx.testutils.withActivity
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+@SmallTest
+class FragmentViewLifecycleOwnerTest {
+
+    /**
+     * Test representing a Non-Hilt case, in which the default factory is not overwritten at the
+     * Fragment level.
+     */
+    @Test
+    fun defaultFactoryNotOverwritten() {
+        with(ActivityScenario.launch(FragmentTestActivity::class.java)) {
+            val fm = withActivity {
+                setContentView(R.layout.simple_container)
+                supportFragmentManager
+            }
+            val fragment = StrictViewFragment()
+
+            fm.beginTransaction()
+                .add(R.id.fragmentContainer, fragment)
+                .commit()
+            executePendingTransactions()
+
+            val defaultFactory1 = (
+                fragment.viewLifecycleOwner as HasDefaultViewModelProviderFactory
+                ).defaultViewModelProviderFactory
+            val defaultFactory2 = (
+                fragment.viewLifecycleOwner as HasDefaultViewModelProviderFactory
+                ).defaultViewModelProviderFactory
+
+            // Assure that multiple call return the same default factory
+            assertThat(defaultFactory1).isSameInstanceAs(defaultFactory2)
+            assertThat(defaultFactory1).isNotSameInstanceAs(
+                fragment.defaultViewModelProviderFactory
+            )
+        }
+    }
+
+    /**
+     * Test representing a Hilt case, in which the default factory is overwritten at the
+     * Fragment level.
+     */
+    @Test
+    fun defaultFactoryOverwritten() {
+        with(ActivityScenario.launch(FragmentTestActivity::class.java)) {
+            val fm = withActivity {
+                setContentView(R.layout.simple_container)
+                supportFragmentManager
+            }
+            val fragment = FragmentWithFactoryOverride()
+
+            fm.beginTransaction()
+                .add(R.id.fragmentContainer, fragment)
+                .commit()
+            executePendingTransactions()
+
+            val defaultFactory = (
+                fragment.viewLifecycleOwner as HasDefaultViewModelProviderFactory
+                ).defaultViewModelProviderFactory
+
+            assertThat(defaultFactory).isInstanceOf(FakeViewModelProviderFactory::class.java)
+        }
+    }
+
+    private class TestViewModel : ViewModel()
+
+    class FakeViewModelProviderFactory : ViewModelProvider.Factory {
+        private var createCalled: Boolean = false
+        override fun <T : ViewModel?> create(modelClass: Class<T>): T {
+            require(modelClass == TestViewModel::class.java)
+            createCalled = true
+            @Suppress("UNCHECKED_CAST")
+            return TestViewModel() as T
+        }
+    }
+
+    public class FragmentWithFactoryOverride : StrictViewFragment() {
+        public override fun getDefaultViewModelProviderFactory(): ViewModelProvider.Factory =
+            FakeViewModelProviderFactory()
+    }
+}
\ No newline at end of file
diff --git a/fragment/fragment/src/androidTest/java/androidx/fragment/app/SaveRestoreBackStackTest.kt b/fragment/fragment/src/androidTest/java/androidx/fragment/app/SaveRestoreBackStackTest.kt
index a66d652..42ad4dc 100644
--- a/fragment/fragment/src/androidTest/java/androidx/fragment/app/SaveRestoreBackStackTest.kt
+++ b/fragment/fragment/src/androidTest/java/androidx/fragment/app/SaveRestoreBackStackTest.kt
@@ -24,7 +24,7 @@
 import androidx.lifecycle.ViewModelProvider
 import androidx.test.core.app.ActivityScenario
 import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.MediumTest
+import androidx.test.filters.LargeTest
 import androidx.testutils.withActivity
 import com.google.common.truth.Truth.assertThat
 import com.google.common.truth.Truth.assertWithMessage
@@ -32,7 +32,7 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 
-@MediumTest
+@LargeTest
 @RunWith(AndroidJUnit4::class)
 class SaveRestoreBackStackTest {
 
@@ -272,6 +272,7 @@
             assertWithMessage("ViewModel should not be cleared after commit()")
                 .that(originalViewModel.cleared)
                 .isFalse()
+            assertThat(fm.backStackEntryCount).isEqualTo(1)
 
             fm.saveBackStack("replacement")
             executePendingTransactions()
@@ -279,6 +280,7 @@
             assertWithMessage("Saved Fragments should have their state saved")
                 .that(fragmentReplacement.calledOnSaveInstanceState)
                 .isTrue()
+            assertThat(fm.backStackEntryCount).isEqualTo(0)
 
             // Saved Fragments should be destroyed
             assertWithMessage("Saved Fragments should be destroyed")
@@ -303,6 +305,7 @@
             assertThat(stateSavedReplacement.savedState).isEqualTo("saved")
             assertThat(stateSavedReplacement.unsavedState).isNull()
             assertThat(stateSavedReplacement.viewModel).isSameInstanceAs(originalViewModel)
+            assertThat(fm.backStackEntryCount).isEqualTo(1)
         }
     }
 
@@ -331,6 +334,7 @@
             assertWithMessage("ViewModel should not be cleared after commit()")
                 .that(originalViewModel.cleared)
                 .isFalse()
+            assertThat(fm.backStackEntryCount).isEqualTo(1)
 
             fm.saveBackStack("replacement")
             executePendingTransactions()
@@ -338,6 +342,7 @@
             assertWithMessage("Saved Fragments should have their state saved")
                 .that(fragmentReplacement.calledOnSaveInstanceState)
                 .isTrue()
+            assertThat(fm.backStackEntryCount).isEqualTo(0)
 
             // Saved Fragments should be destroyed
             assertWithMessage("Saved Fragments should be destroyed")
@@ -369,6 +374,63 @@
             assertThat(stateSavedReplacement.savedState).isEqualTo("saved")
             assertThat(stateSavedReplacement.unsavedState).isNull()
             assertThat(stateSavedReplacement.viewModel).isSameInstanceAs(originalViewModel)
+            assertThat(fm.backStackEntryCount).isEqualTo(1)
+        }
+    }
+
+    @Test
+    fun restoreBackStackWithoutExecutePendingTransactions() {
+        with(ActivityScenario.launch(FragmentTestActivity::class.java)) {
+            val fm = withActivity {
+                supportFragmentManager
+            }
+            val fragmentBase = StrictFragment()
+            val fragmentReplacement = StateSaveFragment("saved", "unsaved")
+
+            fm.beginTransaction()
+                .add(R.id.content, fragmentBase)
+                .commit()
+            executePendingTransactions()
+
+            fm.beginTransaction()
+                .setReorderingAllowed(true)
+                .replace(R.id.content, fragmentReplacement)
+                .addToBackStack("replacement")
+                .commit()
+            executePendingTransactions()
+
+            val originalViewModel = fragmentReplacement.viewModel
+            assertWithMessage("ViewModel should not be cleared after commit()")
+                .that(originalViewModel.cleared)
+                .isFalse()
+            assertThat(fm.backStackEntryCount).isEqualTo(1)
+
+            withActivity {
+                fm.saveBackStack("replacement")
+                // Immediately restore the back stack without calling executePendingTransactions
+                fm.restoreBackStack("replacement")
+            }
+            executePendingTransactions()
+
+            assertWithMessage("Saved Fragments should not go through onSaveInstanceState")
+                .that(fragmentReplacement.calledOnSaveInstanceState)
+                .isFalse()
+            assertWithMessage("Saved Fragments should not have been destroyed")
+                .that(fragmentReplacement.calledOnDestroy)
+                .isFalse()
+            assertWithMessage("ViewModel should not be cleared after saveBackStack()")
+                .that(originalViewModel.cleared)
+                .isFalse()
+
+            assertWithMessage("Fragment should still be returned by FragmentManager")
+                .that(fm.findFragmentById(R.id.content))
+                .isSameInstanceAs(fragmentReplacement)
+
+            // Assert that restored fragment has its saved state restored
+            assertThat(fragmentReplacement.savedState).isEqualTo("saved")
+            assertThat(fragmentReplacement.unsavedState).isEqualTo("unsaved")
+            assertThat(fragmentReplacement.viewModel).isSameInstanceAs(originalViewModel)
+            assertThat(fm.backStackEntryCount).isEqualTo(1)
         }
     }
 
diff --git a/fragment/fragment/src/main/java/androidx/fragment/app/BackStackRecord.java b/fragment/fragment/src/main/java/androidx/fragment/app/BackStackRecord.java
index e691044..70ec336 100644
--- a/fragment/fragment/src/main/java/androidx/fragment/app/BackStackRecord.java
+++ b/fragment/fragment/src/main/java/androidx/fragment/app/BackStackRecord.java
@@ -142,6 +142,17 @@
         mManager = manager;
     }
 
+    BackStackRecord(@NonNull BackStackRecord bse) {
+        super(bse.mManager.getFragmentFactory(), bse.mManager.getHost() != null
+                ? bse.mManager.getHost().getContext().getClassLoader()
+                : null, bse);
+        mManager = bse.mManager;
+        mCommitted = bse.mCommitted;
+        mIndex = bse.mIndex;
+        mBeingSaved = bse.mBeingSaved;
+    }
+
+
     @Override
     public int getId() {
         return mIndex;
@@ -281,15 +292,6 @@
         }
     }
 
-    void runOnExecuteRunnables() {
-        if (mExecuteRunnables != null) {
-            for (int i = 0; i < mExecuteRunnables.size(); i++) {
-                mExecuteRunnables.get(i).run();
-            }
-            mExecuteRunnables = null;
-        }
-    }
-
     public void runOnCommitRunnables() {
         if (mCommitRunnables != null) {
             for (int i = 0; i < mCommitRunnables.size(); i++) {
diff --git a/fragment/fragment/src/main/java/androidx/fragment/app/BackStackState.java b/fragment/fragment/src/main/java/androidx/fragment/app/BackStackState.java
index c102873..06e1df3 100644
--- a/fragment/fragment/src/main/java/androidx/fragment/app/BackStackState.java
+++ b/fragment/fragment/src/main/java/androidx/fragment/app/BackStackState.java
@@ -48,7 +48,14 @@
         // These will populate the transactions we instantiate.
         HashMap<String, Fragment> fragments = new HashMap<>(mFragments.size());
         for (String fWho : mFragments) {
-            // Retrieve any saved state, clearing it out for future calls
+            Fragment existingFragment = fm.getFragmentStore().findFragmentByWho(fWho);
+            if (existingFragment != null) {
+                // If the Fragment still exists, this means the saveBackStack()
+                // hasn't executed yet, so we can use the existing Fragment directly
+                fragments.put(existingFragment.mWho, existingFragment);
+                continue;
+            }
+            // Otherwise, retrieve any saved state, clearing it out for future calls
             FragmentState fragmentState = fm.getFragmentStore().setSavedState(fWho, null);
             if (fragmentState != null) {
                 Fragment fragment = fragmentState.instantiate(fm.getFragmentFactory(),
diff --git a/fragment/fragment/src/main/java/androidx/fragment/app/Fragment.java b/fragment/fragment/src/main/java/androidx/fragment/app/Fragment.java
index c33fffe..0a07f7c 100644
--- a/fragment/fragment/src/main/java/androidx/fragment/app/Fragment.java
+++ b/fragment/fragment/src/main/java/androidx/fragment/app/Fragment.java
@@ -289,7 +289,7 @@
     @Nullable FragmentViewLifecycleOwner mViewLifecycleOwner;
     MutableLiveData<LifecycleOwner> mViewLifecycleOwnerLiveData = new MutableLiveData<>();
 
-    private ViewModelProvider.Factory mDefaultFactory;
+    ViewModelProvider.Factory mDefaultFactory;
 
     SavedStateRegistryController mSavedStateRegistryController;
 
@@ -2939,7 +2939,7 @@
             @Nullable Bundle savedInstanceState) {
         mChildFragmentManager.noteStateNotSaved();
         mPerformedCreateView = true;
-        mViewLifecycleOwner = new FragmentViewLifecycleOwner(getViewModelStore());
+        mViewLifecycleOwner = new FragmentViewLifecycleOwner(this, getViewModelStore());
         mView = onCreateView(inflater, container, savedInstanceState);
         if (mView != null) {
             // Initialize the view lifecycle
diff --git a/fragment/fragment/src/main/java/androidx/fragment/app/FragmentContainerView.java b/fragment/fragment/src/main/java/androidx/fragment/app/FragmentContainerView.java
index 373a909..071cad3 100644
--- a/fragment/fragment/src/main/java/androidx/fragment/app/FragmentContainerView.java
+++ b/fragment/fragment/src/main/java/androidx/fragment/app/FragmentContainerView.java
@@ -399,4 +399,19 @@
             mDisappearingFragmentChildren.add(v);
         }
     }
+
+    /**
+     * This method grabs the {@link Fragment} whose view was most recently
+     * added to the container. This may used as an alternative to calling
+     * {@link FragmentManager#findFragmentById(int)} and passing in the
+     * {@link FragmentContainerView}'s id.
+     *
+     * @return The fragment if any exist, null otherwise.
+     */
+    @Nullable
+    @SuppressWarnings({"unchecked", "TypeParameterUnusedInFormals"}) // a ClassCastException is
+    // automatically thrown if the given type of F is wrong
+    public <F extends Fragment> F getFragment() {
+        return (F) FragmentManager.findFragmentManager(this).findFragmentById(this.getId());
+    }
 }
diff --git a/fragment/fragment/src/main/java/androidx/fragment/app/FragmentManager.java b/fragment/fragment/src/main/java/androidx/fragment/app/FragmentManager.java
index 85a3a37..1c7ab0e 100644
--- a/fragment/fragment/src/main/java/androidx/fragment/app/FragmentManager.java
+++ b/fragment/fragment/src/main/java/androidx/fragment/app/FragmentManager.java
@@ -1758,11 +1758,6 @@
         }
         executeOps(records, isRecordPop, startIndex, endIndex);
 
-        for (int recordNum = startIndex; recordNum < endIndex; recordNum++) {
-            final BackStackRecord record = records.get(recordNum);
-            record.runOnExecuteRunnables();
-        }
-
         // The last operation determines the overall direction, this ensures that operations
         // such as push, push, pop, push are correctly considered a push
         boolean isPop = isRecordPop.get(endIndex - 1);
@@ -1997,11 +1992,11 @@
         }
 
         List<BackStackRecord> backStackRecords = backStackState.instantiate(this);
+        boolean added = false;
         for (BackStackRecord record : backStackRecords) {
-            records.add(record);
-            isRecordPop.add(false);
+            added = record.generateOps(records, isRecordPop) || added;
         }
-        return true;
+        return added;
     }
 
     boolean saveBackStackState(@NonNull ArrayList<BackStackRecord> records,
@@ -2095,20 +2090,17 @@
         final BackStackState backStackState = new BackStackState(
                 fragments, backStackRecordStates);
         for (int i = mBackStack.size() - 1; i >= index; i--) {
-            final BackStackRecord record = mBackStack.remove(i);
+            BackStackRecord record = mBackStack.remove(i);
+
+            // Create a copy of the record to save
+            BackStackRecord copy = new BackStackRecord(record);
+            copy.collapseOps();
+            BackStackRecordState state = new BackStackRecordState(copy);
+            backStackRecordStates.set(i - index, state);
+
+            // And now mark the record as being saved to ensure that each
+            // fragment saves its state properly
             record.mBeingSaved = true;
-            // Get a callback when the BackStackRecord is actually finished
-            final int currentIndex = i;
-            record.addOnExecuteRunnable(new Runnable() {
-                @Override
-                public void run() {
-                    // First collapse the record to remove expanded ops and get it ready to save
-                    record.collapseOps();
-                    // Then save the state
-                    BackStackRecordState state = new BackStackRecordState(record);
-                    backStackRecordStates.set(currentIndex - index, state);
-                }
-            });
             records.add(record);
             isRecordPop.add(true);
         }
@@ -2381,7 +2373,9 @@
         ArrayList<String> savedResultKeys = fms.mResultKeys;
         if (savedResultKeys != null) {
             for (int i = 0; i < savedResultKeys.size(); i++) {
-                mResults.put(savedResultKeys.get(i), fms.mResults.get(i));
+                Bundle savedResult = fms.mResults.get(i);
+                savedResult.setClassLoader(mHost.getContext().getClassLoader());
+                mResults.put(savedResultKeys.get(i), savedResult);
             }
         }
         mLaunchedFragments = new ArrayDeque<>(fms.mLaunchedFragments);
diff --git a/fragment/fragment/src/main/java/androidx/fragment/app/FragmentTransaction.java b/fragment/fragment/src/main/java/androidx/fragment/app/FragmentTransaction.java
index 1ed008c..65a30e5 100644
--- a/fragment/fragment/src/main/java/androidx/fragment/app/FragmentTransaction.java
+++ b/fragment/fragment/src/main/java/androidx/fragment/app/FragmentTransaction.java
@@ -98,6 +98,18 @@
             this.mOldMaxState = fragment.mMaxState;
             this.mCurrentMaxState = state;
         }
+
+        Op(Op op) {
+            this.mCmd = op.mCmd;
+            this.mFragment = op.mFragment;
+            this.mFromExpandedOp = op.mFromExpandedOp;
+            this.mEnterAnim = op.mEnterAnim;
+            this.mExitAnim = op.mExitAnim;
+            this.mPopEnterAnim = op.mPopEnterAnim;
+            this.mPopExitAnim = op.mPopExitAnim;
+            this.mOldMaxState = op.mOldMaxState;
+            this.mCurrentMaxState = op.mCurrentMaxState;
+        }
     }
 
     private final FragmentFactory mFragmentFactory;
@@ -122,7 +134,6 @@
     ArrayList<String> mSharedElementTargetNames;
     boolean mReorderingAllowed = false;
 
-    ArrayList<Runnable> mExecuteRunnables;
     ArrayList<Runnable> mCommitRunnables;
 
     /**
@@ -141,6 +152,35 @@
         mClassLoader = classLoader;
     }
 
+    FragmentTransaction(@NonNull FragmentFactory fragmentFactory,
+            @Nullable ClassLoader classLoader, @NonNull FragmentTransaction ft) {
+        this(fragmentFactory, classLoader);
+        for (Op op : ft.mOps) {
+            mOps.add(new Op(op));
+        }
+        mEnterAnim = ft.mEnterAnim;
+        mExitAnim = ft.mExitAnim;
+        mPopEnterAnim = ft.mPopEnterAnim;
+        mPopExitAnim = ft.mPopExitAnim;
+        mTransition = ft.mTransition;
+        mAddToBackStack = ft.mAddToBackStack;
+        mAllowAddToBackStack = ft.mAllowAddToBackStack;
+        mName = ft.mName;
+        mBreadCrumbShortTitleRes = ft.mBreadCrumbShortTitleRes;
+        mBreadCrumbShortTitleText = ft.mBreadCrumbShortTitleText;
+        mBreadCrumbTitleRes = ft.mBreadCrumbTitleRes;
+        mBreadCrumbTitleText = ft.mBreadCrumbTitleText;
+        if (ft.mSharedElementSourceNames != null) {
+            mSharedElementSourceNames = new ArrayList<>();
+            mSharedElementSourceNames.addAll(ft.mSharedElementSourceNames);
+        }
+        if (ft.mSharedElementTargetNames != null) {
+            mSharedElementTargetNames = new ArrayList<>();
+            mSharedElementTargetNames.addAll(ft.mSharedElementTargetNames);
+        }
+        mReorderingAllowed = ft.mReorderingAllowed;
+    }
+
     void addOp(Op op) {
         mOps.add(op);
         op.mEnterAnim = mEnterAnim;
@@ -826,18 +866,6 @@
     }
 
     /**
-     * Add a runnable that is run immediately after the transaction is executed.
-     * This differs from the commit runnables in that it happens before any
-     * fragments move to their expected state.
-     */
-    void addOnExecuteRunnable(@NonNull Runnable runnable) {
-        if (mExecuteRunnables == null) {
-            mExecuteRunnables = new ArrayList<>();
-        }
-        mExecuteRunnables.add(runnable);
-    }
-
-    /**
      * Add a Runnable to this transaction that will be run after this transaction has
      * been committed. If fragment transactions are {@link #setReorderingAllowed(boolean) optimized}
      * this may be after other subsequent fragment operations have also taken place, or operations
diff --git a/fragment/fragment/src/main/java/androidx/fragment/app/FragmentViewLifecycleOwner.java b/fragment/fragment/src/main/java/androidx/fragment/app/FragmentViewLifecycleOwner.java
index 65f16a4..ef96a00 100644
--- a/fragment/fragment/src/main/java/androidx/fragment/app/FragmentViewLifecycleOwner.java
+++ b/fragment/fragment/src/main/java/androidx/fragment/app/FragmentViewLifecycleOwner.java
@@ -16,25 +16,36 @@
 
 package androidx.fragment.app;
 
+import android.app.Application;
+import android.content.Context;
+import android.content.ContextWrapper;
 import android.os.Bundle;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
+import androidx.lifecycle.HasDefaultViewModelProviderFactory;
 import androidx.lifecycle.Lifecycle;
 import androidx.lifecycle.LifecycleRegistry;
+import androidx.lifecycle.SavedStateViewModelFactory;
+import androidx.lifecycle.ViewModelProvider;
 import androidx.lifecycle.ViewModelStore;
 import androidx.lifecycle.ViewModelStoreOwner;
 import androidx.savedstate.SavedStateRegistry;
 import androidx.savedstate.SavedStateRegistryController;
 import androidx.savedstate.SavedStateRegistryOwner;
 
-class FragmentViewLifecycleOwner implements SavedStateRegistryOwner, ViewModelStoreOwner {
+class FragmentViewLifecycleOwner implements HasDefaultViewModelProviderFactory,
+        SavedStateRegistryOwner, ViewModelStoreOwner {
+    private final Fragment mFragment;
     private final ViewModelStore mViewModelStore;
 
+    private ViewModelProvider.Factory mDefaultFactory;
+
     private LifecycleRegistry mLifecycleRegistry = null;
     private SavedStateRegistryController mSavedStateRegistryController = null;
 
-    FragmentViewLifecycleOwner(@NonNull ViewModelStore viewModelStore) {
+    FragmentViewLifecycleOwner(@NonNull Fragment fragment, @NonNull ViewModelStore viewModelStore) {
+        mFragment = fragment;
         mViewModelStore = viewModelStore;
     }
 
@@ -77,6 +88,44 @@
         mLifecycleRegistry.handleLifecycleEvent(event);
     }
 
+    /**
+     * {@inheritDoc}
+     *
+     * <p>The {@link Fragment#getArguments() Fragment's arguments} when this is first called will
+     * be used as the defaults to any {@link androidx.lifecycle.SavedStateHandle} passed to a
+     * view model created using this factory.</p>
+     */
+    @NonNull
+    @Override
+    public ViewModelProvider.Factory getDefaultViewModelProviderFactory() {
+        ViewModelProvider.Factory currentFactory =
+                mFragment.getDefaultViewModelProviderFactory();
+
+        if (!currentFactory.equals(mFragment.mDefaultFactory)) {
+            mDefaultFactory = currentFactory;
+            return currentFactory;
+        }
+
+        if (mDefaultFactory == null) {
+            Application application = null;
+            Context appContext = mFragment.requireContext().getApplicationContext();
+            while (appContext instanceof ContextWrapper) {
+                if (appContext instanceof Application) {
+                    application = (Application) appContext;
+                    break;
+                }
+                appContext = ((ContextWrapper) appContext).getBaseContext();
+            }
+
+            mDefaultFactory = new SavedStateViewModelFactory(
+                    application,
+                    this,
+                    mFragment.getArguments());
+        }
+
+        return mDefaultFactory;
+    }
+
     @NonNull
     @Override
     public SavedStateRegistry getSavedStateRegistry() {
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index 4d9b6bc..42c5121 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -59,7 +59,7 @@
 dokkaGradlePlugin = { module = "org.jetbrains.dokka:dokka-android-gradle-plugin", version = "0.9.17-g014" }
 espressoContrib = { module = "androidx.test.espresso:espresso-contrib", version.ref = "espresso" }
 espressoCore = { module = "androidx.test.espresso:espresso-core", version.ref = "espresso" }
-espressoIdlingNet = { module = "androidx.test.espresso:espresso-idling-net", version.ref = "espresso" }
+espressoIdlingNet = { module = "androidx.test.espresso.idling:idling-net", version.ref = "espresso" }
 espressoIdlingResource = { module = "androidx.test.espresso:espresso-idling-resource", version.ref = "espresso" }
 espressoIntents = { module = "androidx.test.espresso:espresso-intents", version.ref = "espresso" }
 espressoWeb = { module = "androidx.test.espresso:espresso-web", version.ref = "espresso" }
diff --git a/health/health-services-client/OWNERS b/health/health-services-client/OWNERS
new file mode 100644
index 0000000..99e9acf
--- /dev/null
+++ b/health/health-services-client/OWNERS
@@ -0,0 +1,3 @@
+aakanksha@google.com
+jlannin@google.com
+smskelley@google.com
diff --git a/health/health-services-client/api/api_lint.ignore b/health/health-services-client/api/api_lint.ignore
new file mode 100644
index 0000000..1a6bb40
--- /dev/null
+++ b/health/health-services-client/api/api_lint.ignore
@@ -0,0 +1,9 @@
+// Baseline format: 1.0
+ExecutorRegistration: androidx.health.services.client.ExerciseClient#clearUpdateListener(androidx.health.services.client.ExerciseUpdateListener):
+    Registration methods should have overload that accepts delivery Executor: `clearUpdateListener`
+ExecutorRegistration: androidx.health.services.client.PassiveMonitoringClient#registerDataCallback(java.util.Set<androidx.health.services.client.data.DataType>, android.app.PendingIntent, androidx.health.services.client.PassiveMonitoringCallback):
+    Registration methods should have overload that accepts delivery Executor: `registerDataCallback`
+
+
+MissingGetterMatchingBuilder: androidx.health.services.client.data.ExerciseConfig.Builder#setAutoPauseAndResume(boolean):
+    androidx.health.services.client.data.ExerciseConfig does not declare a `isAutoPauseAndResume()` method matching method androidx.health.services.client.data.ExerciseConfig.Builder.setAutoPauseAndResume(boolean)
diff --git a/health/health-services-client/api/current.txt b/health/health-services-client/api/current.txt
new file mode 100644
index 0000000..6abe37c
--- /dev/null
+++ b/health/health-services-client/api/current.txt
@@ -0,0 +1,869 @@
+// Signature format: 4.0
+package androidx.health.services.client {
+
+  public interface ExerciseClient {
+    method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> addGoalToActiveExercise(androidx.health.services.client.data.ExerciseGoal exerciseGoal);
+    method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> clearUpdateListener(androidx.health.services.client.ExerciseUpdateListener listener);
+    method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> endExercise();
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.health.services.client.data.Capabilities> getCapabilities();
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.health.services.client.data.ExerciseInfo> getCurrentExerciseInfo();
+    method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> markLap();
+    method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> overrideAutoPauseAndResumeForActiveExercise(boolean enabled);
+    method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> pauseExercise();
+    method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> resumeExercise();
+    method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> setUpdateListener(androidx.health.services.client.ExerciseUpdateListener listener);
+    method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> setUpdateListener(androidx.health.services.client.ExerciseUpdateListener listener, java.util.concurrent.Executor executor);
+    method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> startExercise(androidx.health.services.client.data.ExerciseConfig configuration);
+    property public abstract com.google.common.util.concurrent.ListenableFuture<androidx.health.services.client.data.Capabilities> capabilities;
+    property public abstract com.google.common.util.concurrent.ListenableFuture<androidx.health.services.client.data.ExerciseInfo> currentExerciseInfo;
+  }
+
+  public interface ExerciseUpdateListener {
+    method public void onExerciseUpdate(androidx.health.services.client.data.ExerciseUpdate update);
+    method public void onLapSummary(androidx.health.services.client.data.ExerciseLapSummary lapSummary);
+  }
+
+  public final class HealthServices {
+    method public static androidx.health.services.client.HealthServicesClient getClient(android.content.Context context);
+    field public static final androidx.health.services.client.HealthServices INSTANCE;
+  }
+
+  public interface HealthServicesClient {
+    method public androidx.health.services.client.ExerciseClient getExerciseClient();
+    method public androidx.health.services.client.MeasureClient getMeasureClient();
+    method public androidx.health.services.client.PassiveMonitoringClient getPassiveMonitoringClient();
+    property public abstract androidx.health.services.client.ExerciseClient exerciseClient;
+    property public abstract androidx.health.services.client.MeasureClient measureClient;
+    property public abstract androidx.health.services.client.PassiveMonitoringClient passiveMonitoringClient;
+  }
+
+  public interface MeasureCallback {
+    method public void onAvailabilityChanged(androidx.health.services.client.data.DataType dataType, androidx.health.services.client.data.Availability availability);
+    method public void onData(java.util.List<androidx.health.services.client.data.DataPoint> data);
+  }
+
+  public interface MeasureClient {
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.health.services.client.data.MeasureCapabilities> getCapabilities();
+    method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> registerCallback(androidx.health.services.client.data.DataType dataType, androidx.health.services.client.MeasureCallback callback);
+    method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> registerCallback(androidx.health.services.client.data.DataType dataType, androidx.health.services.client.MeasureCallback callback, java.util.concurrent.Executor executor);
+    method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> unregisterCallback(androidx.health.services.client.data.DataType dataType, androidx.health.services.client.MeasureCallback callback);
+    property public abstract com.google.common.util.concurrent.ListenableFuture<androidx.health.services.client.data.MeasureCapabilities> capabilities;
+  }
+
+  public interface PassiveMonitoringCallback {
+    method public void onPassiveActivityState(androidx.health.services.client.data.PassiveActivityState state);
+  }
+
+  public interface PassiveMonitoringClient {
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.health.services.client.data.PassiveMonitoringCapabilities> getCapabilities();
+    method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> registerDataCallback(java.util.Set<androidx.health.services.client.data.DataType> dataTypes, android.app.PendingIntent callbackIntent);
+    method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> registerDataCallback(java.util.Set<androidx.health.services.client.data.DataType> dataTypes, android.app.PendingIntent callbackIntent, androidx.health.services.client.PassiveMonitoringCallback callback);
+    method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> registerEventCallback(androidx.health.services.client.data.event.Event event, android.app.PendingIntent callbackIntent);
+    method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> unregisterDataCallback();
+    method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> unregisterEventCallback(androidx.health.services.client.data.event.Event event);
+    property public abstract com.google.common.util.concurrent.ListenableFuture<androidx.health.services.client.data.PassiveMonitoringCapabilities> capabilities;
+  }
+
+}
+
+package androidx.health.services.client.data {
+
+  public final class AchievedExerciseGoal implements android.os.Parcelable {
+    ctor public AchievedExerciseGoal(androidx.health.services.client.data.ExerciseGoal goal);
+    method public androidx.health.services.client.data.ExerciseGoal component1();
+    method public androidx.health.services.client.data.AchievedExerciseGoal copy(androidx.health.services.client.data.ExerciseGoal goal);
+    method public int describeContents();
+    method public androidx.health.services.client.data.ExerciseGoal getGoal();
+    method public void writeToParcel(android.os.Parcel dest, int flags);
+    property public final androidx.health.services.client.data.ExerciseGoal goal;
+    field public static final android.os.Parcelable.Creator<androidx.health.services.client.data.AchievedExerciseGoal> CREATOR;
+    field public static final androidx.health.services.client.data.AchievedExerciseGoal.Companion Companion;
+  }
+
+  public static final class AchievedExerciseGoal.Companion {
+  }
+
+  public final class AutoExerciseConfig implements android.os.Parcelable {
+    ctor public AutoExerciseConfig(optional java.util.Set<? extends androidx.health.services.client.data.ExerciseType> exercisesToDetect, optional android.app.PendingIntent? launchIntent, optional android.os.Bundle exerciseParams);
+    ctor public AutoExerciseConfig(optional java.util.Set<? extends androidx.health.services.client.data.ExerciseType> exercisesToDetect, optional android.app.PendingIntent? launchIntent);
+    ctor public AutoExerciseConfig(optional java.util.Set<? extends androidx.health.services.client.data.ExerciseType> exercisesToDetect);
+    method public java.util.Set<androidx.health.services.client.data.ExerciseType> component1();
+    method public android.app.PendingIntent? component2();
+    method public android.os.Bundle component3();
+    method public androidx.health.services.client.data.AutoExerciseConfig copy(java.util.Set<? extends androidx.health.services.client.data.ExerciseType> exercisesToDetect, android.app.PendingIntent? launchIntent, android.os.Bundle exerciseParams);
+    method public int describeContents();
+    method public android.os.Bundle getExerciseParams();
+    method public java.util.Set<androidx.health.services.client.data.ExerciseType> getExercisesToDetect();
+    method public android.app.PendingIntent? getLaunchIntent();
+    method public void writeToParcel(android.os.Parcel dest, int flags);
+    property public final android.os.Bundle exerciseParams;
+    property public final java.util.Set<androidx.health.services.client.data.ExerciseType> exercisesToDetect;
+    property public final android.app.PendingIntent? launchIntent;
+    field public static final android.os.Parcelable.Creator<androidx.health.services.client.data.AutoExerciseConfig> CREATOR;
+    field public static final androidx.health.services.client.data.AutoExerciseConfig.Companion Companion;
+  }
+
+  public static final class AutoExerciseConfig.Companion {
+  }
+
+  public enum Availability {
+    method public static final androidx.health.services.client.data.Availability? fromId(int id);
+    method public final int getId();
+    property public final int id;
+    enum_constant public static final androidx.health.services.client.data.Availability ACQUIRING;
+    enum_constant public static final androidx.health.services.client.data.Availability AVAILABLE;
+    enum_constant public static final androidx.health.services.client.data.Availability UNAVAILABLE;
+    enum_constant public static final androidx.health.services.client.data.Availability UNKNOWN;
+    field public static final androidx.health.services.client.data.Availability.Companion Companion;
+  }
+
+  public static final class Availability.Companion {
+    method public androidx.health.services.client.data.Availability? fromId(int id);
+  }
+
+  public final class Capabilities implements android.os.Parcelable {
+    ctor public Capabilities(java.util.Map<androidx.health.services.client.data.ExerciseType,androidx.health.services.client.data.ExerciseCapabilities> exerciseTypeToExerciseCapabilities);
+    method public java.util.Map<androidx.health.services.client.data.ExerciseType,androidx.health.services.client.data.ExerciseCapabilities> component1();
+    method public androidx.health.services.client.data.Capabilities copy(java.util.Map<androidx.health.services.client.data.ExerciseType,androidx.health.services.client.data.ExerciseCapabilities> exerciseTypeToExerciseCapabilities);
+    method public int describeContents();
+    method public java.util.Set<androidx.health.services.client.data.ExerciseType> getAutoPauseAndResumeEnabledExercises();
+    method public androidx.health.services.client.data.ExerciseCapabilities getExerciseCapabilities(androidx.health.services.client.data.ExerciseType exercise);
+    method public java.util.Map<androidx.health.services.client.data.ExerciseType,androidx.health.services.client.data.ExerciseCapabilities> getExerciseTypeToExerciseCapabilities();
+    method public java.util.Set<androidx.health.services.client.data.ExerciseType> getSupportedExerciseTypes();
+    method public void writeToParcel(android.os.Parcel dest, int flags);
+    property public final java.util.Set<androidx.health.services.client.data.ExerciseType> autoPauseAndResumeEnabledExercises;
+    property public final java.util.Map<androidx.health.services.client.data.ExerciseType,androidx.health.services.client.data.ExerciseCapabilities> exerciseTypeToExerciseCapabilities;
+    property public final java.util.Set<androidx.health.services.client.data.ExerciseType> supportedExerciseTypes;
+    field public static final android.os.Parcelable.Creator<androidx.health.services.client.data.Capabilities> CREATOR;
+    field public static final androidx.health.services.client.data.Capabilities.Companion Companion;
+  }
+
+  public static final class Capabilities.Companion {
+  }
+
+  public enum ComparisonType {
+    method public static final androidx.health.services.client.data.ComparisonType? fromId(int id);
+    method public final int getId();
+    property public final int id;
+    enum_constant public static final androidx.health.services.client.data.ComparisonType GREATER_THAN;
+    enum_constant public static final androidx.health.services.client.data.ComparisonType GREATER_THAN_OR_EQUAL;
+    enum_constant public static final androidx.health.services.client.data.ComparisonType LESS_THAN;
+    enum_constant public static final androidx.health.services.client.data.ComparisonType LESS_THAN_OR_EQUAL;
+    field public static final androidx.health.services.client.data.ComparisonType.Companion Companion;
+  }
+
+  public static final class ComparisonType.Companion {
+    method public androidx.health.services.client.data.ComparisonType? fromId(int id);
+  }
+
+  public final class DataPoint implements android.os.Parcelable {
+    method public androidx.health.services.client.data.DataType component1();
+    method public androidx.health.services.client.data.Value component2();
+    method public java.time.Duration component3();
+    method public java.time.Duration component4();
+    method public android.os.Bundle component5();
+    method public androidx.health.services.client.data.DataPoint copy(androidx.health.services.client.data.DataType dataType, androidx.health.services.client.data.Value value, java.time.Duration startDurationFromBoot, java.time.Duration endDurationFromBoot, android.os.Bundle metadata);
+    method public static androidx.health.services.client.data.DataPoint createInterval(androidx.health.services.client.data.DataType dataType, androidx.health.services.client.data.Value value, java.time.Duration startDurationFromBoot, java.time.Duration endDurationFromBoot, optional android.os.Bundle metadata);
+    method public static androidx.health.services.client.data.DataPoint createInterval(androidx.health.services.client.data.DataType dataType, androidx.health.services.client.data.Value value, java.time.Duration startDurationFromBoot, java.time.Duration endDurationFromBoot);
+    method public static androidx.health.services.client.data.DataPoint createSample(androidx.health.services.client.data.DataType dataType, androidx.health.services.client.data.Value value, java.time.Duration durationFromBoot, optional android.os.Bundle metadata);
+    method public static androidx.health.services.client.data.DataPoint createSample(androidx.health.services.client.data.DataType dataType, androidx.health.services.client.data.Value value, java.time.Duration durationFromBoot);
+    method public int describeContents();
+    method public androidx.health.services.client.data.DataType getDataType();
+    method public java.time.Duration getEndDurationFromBoot();
+    method public java.time.Instant getEndInstant(java.time.Instant bootInstant);
+    method public android.os.Bundle getMetadata();
+    method public java.time.Duration getStartDurationFromBoot();
+    method public java.time.Instant getStartInstant(java.time.Instant bootInstant);
+    method public androidx.health.services.client.data.Value getValue();
+    method public void writeToParcel(android.os.Parcel dest, int flags);
+    property public final androidx.health.services.client.data.DataType dataType;
+    property public final java.time.Duration endDurationFromBoot;
+    property public final android.os.Bundle metadata;
+    property public final java.time.Duration startDurationFromBoot;
+    property public final androidx.health.services.client.data.Value value;
+    field public static final android.os.Parcelable.Creator<androidx.health.services.client.data.DataPoint> CREATOR;
+    field public static final androidx.health.services.client.data.DataPoint.Companion Companion;
+  }
+
+  public static final class DataPoint.Companion {
+    method public androidx.health.services.client.data.DataPoint createInterval(androidx.health.services.client.data.DataType dataType, androidx.health.services.client.data.Value value, java.time.Duration startDurationFromBoot, java.time.Duration endDurationFromBoot, optional android.os.Bundle metadata);
+    method public androidx.health.services.client.data.DataPoint createInterval(androidx.health.services.client.data.DataType dataType, androidx.health.services.client.data.Value value, java.time.Duration startDurationFromBoot, java.time.Duration endDurationFromBoot);
+    method public androidx.health.services.client.data.DataPoint createSample(androidx.health.services.client.data.DataType dataType, androidx.health.services.client.data.Value value, java.time.Duration durationFromBoot, optional android.os.Bundle metadata);
+    method public androidx.health.services.client.data.DataPoint createSample(androidx.health.services.client.data.DataType dataType, androidx.health.services.client.data.Value value, java.time.Duration durationFromBoot);
+  }
+
+  @Keep public final class DataPoints {
+    method public static androidx.health.services.client.data.DataPoint aggregateCalories(double kcalories, java.time.Duration startDurationFromBoot, java.time.Duration endDurationFromBoot);
+    method public static androidx.health.services.client.data.DataPoint aggregateDistance(double distance, java.time.Duration startDurationFromBoot, java.time.Duration endDurationFromBoot);
+    method public static androidx.health.services.client.data.DataPoint aggregateSteps(long steps, java.time.Duration startDurationFromBoot, java.time.Duration endDurationFromBoot);
+    method public static androidx.health.services.client.data.DataPoint aggregateSwimmingStrokes(long swimmingStrokes, java.time.Duration startDurationFromBoot, java.time.Duration endDurationFromBoot);
+    method public static androidx.health.services.client.data.DataPoint altitude(double meters, java.time.Duration durationFromBoot);
+    method public static androidx.health.services.client.data.DataPoint averagePace(double millisPerKm, java.time.Duration durationFromBoot);
+    method public static androidx.health.services.client.data.DataPoint averageSpeed(double metersPerSecond, java.time.Duration durationFromBoot);
+    method public static androidx.health.services.client.data.DataPoint calories(double kcalories, java.time.Duration startDurationFromBoot, java.time.Duration endDurationFromBoot);
+    method public static androidx.health.services.client.data.DataPoint distance(double meters, java.time.Duration startDurationFromBoot, java.time.Duration endDurationFromBoot);
+    method public static androidx.health.services.client.data.DataPoint elevation(double meters, java.time.Duration startDurationFromBoot, java.time.Duration endDurationFromBoot);
+    method public static androidx.health.services.client.data.DataPoint floors(double floors, java.time.Duration startDurationFromBoot, java.time.Duration endDurationFromBoot);
+    method @Keep public static java.util.List<androidx.health.services.client.data.DataPoint> getDataPoints(android.content.Intent intent);
+    method public static boolean getPermissionsGranted(android.content.Intent intent);
+    method public static androidx.health.services.client.data.DataPoint heartRate(double bpm, java.time.Duration durationFromBoot);
+    method public static androidx.health.services.client.data.DataPoint location(double latitude, double longitude, java.time.Duration durationFromBoot);
+    method public static androidx.health.services.client.data.DataPoint location(double latitude, double longitude, double altitude, java.time.Duration durationFromBoot);
+    method public static androidx.health.services.client.data.DataPoint maxSpeed(double metersPerSecond, java.time.Duration durationFromBoot);
+    method public static androidx.health.services.client.data.DataPoint pace(double millisPerKm, java.time.Duration durationFromBoot);
+    method public static void putDataPoints(android.content.Intent intent, java.util.Collection<androidx.health.services.client.data.DataPoint> dataPoints);
+    method public static void putPermissionsGranted(android.content.Intent intent, boolean granted);
+    method public static androidx.health.services.client.data.DataPoint speed(double metersPerSecond, java.time.Duration durationFromBoot);
+    method public static androidx.health.services.client.data.DataPoint spo2(double percent, java.time.Duration durationFromBoot);
+    method public static androidx.health.services.client.data.DataPoint steps(long steps, java.time.Duration startDurationFromBoot, java.time.Duration endDurationFromBoot);
+    method public static androidx.health.services.client.data.DataPoint stepsPerMinute(long stepsPerMinute, java.time.Duration startDurationFromBoot);
+    method public static androidx.health.services.client.data.DataPoint swimmingStrokes(long strokes, java.time.Duration startDurationFromBoot, java.time.Duration endDurationFromBoot);
+    field public static final androidx.health.services.client.data.DataPoints INSTANCE;
+    field public static final int LOCATION_DATA_POINT_ALTITUDE_INDEX = 2; // 0x2
+    field public static final int LOCATION_DATA_POINT_LATITUDE_INDEX = 0; // 0x0
+    field public static final int LOCATION_DATA_POINT_LONGITUDE_INDEX = 1; // 0x1
+  }
+
+  public final class DataType implements android.os.Parcelable {
+    ctor public DataType(String name, androidx.health.services.client.data.DataType.TimeType timeType, int format);
+    method public String component1();
+    method public androidx.health.services.client.data.DataType.TimeType component2();
+    method public int component3();
+    method public androidx.health.services.client.data.DataType copy(String name, androidx.health.services.client.data.DataType.TimeType timeType, int format);
+    method public int describeContents();
+    method public int getFormat();
+    method public String getName();
+    method public androidx.health.services.client.data.DataType.TimeType getTimeType();
+    method public void writeToParcel(android.os.Parcel dest, int flags);
+    property public final int format;
+    property public final String name;
+    property public final androidx.health.services.client.data.DataType.TimeType timeType;
+    field public static final androidx.health.services.client.data.DataType ABSOLUTE_ELEVATION;
+    field public static final androidx.health.services.client.data.DataType ACTIVE_EXERCISE_DURATION;
+    field public static final androidx.health.services.client.data.DataType AGGREGATE_CALORIES_EXPENDED;
+    field public static final androidx.health.services.client.data.DataType AGGREGATE_DECLINE_DISTANCE;
+    field public static final androidx.health.services.client.data.DataType AGGREGATE_DECLINE_TIME;
+    field public static final androidx.health.services.client.data.DataType AGGREGATE_DISTANCE;
+    field public static final androidx.health.services.client.data.DataType AGGREGATE_ELEVATION;
+    field public static final androidx.health.services.client.data.DataType AGGREGATE_FLAT_DISTANCE;
+    field public static final androidx.health.services.client.data.DataType AGGREGATE_FLAT_TIME;
+    field public static final androidx.health.services.client.data.DataType AGGREGATE_FLOORS;
+    field public static final androidx.health.services.client.data.DataType AGGREGATE_INCLINE_DISTANCE;
+    field public static final androidx.health.services.client.data.DataType AGGREGATE_INCLINE_TIME;
+    field public static final androidx.health.services.client.data.DataType AGGREGATE_RUNNING_STEP_COUNT;
+    field public static final androidx.health.services.client.data.DataType AGGREGATE_STEP_COUNT;
+    field public static final androidx.health.services.client.data.DataType AGGREGATE_SWIMMING_STROKE_COUNT;
+    field public static final androidx.health.services.client.data.DataType AGGREGATE_WALKING_STEP_COUNT;
+    field public static final androidx.health.services.client.data.DataType ALTITUDE;
+    field public static final androidx.health.services.client.data.DataType AVERAGE_HEART_RATE_BPM;
+    field public static final androidx.health.services.client.data.DataType AVERAGE_PACE;
+    field public static final androidx.health.services.client.data.DataType AVERAGE_SPEED;
+    field public static final android.os.Parcelable.Creator<androidx.health.services.client.data.DataType> CREATOR;
+    field public static final androidx.health.services.client.data.DataType.Companion Companion;
+    field public static final androidx.health.services.client.data.DataType DECLINE_DISTANCE;
+    field public static final androidx.health.services.client.data.DataType DECLINE_TIME;
+    field public static final androidx.health.services.client.data.DataType DISTANCE;
+    field public static final androidx.health.services.client.data.DataType ELEVATION;
+    field public static final androidx.health.services.client.data.DataType FLAT_DISTANCE;
+    field public static final androidx.health.services.client.data.DataType FLAT_TIME;
+    field public static final androidx.health.services.client.data.DataType FLOORS;
+    field public static final androidx.health.services.client.data.DataType HEART_RATE_BPM;
+    field public static final androidx.health.services.client.data.DataType INCLINE_DISTANCE;
+    field public static final androidx.health.services.client.data.DataType INCLINE_TIME;
+    field public static final androidx.health.services.client.data.DataType LOCATION;
+    field public static final androidx.health.services.client.data.DataType MAX_ALTITUDE;
+    field public static final androidx.health.services.client.data.DataType MAX_HEART_RATE_BPM;
+    field public static final androidx.health.services.client.data.DataType MAX_PACE;
+    field public static final androidx.health.services.client.data.DataType MAX_SPEED;
+    field public static final androidx.health.services.client.data.DataType MIN_ALTITUDE;
+    field public static final androidx.health.services.client.data.DataType PACE;
+    field public static final androidx.health.services.client.data.DataType REP_COUNT;
+    field public static final androidx.health.services.client.data.DataType RESTING_EXERCISE_DURATION;
+    field public static final androidx.health.services.client.data.DataType RUNNING_STEPS;
+    field public static final androidx.health.services.client.data.DataType SPEED;
+    field public static final androidx.health.services.client.data.DataType SPO2;
+    field public static final androidx.health.services.client.data.DataType STEPS;
+    field public static final androidx.health.services.client.data.DataType STEPS_PER_MINUTE;
+    field public static final androidx.health.services.client.data.DataType SWIMMING_LAP_COUNT;
+    field public static final androidx.health.services.client.data.DataType SWIMMING_STROKES;
+    field public static final androidx.health.services.client.data.DataType TOTAL_CALORIES;
+    field public static final androidx.health.services.client.data.DataType VO2;
+    field public static final androidx.health.services.client.data.DataType VO2_MAX;
+    field public static final androidx.health.services.client.data.DataType WALKING_STEPS;
+  }
+
+  public static final class DataType.Companion {
+  }
+
+  public enum DataType.TimeType {
+    enum_constant public static final androidx.health.services.client.data.DataType.TimeType INTERVAL;
+    enum_constant public static final androidx.health.services.client.data.DataType.TimeType SAMPLE;
+  }
+
+  public final class DataTypeCondition implements android.os.Parcelable {
+    ctor public DataTypeCondition(androidx.health.services.client.data.DataType dataType, androidx.health.services.client.data.Value threshold, androidx.health.services.client.data.ComparisonType comparisonType);
+    method public androidx.health.services.client.data.DataType component1();
+    method public androidx.health.services.client.data.Value component2();
+    method public androidx.health.services.client.data.ComparisonType component3();
+    method public androidx.health.services.client.data.DataTypeCondition copy(androidx.health.services.client.data.DataType dataType, androidx.health.services.client.data.Value threshold, androidx.health.services.client.data.ComparisonType comparisonType);
+    method public int describeContents();
+    method public androidx.health.services.client.data.ComparisonType getComparisonType();
+    method public androidx.health.services.client.data.DataType getDataType();
+    method public androidx.health.services.client.data.Value getThreshold();
+    method public boolean isSatisfied(androidx.health.services.client.data.DataPoint dataPoint);
+    method public boolean isThresholdSatisfied(androidx.health.services.client.data.Value value);
+    method public void writeToParcel(android.os.Parcel dest, int flags);
+    property public final androidx.health.services.client.data.ComparisonType comparisonType;
+    property public final androidx.health.services.client.data.DataType dataType;
+    property public final androidx.health.services.client.data.Value threshold;
+    field public static final android.os.Parcelable.Creator<androidx.health.services.client.data.DataTypeCondition> CREATOR;
+    field public static final androidx.health.services.client.data.DataTypeCondition.Companion Companion;
+  }
+
+  public static final class DataTypeCondition.Companion {
+  }
+
+  public final class DataTypes {
+    method public static androidx.health.services.client.data.DataType? getAggregateTypeFromRawType(androidx.health.services.client.data.DataType rawType);
+    method public static java.util.Set<androidx.health.services.client.data.DataType> getAggregatedDataTypesFromRawType(androidx.health.services.client.data.DataType rawType);
+    method public static androidx.health.services.client.data.DataType? getAverageTypeFromRawType(androidx.health.services.client.data.DataType rawType);
+    method public static androidx.health.services.client.data.DataType? getMaxTypeFromRawType(androidx.health.services.client.data.DataType rawType);
+    method public static androidx.health.services.client.data.DataType? getRawTypeFromAggregateType(androidx.health.services.client.data.DataType aggregateType);
+    method public static androidx.health.services.client.data.DataType? getRawTypeFromAverageType(androidx.health.services.client.data.DataType averageType);
+    method public static androidx.health.services.client.data.DataType? getRawTypeFromMaxType(androidx.health.services.client.data.DataType maxType);
+    method public static boolean isAggregateDataType(androidx.health.services.client.data.DataType dataType);
+    method public static boolean isRawType(androidx.health.services.client.data.DataType dataType);
+    method public static boolean isStatisticalAverageDataType(androidx.health.services.client.data.DataType dataType);
+    method public static boolean isStatisticalMaxDataType(androidx.health.services.client.data.DataType dataType);
+    field public static final androidx.health.services.client.data.DataTypes INSTANCE;
+  }
+
+  public final class ExerciseCapabilities implements android.os.Parcelable {
+    ctor public ExerciseCapabilities(java.util.Set<androidx.health.services.client.data.DataType> supportedDataTypes, java.util.Map<androidx.health.services.client.data.DataType,? extends java.util.Set<? extends androidx.health.services.client.data.ComparisonType>> supportedGoals, java.util.Map<androidx.health.services.client.data.DataType,? extends java.util.Set<? extends androidx.health.services.client.data.ComparisonType>> supportedMilestones, boolean supportsAutoPauseAndResume, boolean supportsLaps);
+    method public java.util.Set<androidx.health.services.client.data.DataType> component1();
+    method public java.util.Map<androidx.health.services.client.data.DataType,java.util.Set<androidx.health.services.client.data.ComparisonType>> component2();
+    method public java.util.Map<androidx.health.services.client.data.DataType,java.util.Set<androidx.health.services.client.data.ComparisonType>> component3();
+    method public boolean component4();
+    method public boolean component5();
+    method public androidx.health.services.client.data.ExerciseCapabilities copy(java.util.Set<androidx.health.services.client.data.DataType> supportedDataTypes, java.util.Map<androidx.health.services.client.data.DataType,? extends java.util.Set<? extends androidx.health.services.client.data.ComparisonType>> supportedGoals, java.util.Map<androidx.health.services.client.data.DataType,? extends java.util.Set<? extends androidx.health.services.client.data.ComparisonType>> supportedMilestones, boolean supportsAutoPauseAndResume, boolean supportsLaps);
+    method public int describeContents();
+    method public java.util.Set<androidx.health.services.client.data.DataType> getSupportedDataTypes();
+    method public java.util.Map<androidx.health.services.client.data.DataType,java.util.Set<androidx.health.services.client.data.ComparisonType>> getSupportedGoals();
+    method public java.util.Map<androidx.health.services.client.data.DataType,java.util.Set<androidx.health.services.client.data.ComparisonType>> getSupportedMilestones();
+    method public boolean getSupportsAutoPauseAndResume();
+    method public boolean getSupportsLaps();
+    method public void writeToParcel(android.os.Parcel dest, int flags);
+    property public final java.util.Set<androidx.health.services.client.data.DataType> supportedDataTypes;
+    property public final java.util.Map<androidx.health.services.client.data.DataType,java.util.Set<androidx.health.services.client.data.ComparisonType>> supportedGoals;
+    property public final java.util.Map<androidx.health.services.client.data.DataType,java.util.Set<androidx.health.services.client.data.ComparisonType>> supportedMilestones;
+    property public final boolean supportsAutoPauseAndResume;
+    property public final boolean supportsLaps;
+    field public static final android.os.Parcelable.Creator<androidx.health.services.client.data.ExerciseCapabilities> CREATOR;
+    field public static final androidx.health.services.client.data.ExerciseCapabilities.Companion Companion;
+  }
+
+  public static final class ExerciseCapabilities.Companion {
+  }
+
+  public final class ExerciseConfig implements android.os.Parcelable {
+    ctor protected ExerciseConfig(androidx.health.services.client.data.ExerciseType exerciseType, java.util.Set<androidx.health.services.client.data.DataType> dataTypes, boolean autoPauseAndResume, java.util.List<androidx.health.services.client.data.ExerciseGoal> exerciseGoals, android.os.Bundle exerciseParams);
+    method public static androidx.health.services.client.data.ExerciseConfig.Builder builder();
+    method public androidx.health.services.client.data.ExerciseType component1();
+    method public java.util.Set<androidx.health.services.client.data.DataType> component2();
+    method public boolean component3();
+    method public java.util.List<androidx.health.services.client.data.ExerciseGoal> component4();
+    method public android.os.Bundle component5();
+    method public androidx.health.services.client.data.ExerciseConfig copy(androidx.health.services.client.data.ExerciseType exerciseType, java.util.Set<androidx.health.services.client.data.DataType> dataTypes, boolean autoPauseAndResume, java.util.List<androidx.health.services.client.data.ExerciseGoal> exerciseGoals, android.os.Bundle exerciseParams);
+    method public int describeContents();
+    method public boolean getAutoPauseAndResume();
+    method public java.util.Set<androidx.health.services.client.data.DataType> getDataTypes();
+    method public java.util.List<androidx.health.services.client.data.ExerciseGoal> getExerciseGoals();
+    method public android.os.Bundle getExerciseParams();
+    method public androidx.health.services.client.data.ExerciseType getExerciseType();
+    method public void writeToParcel(android.os.Parcel dest, int flags);
+    property public final boolean autoPauseAndResume;
+    property public final java.util.Set<androidx.health.services.client.data.DataType> dataTypes;
+    property public final java.util.List<androidx.health.services.client.data.ExerciseGoal> exerciseGoals;
+    property public final android.os.Bundle exerciseParams;
+    property public final androidx.health.services.client.data.ExerciseType exerciseType;
+    field public static final android.os.Parcelable.Creator<androidx.health.services.client.data.ExerciseConfig> CREATOR;
+    field public static final androidx.health.services.client.data.ExerciseConfig.Companion Companion;
+  }
+
+  public static final class ExerciseConfig.Builder {
+    ctor public ExerciseConfig.Builder();
+    method public androidx.health.services.client.data.ExerciseConfig build();
+    method public androidx.health.services.client.data.ExerciseConfig.Builder setAutoPauseAndResume(boolean autoPauseAndResume);
+    method public androidx.health.services.client.data.ExerciseConfig.Builder setDataTypes(java.util.Set<androidx.health.services.client.data.DataType> dataTypes);
+    method public androidx.health.services.client.data.ExerciseConfig.Builder setExerciseGoals(java.util.List<androidx.health.services.client.data.ExerciseGoal> exerciseGoals);
+    method public androidx.health.services.client.data.ExerciseConfig.Builder setExerciseParams(android.os.Bundle exerciseParams);
+    method public androidx.health.services.client.data.ExerciseConfig.Builder setExerciseType(androidx.health.services.client.data.ExerciseType exerciseType);
+  }
+
+  public static final class ExerciseConfig.Companion {
+    method public androidx.health.services.client.data.ExerciseConfig.Builder builder();
+  }
+
+  public final class ExerciseGoal implements android.os.Parcelable {
+    ctor protected ExerciseGoal(androidx.health.services.client.data.ExerciseGoalType exerciseGoalType, androidx.health.services.client.data.DataTypeCondition dataTypeCondition, optional androidx.health.services.client.data.Value? period);
+    method public androidx.health.services.client.data.ExerciseGoalType component1();
+    method public androidx.health.services.client.data.DataTypeCondition component2();
+    method public androidx.health.services.client.data.Value? component3();
+    method public androidx.health.services.client.data.ExerciseGoal copy(androidx.health.services.client.data.ExerciseGoalType exerciseGoalType, androidx.health.services.client.data.DataTypeCondition dataTypeCondition, androidx.health.services.client.data.Value? period);
+    method public static androidx.health.services.client.data.ExerciseGoal createMilestone(androidx.health.services.client.data.DataTypeCondition condition, androidx.health.services.client.data.Value period);
+    method public static androidx.health.services.client.data.ExerciseGoal createMilestoneGoalWithUpdatedThreshold(androidx.health.services.client.data.ExerciseGoal goal, androidx.health.services.client.data.Value newThreshold);
+    method public static androidx.health.services.client.data.ExerciseGoal createOneTimeGoal(androidx.health.services.client.data.DataTypeCondition condition);
+    method public int describeContents();
+    method public androidx.health.services.client.data.DataTypeCondition getDataTypeCondition();
+    method public androidx.health.services.client.data.ExerciseGoalType getExerciseGoalType();
+    method public androidx.health.services.client.data.Value? getPeriod();
+    method public void writeToParcel(android.os.Parcel dest, int flags);
+    property public final androidx.health.services.client.data.DataTypeCondition dataTypeCondition;
+    property public final androidx.health.services.client.data.ExerciseGoalType exerciseGoalType;
+    property public final androidx.health.services.client.data.Value? period;
+    field public static final android.os.Parcelable.Creator<androidx.health.services.client.data.ExerciseGoal> CREATOR;
+    field public static final androidx.health.services.client.data.ExerciseGoal.Companion Companion;
+  }
+
+  public static final class ExerciseGoal.Companion {
+    method public androidx.health.services.client.data.ExerciseGoal createMilestone(androidx.health.services.client.data.DataTypeCondition condition, androidx.health.services.client.data.Value period);
+    method public androidx.health.services.client.data.ExerciseGoal createMilestoneGoalWithUpdatedThreshold(androidx.health.services.client.data.ExerciseGoal goal, androidx.health.services.client.data.Value newThreshold);
+    method public androidx.health.services.client.data.ExerciseGoal createOneTimeGoal(androidx.health.services.client.data.DataTypeCondition condition);
+  }
+
+  public enum ExerciseGoalType {
+    method public static final androidx.health.services.client.data.ExerciseGoalType? fromId(int id);
+    method public final int getId();
+    property public final int id;
+    enum_constant public static final androidx.health.services.client.data.ExerciseGoalType MILESTONE;
+    enum_constant public static final androidx.health.services.client.data.ExerciseGoalType ONE_TIME_GOAL;
+    field public static final androidx.health.services.client.data.ExerciseGoalType.Companion Companion;
+  }
+
+  public static final class ExerciseGoalType.Companion {
+    method public androidx.health.services.client.data.ExerciseGoalType? fromId(int id);
+  }
+
+  public final class ExerciseInfo implements android.os.Parcelable {
+    ctor public ExerciseInfo(androidx.health.services.client.data.ExerciseTrackedStatus exerciseTrackedStatus, androidx.health.services.client.data.ExerciseType exerciseType);
+    method public androidx.health.services.client.data.ExerciseTrackedStatus component1();
+    method public androidx.health.services.client.data.ExerciseType component2();
+    method public androidx.health.services.client.data.ExerciseInfo copy(androidx.health.services.client.data.ExerciseTrackedStatus exerciseTrackedStatus, androidx.health.services.client.data.ExerciseType exerciseType);
+    method public int describeContents();
+    method public androidx.health.services.client.data.ExerciseTrackedStatus getExerciseTrackedStatus();
+    method public androidx.health.services.client.data.ExerciseType getExerciseType();
+    method public void writeToParcel(android.os.Parcel dest, int flags);
+    property public final androidx.health.services.client.data.ExerciseTrackedStatus exerciseTrackedStatus;
+    property public final androidx.health.services.client.data.ExerciseType exerciseType;
+    field public static final android.os.Parcelable.Creator<androidx.health.services.client.data.ExerciseInfo> CREATOR;
+    field public static final androidx.health.services.client.data.ExerciseInfo.Companion Companion;
+  }
+
+  public static final class ExerciseInfo.Companion {
+  }
+
+  public final class ExerciseLapSummary implements android.os.Parcelable {
+    ctor public ExerciseLapSummary(int lapCount, java.time.Instant startTime, java.time.Instant endTime, java.time.Duration activeDuration, java.util.Map<androidx.health.services.client.data.DataType,androidx.health.services.client.data.DataPoint> lapMetrics);
+    method public int component1();
+    method public java.time.Instant component2();
+    method public java.time.Instant component3();
+    method public java.time.Duration component4();
+    method public java.util.Map<androidx.health.services.client.data.DataType,androidx.health.services.client.data.DataPoint> component5();
+    method public androidx.health.services.client.data.ExerciseLapSummary copy(int lapCount, java.time.Instant startTime, java.time.Instant endTime, java.time.Duration activeDuration, java.util.Map<androidx.health.services.client.data.DataType,androidx.health.services.client.data.DataPoint> lapMetrics);
+    method public int describeContents();
+    method public java.time.Duration getActiveDuration();
+    method public java.time.Instant getEndTime();
+    method public int getLapCount();
+    method public java.util.Map<androidx.health.services.client.data.DataType,androidx.health.services.client.data.DataPoint> getLapMetrics();
+    method public java.time.Instant getStartTime();
+    method public void writeToParcel(android.os.Parcel dest, int flags);
+    property public final java.time.Duration activeDuration;
+    property public final java.time.Instant endTime;
+    property public final int lapCount;
+    property public final java.util.Map<androidx.health.services.client.data.DataType,androidx.health.services.client.data.DataPoint> lapMetrics;
+    property public final java.time.Instant startTime;
+    field public static final android.os.Parcelable.Creator<androidx.health.services.client.data.ExerciseLapSummary> CREATOR;
+    field public static final androidx.health.services.client.data.ExerciseLapSummary.Companion Companion;
+  }
+
+  public static final class ExerciseLapSummary.Companion {
+  }
+
+  public enum ExerciseState {
+    method public static final androidx.health.services.client.data.ExerciseState? fromId(int id);
+    method public final int getId();
+    method public final boolean isEnded();
+    method public final boolean isPaused();
+    method public final boolean isResuming();
+    property public final int id;
+    property public final boolean isEnded;
+    property public final boolean isPaused;
+    property public final boolean isResuming;
+    enum_constant public static final androidx.health.services.client.data.ExerciseState ACTIVE;
+    enum_constant public static final androidx.health.services.client.data.ExerciseState AUTO_ENDED;
+    enum_constant public static final androidx.health.services.client.data.ExerciseState AUTO_ENDING;
+    enum_constant public static final androidx.health.services.client.data.ExerciseState AUTO_PAUSED;
+    enum_constant public static final androidx.health.services.client.data.ExerciseState AUTO_PAUSING;
+    enum_constant public static final androidx.health.services.client.data.ExerciseState AUTO_RESUMING;
+    enum_constant public static final androidx.health.services.client.data.ExerciseState TERMINATED;
+    enum_constant public static final androidx.health.services.client.data.ExerciseState TERMINATING;
+    enum_constant public static final androidx.health.services.client.data.ExerciseState USER_ENDED;
+    enum_constant public static final androidx.health.services.client.data.ExerciseState USER_ENDING;
+    enum_constant public static final androidx.health.services.client.data.ExerciseState USER_PAUSED;
+    enum_constant public static final androidx.health.services.client.data.ExerciseState USER_PAUSING;
+    enum_constant public static final androidx.health.services.client.data.ExerciseState USER_RESUMING;
+    enum_constant public static final androidx.health.services.client.data.ExerciseState USER_STARTING;
+    field public static final androidx.health.services.client.data.ExerciseState.Companion Companion;
+  }
+
+  public static final class ExerciseState.Companion {
+    method public androidx.health.services.client.data.ExerciseState? fromId(int id);
+  }
+
+  public enum ExerciseTrackedStatus {
+    method public static final androidx.health.services.client.data.ExerciseTrackedStatus? fromId(int id);
+    method public final int getId();
+    property public final int id;
+    enum_constant public static final androidx.health.services.client.data.ExerciseTrackedStatus NO_EXERCISE_IN_PROGRESS;
+    enum_constant public static final androidx.health.services.client.data.ExerciseTrackedStatus OTHER_APP_IN_PROGRESS;
+    enum_constant public static final androidx.health.services.client.data.ExerciseTrackedStatus OWNED_EXERCISE_IN_PROGRESS;
+    field public static final androidx.health.services.client.data.ExerciseTrackedStatus.Companion Companion;
+  }
+
+  public static final class ExerciseTrackedStatus.Companion {
+    method public androidx.health.services.client.data.ExerciseTrackedStatus? fromId(int id);
+  }
+
+  public enum ExerciseType {
+    method public static final androidx.health.services.client.data.ExerciseType fromId(int id);
+    method public final int getId();
+    property public final int id;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType BACK_EXTENSION;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType BADMINTON;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType BARBELL_SHOULDER_PRESS;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType BASEBALL;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType BASKETBALL;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType BENCH_PRESS;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType BENCH_SIT_UP;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType BIKING;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType BIKING_STATIONARY;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType BOOT_CAMP;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType BOXING;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType BURPEE;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType CALISTHENICS;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType CRICKET;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType CRUNCH;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType DANCING;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType DEADLIFT;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType DUMBBELL_CURL_LEFT_ARM;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType DUMBBELL_CURL_RIGHT_ARM;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType DUMBBELL_FRONT_RAISE;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType DUMBBELL_LATERAL_RAISE;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType DUMBBELL_TRICEPS_EXTENSION_LEFT_ARM;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType DUMBBELL_TRICEPS_EXTENSION_RIGHT_ARM;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType DUMBBELL_TRICEPS_EXTENSION_TWO_ARM;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType ELLIPTICAL;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType EXERCISE_CLASS;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType FENCING;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType FOOTBALL_AMERICAN;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType FOOTBALL_AUSTRALIAN;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType FRISBEE_DISC;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType GOLF;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType GUIDED_BREATHING;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType GYNMASTICS;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType HANDBALL;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType HIGH_INTENSITY_INTERVAL_TRAINING;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType HIKING;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType ICE_HOCKEY;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType ICE_SKATING;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType JUMPING_JACK;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType JUMP_ROPE;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType LAT_PULL_DOWN;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType LUNGE;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType MARTIAL_ARTS;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType MEDITATION;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType PADDLING;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType PARA_GLIDING;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType PILATES;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType PLANK;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType RACQUETBALL;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType ROCK_CLIMBING;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType ROLLER_HOCKEY;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType ROWING;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType ROWING_MACHINE;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType RUGBY;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType RUNNING;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType RUNNING_TREADMILL;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType SAILING;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType SCUBA_DIVING;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType SKATING;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType SKIING;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType SNOWBOARDING;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType SNOWSHOEING;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType SOCCER;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType SOFTBALL;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType SQUASH;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType SQUAT;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType STAIR_CLIMBING;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType STAIR_CLIMBING_MACHINE;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType STRENGTH_TRAINING;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType STRETCHING;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType SURFING;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType SWIMMING_OPEN_WATER;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType SWIMMING_POOL;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType TABLE_TENNIS;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType TENNIS;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType UNKNOWN;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType VOLLEYBALL;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType WALKING;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType WATER_POLO;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType WEIGHTLIFTING;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType WORKOUT_INDOOR;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType WORKOUT_OUTDOOR;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType YOGA;
+    field public static final androidx.health.services.client.data.ExerciseType.Companion Companion;
+  }
+
+  public static final class ExerciseType.Companion {
+    method public androidx.health.services.client.data.ExerciseType fromId(int id);
+  }
+
+  public final class ExerciseUpdate implements android.os.Parcelable {
+    ctor public ExerciseUpdate(androidx.health.services.client.data.ExerciseState state, java.time.Instant startTime, java.time.Duration activeDuration, java.util.Map<androidx.health.services.client.data.DataType,? extends java.util.List<androidx.health.services.client.data.DataPoint>> latestMetrics, java.util.Set<androidx.health.services.client.data.AchievedExerciseGoal> latestAchievedGoals, java.util.Set<androidx.health.services.client.data.MilestoneMarkerSummary> latestMilestoneMarkerSummaries, androidx.health.services.client.data.ExerciseConfig? exerciseConfig, androidx.health.services.client.data.AutoExerciseConfig? autoExerciseConfig, androidx.health.services.client.data.ExerciseType? autoExerciseDetected);
+    method public androidx.health.services.client.data.ExerciseState component1();
+    method public java.time.Instant component2();
+    method public java.time.Duration component3();
+    method public java.util.Map<androidx.health.services.client.data.DataType,java.util.List<androidx.health.services.client.data.DataPoint>> component4();
+    method public java.util.Set<androidx.health.services.client.data.AchievedExerciseGoal> component5();
+    method public java.util.Set<androidx.health.services.client.data.MilestoneMarkerSummary> component6();
+    method public androidx.health.services.client.data.ExerciseConfig? component7();
+    method public androidx.health.services.client.data.AutoExerciseConfig? component8();
+    method public androidx.health.services.client.data.ExerciseType? component9();
+    method public androidx.health.services.client.data.ExerciseUpdate copy(androidx.health.services.client.data.ExerciseState state, java.time.Instant startTime, java.time.Duration activeDuration, java.util.Map<androidx.health.services.client.data.DataType,? extends java.util.List<androidx.health.services.client.data.DataPoint>> latestMetrics, java.util.Set<androidx.health.services.client.data.AchievedExerciseGoal> latestAchievedGoals, java.util.Set<androidx.health.services.client.data.MilestoneMarkerSummary> latestMilestoneMarkerSummaries, androidx.health.services.client.data.ExerciseConfig? exerciseConfig, androidx.health.services.client.data.AutoExerciseConfig? autoExerciseConfig, androidx.health.services.client.data.ExerciseType? autoExerciseDetected);
+    method public int describeContents();
+    method public java.time.Duration getActiveDuration();
+    method public androidx.health.services.client.data.AutoExerciseConfig? getAutoExerciseConfig();
+    method public androidx.health.services.client.data.ExerciseType? getAutoExerciseDetected();
+    method public androidx.health.services.client.data.ExerciseConfig? getExerciseConfig();
+    method public androidx.health.services.client.data.ExerciseUpdate.ExerciseSessionType getExerciseSessionType();
+    method public java.util.Set<androidx.health.services.client.data.AchievedExerciseGoal> getLatestAchievedGoals();
+    method public java.util.Map<androidx.health.services.client.data.DataType,java.util.List<androidx.health.services.client.data.DataPoint>> getLatestMetrics();
+    method public java.util.Set<androidx.health.services.client.data.MilestoneMarkerSummary> getLatestMilestoneMarkerSummaries();
+    method public java.time.Instant getStartTime();
+    method public androidx.health.services.client.data.ExerciseState getState();
+    method public void writeToParcel(android.os.Parcel dest, int flags);
+    property public final java.time.Duration activeDuration;
+    property public final androidx.health.services.client.data.AutoExerciseConfig? autoExerciseConfig;
+    property public final androidx.health.services.client.data.ExerciseType? autoExerciseDetected;
+    property public final androidx.health.services.client.data.ExerciseConfig? exerciseConfig;
+    property public final java.util.Set<androidx.health.services.client.data.AchievedExerciseGoal> latestAchievedGoals;
+    property public final java.util.Map<androidx.health.services.client.data.DataType,java.util.List<androidx.health.services.client.data.DataPoint>> latestMetrics;
+    property public final java.util.Set<androidx.health.services.client.data.MilestoneMarkerSummary> latestMilestoneMarkerSummaries;
+    property public final java.time.Instant startTime;
+    property public final androidx.health.services.client.data.ExerciseState state;
+    field public static final android.os.Parcelable.Creator<androidx.health.services.client.data.ExerciseUpdate> CREATOR;
+    field public static final androidx.health.services.client.data.ExerciseUpdate.Companion Companion;
+  }
+
+  public static final class ExerciseUpdate.Companion {
+  }
+
+  public enum ExerciseUpdate.ExerciseSessionType {
+    enum_constant public static final androidx.health.services.client.data.ExerciseUpdate.ExerciseSessionType AUTO_EXERCISE_DETECTION;
+    enum_constant public static final androidx.health.services.client.data.ExerciseUpdate.ExerciseSessionType MANUALLY_STARTED_EXERCISE;
+  }
+
+  public final class MeasureCapabilities implements android.os.Parcelable {
+    ctor public MeasureCapabilities(java.util.Set<androidx.health.services.client.data.DataType> supportedDataTypesMeasure);
+    method public java.util.Set<androidx.health.services.client.data.DataType> component1();
+    method public androidx.health.services.client.data.MeasureCapabilities copy(java.util.Set<androidx.health.services.client.data.DataType> supportedDataTypesMeasure);
+    method public int describeContents();
+    method public java.util.Set<androidx.health.services.client.data.DataType> getSupportedDataTypesMeasure();
+    method public void writeToParcel(android.os.Parcel dest, int flags);
+    property public final java.util.Set<androidx.health.services.client.data.DataType> supportedDataTypesMeasure;
+    field public static final android.os.Parcelable.Creator<androidx.health.services.client.data.MeasureCapabilities> CREATOR;
+    field public static final androidx.health.services.client.data.MeasureCapabilities.Companion Companion;
+  }
+
+  public static final class MeasureCapabilities.Companion {
+  }
+
+  public final class MilestoneMarkerSummary implements android.os.Parcelable {
+    ctor public MilestoneMarkerSummary(java.time.Instant startTime, java.time.Instant endTime, java.time.Duration activeDuration, androidx.health.services.client.data.AchievedExerciseGoal achievedGoal, java.util.Map<androidx.health.services.client.data.DataType,androidx.health.services.client.data.DataPoint> summaryMetrics);
+    method public java.time.Instant component1();
+    method public java.time.Instant component2();
+    method public java.time.Duration component3();
+    method public androidx.health.services.client.data.AchievedExerciseGoal component4();
+    method public java.util.Map<androidx.health.services.client.data.DataType,androidx.health.services.client.data.DataPoint> component5();
+    method public androidx.health.services.client.data.MilestoneMarkerSummary copy(java.time.Instant startTime, java.time.Instant endTime, java.time.Duration activeDuration, androidx.health.services.client.data.AchievedExerciseGoal achievedGoal, java.util.Map<androidx.health.services.client.data.DataType,androidx.health.services.client.data.DataPoint> summaryMetrics);
+    method public int describeContents();
+    method public androidx.health.services.client.data.AchievedExerciseGoal getAchievedGoal();
+    method public java.time.Duration getActiveDuration();
+    method public java.time.Instant getEndTime();
+    method public java.time.Instant getStartTime();
+    method public java.util.Map<androidx.health.services.client.data.DataType,androidx.health.services.client.data.DataPoint> getSummaryMetrics();
+    method public void writeToParcel(android.os.Parcel dest, int flags);
+    property public final androidx.health.services.client.data.AchievedExerciseGoal achievedGoal;
+    property public final java.time.Duration activeDuration;
+    property public final java.time.Instant endTime;
+    property public final java.time.Instant startTime;
+    property public final java.util.Map<androidx.health.services.client.data.DataType,androidx.health.services.client.data.DataPoint> summaryMetrics;
+    field public static final android.os.Parcelable.Creator<androidx.health.services.client.data.MilestoneMarkerSummary> CREATOR;
+    field public static final androidx.health.services.client.data.MilestoneMarkerSummary.Companion Companion;
+  }
+
+  public static final class MilestoneMarkerSummary.Companion {
+  }
+
+  public final class PassiveActivityState implements android.os.Parcelable {
+    ctor public PassiveActivityState(java.util.List<androidx.health.services.client.data.DataPoint> dataPoints, androidx.health.services.client.data.UserActivityState userActivityState, androidx.health.services.client.data.ExerciseType? exerciseType, java.time.Instant stateChangeTime);
+    method public java.util.List<androidx.health.services.client.data.DataPoint> component1();
+    method public androidx.health.services.client.data.UserActivityState component2();
+    method public androidx.health.services.client.data.ExerciseType? component3();
+    method public java.time.Instant component4();
+    method public androidx.health.services.client.data.PassiveActivityState copy(java.util.List<androidx.health.services.client.data.DataPoint> dataPoints, androidx.health.services.client.data.UserActivityState userActivityState, androidx.health.services.client.data.ExerciseType? exerciseType, java.time.Instant stateChangeTime);
+    method public static androidx.health.services.client.data.PassiveActivityState createActiveExerciseState(java.util.List<androidx.health.services.client.data.DataPoint> dataPoints, androidx.health.services.client.data.ExerciseType exerciseType, java.time.Instant stateChangeTime);
+    method public static androidx.health.services.client.data.PassiveActivityState createInactiveState(java.util.List<androidx.health.services.client.data.DataPoint> dataPoints, java.time.Instant stateChangeTime);
+    method public static androidx.health.services.client.data.PassiveActivityState createPassiveActivityState(java.util.List<androidx.health.services.client.data.DataPoint> dataPoints, java.time.Instant stateChangeTime);
+    method public static androidx.health.services.client.data.PassiveActivityState createUnknownTypeState(java.util.List<androidx.health.services.client.data.DataPoint> dataPoints, java.time.Instant stateChangeTime);
+    method public int describeContents();
+    method public static androidx.health.services.client.data.PassiveActivityState? fromIntent(android.content.Intent intent);
+    method public java.util.List<androidx.health.services.client.data.DataPoint> getDataPoints();
+    method public androidx.health.services.client.data.ExerciseType? getExerciseType();
+    method public java.time.Instant getStateChangeTime();
+    method public androidx.health.services.client.data.UserActivityState getUserActivityState();
+    method public void putToIntent(android.content.Intent intent);
+    method public void writeToParcel(android.os.Parcel dest, int flags);
+    property public final java.util.List<androidx.health.services.client.data.DataPoint> dataPoints;
+    property public final androidx.health.services.client.data.ExerciseType? exerciseType;
+    property public final java.time.Instant stateChangeTime;
+    property public final androidx.health.services.client.data.UserActivityState userActivityState;
+    field public static final android.os.Parcelable.Creator<androidx.health.services.client.data.PassiveActivityState> CREATOR;
+    field public static final androidx.health.services.client.data.PassiveActivityState.Companion Companion;
+  }
+
+  public static final class PassiveActivityState.Companion {
+    method public androidx.health.services.client.data.PassiveActivityState createActiveExerciseState(java.util.List<androidx.health.services.client.data.DataPoint> dataPoints, androidx.health.services.client.data.ExerciseType exerciseType, java.time.Instant stateChangeTime);
+    method public androidx.health.services.client.data.PassiveActivityState createInactiveState(java.util.List<androidx.health.services.client.data.DataPoint> dataPoints, java.time.Instant stateChangeTime);
+    method public androidx.health.services.client.data.PassiveActivityState createPassiveActivityState(java.util.List<androidx.health.services.client.data.DataPoint> dataPoints, java.time.Instant stateChangeTime);
+    method public androidx.health.services.client.data.PassiveActivityState createUnknownTypeState(java.util.List<androidx.health.services.client.data.DataPoint> dataPoints, java.time.Instant stateChangeTime);
+    method public androidx.health.services.client.data.PassiveActivityState? fromIntent(android.content.Intent intent);
+  }
+
+  public final class PassiveMonitoringCapabilities implements android.os.Parcelable {
+    ctor public PassiveMonitoringCapabilities(java.util.Set<androidx.health.services.client.data.DataType> supportedDataTypesPassiveMonitoring, java.util.Set<androidx.health.services.client.data.DataType> supportedDataTypesEvents);
+    method public java.util.Set<androidx.health.services.client.data.DataType> component1();
+    method public java.util.Set<androidx.health.services.client.data.DataType> component2();
+    method public androidx.health.services.client.data.PassiveMonitoringCapabilities copy(java.util.Set<androidx.health.services.client.data.DataType> supportedDataTypesPassiveMonitoring, java.util.Set<androidx.health.services.client.data.DataType> supportedDataTypesEvents);
+    method public int describeContents();
+    method public java.util.Set<androidx.health.services.client.data.DataType> getSupportedDataTypesEvents();
+    method public java.util.Set<androidx.health.services.client.data.DataType> getSupportedDataTypesPassiveMonitoring();
+    method public void writeToParcel(android.os.Parcel dest, int flags);
+    property public final java.util.Set<androidx.health.services.client.data.DataType> supportedDataTypesEvents;
+    property public final java.util.Set<androidx.health.services.client.data.DataType> supportedDataTypesPassiveMonitoring;
+    field public static final android.os.Parcelable.Creator<androidx.health.services.client.data.PassiveMonitoringCapabilities> CREATOR;
+    field public static final androidx.health.services.client.data.PassiveMonitoringCapabilities.Companion Companion;
+  }
+
+  public static final class PassiveMonitoringCapabilities.Companion {
+  }
+
+  public enum UserActivityState {
+    method public static final androidx.health.services.client.data.UserActivityState? fromId(int id);
+    method public final int getId();
+    property public final int id;
+    enum_constant public static final androidx.health.services.client.data.UserActivityState USER_ACTIVITY_EXERCISE;
+    enum_constant public static final androidx.health.services.client.data.UserActivityState USER_ACTIVITY_INACTIVE;
+    enum_constant public static final androidx.health.services.client.data.UserActivityState USER_ACTIVITY_PASSIVE;
+    enum_constant public static final androidx.health.services.client.data.UserActivityState USER_ACTIVITY_UNKNOWN;
+    field public static final androidx.health.services.client.data.UserActivityState.Companion Companion;
+  }
+
+  public static final class UserActivityState.Companion {
+    method public androidx.health.services.client.data.UserActivityState? fromId(int id);
+  }
+
+  public final class Value implements android.os.Parcelable {
+    method public boolean asBoolean();
+    method public double asDouble();
+    method public double[] asDoubleArray();
+    method public long asLong();
+    method public int component1();
+    method public java.util.List<java.lang.Double> component2();
+    method public long component3();
+    method public androidx.health.services.client.data.Value copy(int format, java.util.List<java.lang.Double> doubleList, long longValue);
+    method public int describeContents();
+    method public java.util.List<java.lang.Double> getDoubleList();
+    method public int getFormat();
+    method public long getLongValue();
+    method public boolean isBoolean();
+    method public boolean isDouble();
+    method public boolean isDoubleArray();
+    method public boolean isLong();
+    method public static androidx.health.services.client.data.Value ofBoolean(boolean value);
+    method public static androidx.health.services.client.data.Value ofDouble(double value);
+    method public static androidx.health.services.client.data.Value ofDoubleArray(double... doubleArray);
+    method public static androidx.health.services.client.data.Value ofLong(long value);
+    method public static androidx.health.services.client.data.Value sum(androidx.health.services.client.data.Value first, androidx.health.services.client.data.Value second);
+    method public void writeToParcel(android.os.Parcel dest, int flags);
+    property public final java.util.List<java.lang.Double> doubleList;
+    property public final int format;
+    property public final boolean isBoolean;
+    property public final boolean isDouble;
+    property public final boolean isDoubleArray;
+    property public final boolean isLong;
+    property public final long longValue;
+    field public static final android.os.Parcelable.Creator<androidx.health.services.client.data.Value> CREATOR;
+    field public static final androidx.health.services.client.data.Value.Companion Companion;
+    field public static final int FORMAT_BOOLEAN = 4; // 0x4
+    field public static final int FORMAT_DOUBLE = 1; // 0x1
+    field public static final int FORMAT_DOUBLE_ARRAY = 3; // 0x3
+    field public static final int FORMAT_LONG = 2; // 0x2
+  }
+
+  public static final class Value.Companion {
+    method public androidx.health.services.client.data.Value ofBoolean(boolean value);
+    method public androidx.health.services.client.data.Value ofDouble(double value);
+    method public androidx.health.services.client.data.Value ofDoubleArray(double... doubleArray);
+    method public androidx.health.services.client.data.Value ofLong(long value);
+    method public androidx.health.services.client.data.Value sum(androidx.health.services.client.data.Value first, androidx.health.services.client.data.Value second);
+  }
+
+}
+
+package androidx.health.services.client.data.event {
+
+  public final class Event implements android.os.Parcelable {
+    ctor public Event(androidx.health.services.client.data.DataTypeCondition dataTypeCondition, androidx.health.services.client.data.event.Event.TriggerType triggerType);
+    method public androidx.health.services.client.data.DataTypeCondition component1();
+    method public androidx.health.services.client.data.event.Event.TriggerType component2();
+    method public androidx.health.services.client.data.event.Event copy(androidx.health.services.client.data.DataTypeCondition dataTypeCondition, androidx.health.services.client.data.event.Event.TriggerType triggerType);
+    method public int describeContents();
+    method public androidx.health.services.client.data.DataTypeCondition getDataTypeCondition();
+    method public androidx.health.services.client.data.event.Event.TriggerType getTriggerType();
+    method public boolean isTriggered(androidx.health.services.client.data.DataPoint dataPoint);
+    method public void writeToParcel(android.os.Parcel dest, int flags);
+    property public final androidx.health.services.client.data.DataTypeCondition dataTypeCondition;
+    property public final androidx.health.services.client.data.event.Event.TriggerType triggerType;
+    field public static final android.os.Parcelable.Creator<androidx.health.services.client.data.event.Event> CREATOR;
+    field public static final androidx.health.services.client.data.event.Event.Companion Companion;
+  }
+
+  public static final class Event.Companion {
+  }
+
+  public enum Event.TriggerType {
+    method public static final androidx.health.services.client.data.event.Event.TriggerType? fromId(int id);
+    method public final int getId();
+    property public final int id;
+    enum_constant public static final androidx.health.services.client.data.event.Event.TriggerType ONCE;
+    enum_constant public static final androidx.health.services.client.data.event.Event.TriggerType REPEATED;
+    field public static final androidx.health.services.client.data.event.Event.TriggerType.Companion Companion;
+  }
+
+  public static final class Event.TriggerType.Companion {
+    method public androidx.health.services.client.data.event.Event.TriggerType? fromId(int id);
+  }
+
+}
+
diff --git a/health/health-services-client/api/public_plus_experimental_current.txt b/health/health-services-client/api/public_plus_experimental_current.txt
new file mode 100644
index 0000000..6abe37c
--- /dev/null
+++ b/health/health-services-client/api/public_plus_experimental_current.txt
@@ -0,0 +1,869 @@
+// Signature format: 4.0
+package androidx.health.services.client {
+
+  public interface ExerciseClient {
+    method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> addGoalToActiveExercise(androidx.health.services.client.data.ExerciseGoal exerciseGoal);
+    method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> clearUpdateListener(androidx.health.services.client.ExerciseUpdateListener listener);
+    method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> endExercise();
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.health.services.client.data.Capabilities> getCapabilities();
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.health.services.client.data.ExerciseInfo> getCurrentExerciseInfo();
+    method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> markLap();
+    method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> overrideAutoPauseAndResumeForActiveExercise(boolean enabled);
+    method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> pauseExercise();
+    method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> resumeExercise();
+    method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> setUpdateListener(androidx.health.services.client.ExerciseUpdateListener listener);
+    method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> setUpdateListener(androidx.health.services.client.ExerciseUpdateListener listener, java.util.concurrent.Executor executor);
+    method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> startExercise(androidx.health.services.client.data.ExerciseConfig configuration);
+    property public abstract com.google.common.util.concurrent.ListenableFuture<androidx.health.services.client.data.Capabilities> capabilities;
+    property public abstract com.google.common.util.concurrent.ListenableFuture<androidx.health.services.client.data.ExerciseInfo> currentExerciseInfo;
+  }
+
+  public interface ExerciseUpdateListener {
+    method public void onExerciseUpdate(androidx.health.services.client.data.ExerciseUpdate update);
+    method public void onLapSummary(androidx.health.services.client.data.ExerciseLapSummary lapSummary);
+  }
+
+  public final class HealthServices {
+    method public static androidx.health.services.client.HealthServicesClient getClient(android.content.Context context);
+    field public static final androidx.health.services.client.HealthServices INSTANCE;
+  }
+
+  public interface HealthServicesClient {
+    method public androidx.health.services.client.ExerciseClient getExerciseClient();
+    method public androidx.health.services.client.MeasureClient getMeasureClient();
+    method public androidx.health.services.client.PassiveMonitoringClient getPassiveMonitoringClient();
+    property public abstract androidx.health.services.client.ExerciseClient exerciseClient;
+    property public abstract androidx.health.services.client.MeasureClient measureClient;
+    property public abstract androidx.health.services.client.PassiveMonitoringClient passiveMonitoringClient;
+  }
+
+  public interface MeasureCallback {
+    method public void onAvailabilityChanged(androidx.health.services.client.data.DataType dataType, androidx.health.services.client.data.Availability availability);
+    method public void onData(java.util.List<androidx.health.services.client.data.DataPoint> data);
+  }
+
+  public interface MeasureClient {
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.health.services.client.data.MeasureCapabilities> getCapabilities();
+    method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> registerCallback(androidx.health.services.client.data.DataType dataType, androidx.health.services.client.MeasureCallback callback);
+    method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> registerCallback(androidx.health.services.client.data.DataType dataType, androidx.health.services.client.MeasureCallback callback, java.util.concurrent.Executor executor);
+    method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> unregisterCallback(androidx.health.services.client.data.DataType dataType, androidx.health.services.client.MeasureCallback callback);
+    property public abstract com.google.common.util.concurrent.ListenableFuture<androidx.health.services.client.data.MeasureCapabilities> capabilities;
+  }
+
+  public interface PassiveMonitoringCallback {
+    method public void onPassiveActivityState(androidx.health.services.client.data.PassiveActivityState state);
+  }
+
+  public interface PassiveMonitoringClient {
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.health.services.client.data.PassiveMonitoringCapabilities> getCapabilities();
+    method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> registerDataCallback(java.util.Set<androidx.health.services.client.data.DataType> dataTypes, android.app.PendingIntent callbackIntent);
+    method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> registerDataCallback(java.util.Set<androidx.health.services.client.data.DataType> dataTypes, android.app.PendingIntent callbackIntent, androidx.health.services.client.PassiveMonitoringCallback callback);
+    method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> registerEventCallback(androidx.health.services.client.data.event.Event event, android.app.PendingIntent callbackIntent);
+    method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> unregisterDataCallback();
+    method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> unregisterEventCallback(androidx.health.services.client.data.event.Event event);
+    property public abstract com.google.common.util.concurrent.ListenableFuture<androidx.health.services.client.data.PassiveMonitoringCapabilities> capabilities;
+  }
+
+}
+
+package androidx.health.services.client.data {
+
+  public final class AchievedExerciseGoal implements android.os.Parcelable {
+    ctor public AchievedExerciseGoal(androidx.health.services.client.data.ExerciseGoal goal);
+    method public androidx.health.services.client.data.ExerciseGoal component1();
+    method public androidx.health.services.client.data.AchievedExerciseGoal copy(androidx.health.services.client.data.ExerciseGoal goal);
+    method public int describeContents();
+    method public androidx.health.services.client.data.ExerciseGoal getGoal();
+    method public void writeToParcel(android.os.Parcel dest, int flags);
+    property public final androidx.health.services.client.data.ExerciseGoal goal;
+    field public static final android.os.Parcelable.Creator<androidx.health.services.client.data.AchievedExerciseGoal> CREATOR;
+    field public static final androidx.health.services.client.data.AchievedExerciseGoal.Companion Companion;
+  }
+
+  public static final class AchievedExerciseGoal.Companion {
+  }
+
+  public final class AutoExerciseConfig implements android.os.Parcelable {
+    ctor public AutoExerciseConfig(optional java.util.Set<? extends androidx.health.services.client.data.ExerciseType> exercisesToDetect, optional android.app.PendingIntent? launchIntent, optional android.os.Bundle exerciseParams);
+    ctor public AutoExerciseConfig(optional java.util.Set<? extends androidx.health.services.client.data.ExerciseType> exercisesToDetect, optional android.app.PendingIntent? launchIntent);
+    ctor public AutoExerciseConfig(optional java.util.Set<? extends androidx.health.services.client.data.ExerciseType> exercisesToDetect);
+    method public java.util.Set<androidx.health.services.client.data.ExerciseType> component1();
+    method public android.app.PendingIntent? component2();
+    method public android.os.Bundle component3();
+    method public androidx.health.services.client.data.AutoExerciseConfig copy(java.util.Set<? extends androidx.health.services.client.data.ExerciseType> exercisesToDetect, android.app.PendingIntent? launchIntent, android.os.Bundle exerciseParams);
+    method public int describeContents();
+    method public android.os.Bundle getExerciseParams();
+    method public java.util.Set<androidx.health.services.client.data.ExerciseType> getExercisesToDetect();
+    method public android.app.PendingIntent? getLaunchIntent();
+    method public void writeToParcel(android.os.Parcel dest, int flags);
+    property public final android.os.Bundle exerciseParams;
+    property public final java.util.Set<androidx.health.services.client.data.ExerciseType> exercisesToDetect;
+    property public final android.app.PendingIntent? launchIntent;
+    field public static final android.os.Parcelable.Creator<androidx.health.services.client.data.AutoExerciseConfig> CREATOR;
+    field public static final androidx.health.services.client.data.AutoExerciseConfig.Companion Companion;
+  }
+
+  public static final class AutoExerciseConfig.Companion {
+  }
+
+  public enum Availability {
+    method public static final androidx.health.services.client.data.Availability? fromId(int id);
+    method public final int getId();
+    property public final int id;
+    enum_constant public static final androidx.health.services.client.data.Availability ACQUIRING;
+    enum_constant public static final androidx.health.services.client.data.Availability AVAILABLE;
+    enum_constant public static final androidx.health.services.client.data.Availability UNAVAILABLE;
+    enum_constant public static final androidx.health.services.client.data.Availability UNKNOWN;
+    field public static final androidx.health.services.client.data.Availability.Companion Companion;
+  }
+
+  public static final class Availability.Companion {
+    method public androidx.health.services.client.data.Availability? fromId(int id);
+  }
+
+  public final class Capabilities implements android.os.Parcelable {
+    ctor public Capabilities(java.util.Map<androidx.health.services.client.data.ExerciseType,androidx.health.services.client.data.ExerciseCapabilities> exerciseTypeToExerciseCapabilities);
+    method public java.util.Map<androidx.health.services.client.data.ExerciseType,androidx.health.services.client.data.ExerciseCapabilities> component1();
+    method public androidx.health.services.client.data.Capabilities copy(java.util.Map<androidx.health.services.client.data.ExerciseType,androidx.health.services.client.data.ExerciseCapabilities> exerciseTypeToExerciseCapabilities);
+    method public int describeContents();
+    method public java.util.Set<androidx.health.services.client.data.ExerciseType> getAutoPauseAndResumeEnabledExercises();
+    method public androidx.health.services.client.data.ExerciseCapabilities getExerciseCapabilities(androidx.health.services.client.data.ExerciseType exercise);
+    method public java.util.Map<androidx.health.services.client.data.ExerciseType,androidx.health.services.client.data.ExerciseCapabilities> getExerciseTypeToExerciseCapabilities();
+    method public java.util.Set<androidx.health.services.client.data.ExerciseType> getSupportedExerciseTypes();
+    method public void writeToParcel(android.os.Parcel dest, int flags);
+    property public final java.util.Set<androidx.health.services.client.data.ExerciseType> autoPauseAndResumeEnabledExercises;
+    property public final java.util.Map<androidx.health.services.client.data.ExerciseType,androidx.health.services.client.data.ExerciseCapabilities> exerciseTypeToExerciseCapabilities;
+    property public final java.util.Set<androidx.health.services.client.data.ExerciseType> supportedExerciseTypes;
+    field public static final android.os.Parcelable.Creator<androidx.health.services.client.data.Capabilities> CREATOR;
+    field public static final androidx.health.services.client.data.Capabilities.Companion Companion;
+  }
+
+  public static final class Capabilities.Companion {
+  }
+
+  public enum ComparisonType {
+    method public static final androidx.health.services.client.data.ComparisonType? fromId(int id);
+    method public final int getId();
+    property public final int id;
+    enum_constant public static final androidx.health.services.client.data.ComparisonType GREATER_THAN;
+    enum_constant public static final androidx.health.services.client.data.ComparisonType GREATER_THAN_OR_EQUAL;
+    enum_constant public static final androidx.health.services.client.data.ComparisonType LESS_THAN;
+    enum_constant public static final androidx.health.services.client.data.ComparisonType LESS_THAN_OR_EQUAL;
+    field public static final androidx.health.services.client.data.ComparisonType.Companion Companion;
+  }
+
+  public static final class ComparisonType.Companion {
+    method public androidx.health.services.client.data.ComparisonType? fromId(int id);
+  }
+
+  public final class DataPoint implements android.os.Parcelable {
+    method public androidx.health.services.client.data.DataType component1();
+    method public androidx.health.services.client.data.Value component2();
+    method public java.time.Duration component3();
+    method public java.time.Duration component4();
+    method public android.os.Bundle component5();
+    method public androidx.health.services.client.data.DataPoint copy(androidx.health.services.client.data.DataType dataType, androidx.health.services.client.data.Value value, java.time.Duration startDurationFromBoot, java.time.Duration endDurationFromBoot, android.os.Bundle metadata);
+    method public static androidx.health.services.client.data.DataPoint createInterval(androidx.health.services.client.data.DataType dataType, androidx.health.services.client.data.Value value, java.time.Duration startDurationFromBoot, java.time.Duration endDurationFromBoot, optional android.os.Bundle metadata);
+    method public static androidx.health.services.client.data.DataPoint createInterval(androidx.health.services.client.data.DataType dataType, androidx.health.services.client.data.Value value, java.time.Duration startDurationFromBoot, java.time.Duration endDurationFromBoot);
+    method public static androidx.health.services.client.data.DataPoint createSample(androidx.health.services.client.data.DataType dataType, androidx.health.services.client.data.Value value, java.time.Duration durationFromBoot, optional android.os.Bundle metadata);
+    method public static androidx.health.services.client.data.DataPoint createSample(androidx.health.services.client.data.DataType dataType, androidx.health.services.client.data.Value value, java.time.Duration durationFromBoot);
+    method public int describeContents();
+    method public androidx.health.services.client.data.DataType getDataType();
+    method public java.time.Duration getEndDurationFromBoot();
+    method public java.time.Instant getEndInstant(java.time.Instant bootInstant);
+    method public android.os.Bundle getMetadata();
+    method public java.time.Duration getStartDurationFromBoot();
+    method public java.time.Instant getStartInstant(java.time.Instant bootInstant);
+    method public androidx.health.services.client.data.Value getValue();
+    method public void writeToParcel(android.os.Parcel dest, int flags);
+    property public final androidx.health.services.client.data.DataType dataType;
+    property public final java.time.Duration endDurationFromBoot;
+    property public final android.os.Bundle metadata;
+    property public final java.time.Duration startDurationFromBoot;
+    property public final androidx.health.services.client.data.Value value;
+    field public static final android.os.Parcelable.Creator<androidx.health.services.client.data.DataPoint> CREATOR;
+    field public static final androidx.health.services.client.data.DataPoint.Companion Companion;
+  }
+
+  public static final class DataPoint.Companion {
+    method public androidx.health.services.client.data.DataPoint createInterval(androidx.health.services.client.data.DataType dataType, androidx.health.services.client.data.Value value, java.time.Duration startDurationFromBoot, java.time.Duration endDurationFromBoot, optional android.os.Bundle metadata);
+    method public androidx.health.services.client.data.DataPoint createInterval(androidx.health.services.client.data.DataType dataType, androidx.health.services.client.data.Value value, java.time.Duration startDurationFromBoot, java.time.Duration endDurationFromBoot);
+    method public androidx.health.services.client.data.DataPoint createSample(androidx.health.services.client.data.DataType dataType, androidx.health.services.client.data.Value value, java.time.Duration durationFromBoot, optional android.os.Bundle metadata);
+    method public androidx.health.services.client.data.DataPoint createSample(androidx.health.services.client.data.DataType dataType, androidx.health.services.client.data.Value value, java.time.Duration durationFromBoot);
+  }
+
+  @Keep public final class DataPoints {
+    method public static androidx.health.services.client.data.DataPoint aggregateCalories(double kcalories, java.time.Duration startDurationFromBoot, java.time.Duration endDurationFromBoot);
+    method public static androidx.health.services.client.data.DataPoint aggregateDistance(double distance, java.time.Duration startDurationFromBoot, java.time.Duration endDurationFromBoot);
+    method public static androidx.health.services.client.data.DataPoint aggregateSteps(long steps, java.time.Duration startDurationFromBoot, java.time.Duration endDurationFromBoot);
+    method public static androidx.health.services.client.data.DataPoint aggregateSwimmingStrokes(long swimmingStrokes, java.time.Duration startDurationFromBoot, java.time.Duration endDurationFromBoot);
+    method public static androidx.health.services.client.data.DataPoint altitude(double meters, java.time.Duration durationFromBoot);
+    method public static androidx.health.services.client.data.DataPoint averagePace(double millisPerKm, java.time.Duration durationFromBoot);
+    method public static androidx.health.services.client.data.DataPoint averageSpeed(double metersPerSecond, java.time.Duration durationFromBoot);
+    method public static androidx.health.services.client.data.DataPoint calories(double kcalories, java.time.Duration startDurationFromBoot, java.time.Duration endDurationFromBoot);
+    method public static androidx.health.services.client.data.DataPoint distance(double meters, java.time.Duration startDurationFromBoot, java.time.Duration endDurationFromBoot);
+    method public static androidx.health.services.client.data.DataPoint elevation(double meters, java.time.Duration startDurationFromBoot, java.time.Duration endDurationFromBoot);
+    method public static androidx.health.services.client.data.DataPoint floors(double floors, java.time.Duration startDurationFromBoot, java.time.Duration endDurationFromBoot);
+    method @Keep public static java.util.List<androidx.health.services.client.data.DataPoint> getDataPoints(android.content.Intent intent);
+    method public static boolean getPermissionsGranted(android.content.Intent intent);
+    method public static androidx.health.services.client.data.DataPoint heartRate(double bpm, java.time.Duration durationFromBoot);
+    method public static androidx.health.services.client.data.DataPoint location(double latitude, double longitude, java.time.Duration durationFromBoot);
+    method public static androidx.health.services.client.data.DataPoint location(double latitude, double longitude, double altitude, java.time.Duration durationFromBoot);
+    method public static androidx.health.services.client.data.DataPoint maxSpeed(double metersPerSecond, java.time.Duration durationFromBoot);
+    method public static androidx.health.services.client.data.DataPoint pace(double millisPerKm, java.time.Duration durationFromBoot);
+    method public static void putDataPoints(android.content.Intent intent, java.util.Collection<androidx.health.services.client.data.DataPoint> dataPoints);
+    method public static void putPermissionsGranted(android.content.Intent intent, boolean granted);
+    method public static androidx.health.services.client.data.DataPoint speed(double metersPerSecond, java.time.Duration durationFromBoot);
+    method public static androidx.health.services.client.data.DataPoint spo2(double percent, java.time.Duration durationFromBoot);
+    method public static androidx.health.services.client.data.DataPoint steps(long steps, java.time.Duration startDurationFromBoot, java.time.Duration endDurationFromBoot);
+    method public static androidx.health.services.client.data.DataPoint stepsPerMinute(long stepsPerMinute, java.time.Duration startDurationFromBoot);
+    method public static androidx.health.services.client.data.DataPoint swimmingStrokes(long strokes, java.time.Duration startDurationFromBoot, java.time.Duration endDurationFromBoot);
+    field public static final androidx.health.services.client.data.DataPoints INSTANCE;
+    field public static final int LOCATION_DATA_POINT_ALTITUDE_INDEX = 2; // 0x2
+    field public static final int LOCATION_DATA_POINT_LATITUDE_INDEX = 0; // 0x0
+    field public static final int LOCATION_DATA_POINT_LONGITUDE_INDEX = 1; // 0x1
+  }
+
+  public final class DataType implements android.os.Parcelable {
+    ctor public DataType(String name, androidx.health.services.client.data.DataType.TimeType timeType, int format);
+    method public String component1();
+    method public androidx.health.services.client.data.DataType.TimeType component2();
+    method public int component3();
+    method public androidx.health.services.client.data.DataType copy(String name, androidx.health.services.client.data.DataType.TimeType timeType, int format);
+    method public int describeContents();
+    method public int getFormat();
+    method public String getName();
+    method public androidx.health.services.client.data.DataType.TimeType getTimeType();
+    method public void writeToParcel(android.os.Parcel dest, int flags);
+    property public final int format;
+    property public final String name;
+    property public final androidx.health.services.client.data.DataType.TimeType timeType;
+    field public static final androidx.health.services.client.data.DataType ABSOLUTE_ELEVATION;
+    field public static final androidx.health.services.client.data.DataType ACTIVE_EXERCISE_DURATION;
+    field public static final androidx.health.services.client.data.DataType AGGREGATE_CALORIES_EXPENDED;
+    field public static final androidx.health.services.client.data.DataType AGGREGATE_DECLINE_DISTANCE;
+    field public static final androidx.health.services.client.data.DataType AGGREGATE_DECLINE_TIME;
+    field public static final androidx.health.services.client.data.DataType AGGREGATE_DISTANCE;
+    field public static final androidx.health.services.client.data.DataType AGGREGATE_ELEVATION;
+    field public static final androidx.health.services.client.data.DataType AGGREGATE_FLAT_DISTANCE;
+    field public static final androidx.health.services.client.data.DataType AGGREGATE_FLAT_TIME;
+    field public static final androidx.health.services.client.data.DataType AGGREGATE_FLOORS;
+    field public static final androidx.health.services.client.data.DataType AGGREGATE_INCLINE_DISTANCE;
+    field public static final androidx.health.services.client.data.DataType AGGREGATE_INCLINE_TIME;
+    field public static final androidx.health.services.client.data.DataType AGGREGATE_RUNNING_STEP_COUNT;
+    field public static final androidx.health.services.client.data.DataType AGGREGATE_STEP_COUNT;
+    field public static final androidx.health.services.client.data.DataType AGGREGATE_SWIMMING_STROKE_COUNT;
+    field public static final androidx.health.services.client.data.DataType AGGREGATE_WALKING_STEP_COUNT;
+    field public static final androidx.health.services.client.data.DataType ALTITUDE;
+    field public static final androidx.health.services.client.data.DataType AVERAGE_HEART_RATE_BPM;
+    field public static final androidx.health.services.client.data.DataType AVERAGE_PACE;
+    field public static final androidx.health.services.client.data.DataType AVERAGE_SPEED;
+    field public static final android.os.Parcelable.Creator<androidx.health.services.client.data.DataType> CREATOR;
+    field public static final androidx.health.services.client.data.DataType.Companion Companion;
+    field public static final androidx.health.services.client.data.DataType DECLINE_DISTANCE;
+    field public static final androidx.health.services.client.data.DataType DECLINE_TIME;
+    field public static final androidx.health.services.client.data.DataType DISTANCE;
+    field public static final androidx.health.services.client.data.DataType ELEVATION;
+    field public static final androidx.health.services.client.data.DataType FLAT_DISTANCE;
+    field public static final androidx.health.services.client.data.DataType FLAT_TIME;
+    field public static final androidx.health.services.client.data.DataType FLOORS;
+    field public static final androidx.health.services.client.data.DataType HEART_RATE_BPM;
+    field public static final androidx.health.services.client.data.DataType INCLINE_DISTANCE;
+    field public static final androidx.health.services.client.data.DataType INCLINE_TIME;
+    field public static final androidx.health.services.client.data.DataType LOCATION;
+    field public static final androidx.health.services.client.data.DataType MAX_ALTITUDE;
+    field public static final androidx.health.services.client.data.DataType MAX_HEART_RATE_BPM;
+    field public static final androidx.health.services.client.data.DataType MAX_PACE;
+    field public static final androidx.health.services.client.data.DataType MAX_SPEED;
+    field public static final androidx.health.services.client.data.DataType MIN_ALTITUDE;
+    field public static final androidx.health.services.client.data.DataType PACE;
+    field public static final androidx.health.services.client.data.DataType REP_COUNT;
+    field public static final androidx.health.services.client.data.DataType RESTING_EXERCISE_DURATION;
+    field public static final androidx.health.services.client.data.DataType RUNNING_STEPS;
+    field public static final androidx.health.services.client.data.DataType SPEED;
+    field public static final androidx.health.services.client.data.DataType SPO2;
+    field public static final androidx.health.services.client.data.DataType STEPS;
+    field public static final androidx.health.services.client.data.DataType STEPS_PER_MINUTE;
+    field public static final androidx.health.services.client.data.DataType SWIMMING_LAP_COUNT;
+    field public static final androidx.health.services.client.data.DataType SWIMMING_STROKES;
+    field public static final androidx.health.services.client.data.DataType TOTAL_CALORIES;
+    field public static final androidx.health.services.client.data.DataType VO2;
+    field public static final androidx.health.services.client.data.DataType VO2_MAX;
+    field public static final androidx.health.services.client.data.DataType WALKING_STEPS;
+  }
+
+  public static final class DataType.Companion {
+  }
+
+  public enum DataType.TimeType {
+    enum_constant public static final androidx.health.services.client.data.DataType.TimeType INTERVAL;
+    enum_constant public static final androidx.health.services.client.data.DataType.TimeType SAMPLE;
+  }
+
+  public final class DataTypeCondition implements android.os.Parcelable {
+    ctor public DataTypeCondition(androidx.health.services.client.data.DataType dataType, androidx.health.services.client.data.Value threshold, androidx.health.services.client.data.ComparisonType comparisonType);
+    method public androidx.health.services.client.data.DataType component1();
+    method public androidx.health.services.client.data.Value component2();
+    method public androidx.health.services.client.data.ComparisonType component3();
+    method public androidx.health.services.client.data.DataTypeCondition copy(androidx.health.services.client.data.DataType dataType, androidx.health.services.client.data.Value threshold, androidx.health.services.client.data.ComparisonType comparisonType);
+    method public int describeContents();
+    method public androidx.health.services.client.data.ComparisonType getComparisonType();
+    method public androidx.health.services.client.data.DataType getDataType();
+    method public androidx.health.services.client.data.Value getThreshold();
+    method public boolean isSatisfied(androidx.health.services.client.data.DataPoint dataPoint);
+    method public boolean isThresholdSatisfied(androidx.health.services.client.data.Value value);
+    method public void writeToParcel(android.os.Parcel dest, int flags);
+    property public final androidx.health.services.client.data.ComparisonType comparisonType;
+    property public final androidx.health.services.client.data.DataType dataType;
+    property public final androidx.health.services.client.data.Value threshold;
+    field public static final android.os.Parcelable.Creator<androidx.health.services.client.data.DataTypeCondition> CREATOR;
+    field public static final androidx.health.services.client.data.DataTypeCondition.Companion Companion;
+  }
+
+  public static final class DataTypeCondition.Companion {
+  }
+
+  public final class DataTypes {
+    method public static androidx.health.services.client.data.DataType? getAggregateTypeFromRawType(androidx.health.services.client.data.DataType rawType);
+    method public static java.util.Set<androidx.health.services.client.data.DataType> getAggregatedDataTypesFromRawType(androidx.health.services.client.data.DataType rawType);
+    method public static androidx.health.services.client.data.DataType? getAverageTypeFromRawType(androidx.health.services.client.data.DataType rawType);
+    method public static androidx.health.services.client.data.DataType? getMaxTypeFromRawType(androidx.health.services.client.data.DataType rawType);
+    method public static androidx.health.services.client.data.DataType? getRawTypeFromAggregateType(androidx.health.services.client.data.DataType aggregateType);
+    method public static androidx.health.services.client.data.DataType? getRawTypeFromAverageType(androidx.health.services.client.data.DataType averageType);
+    method public static androidx.health.services.client.data.DataType? getRawTypeFromMaxType(androidx.health.services.client.data.DataType maxType);
+    method public static boolean isAggregateDataType(androidx.health.services.client.data.DataType dataType);
+    method public static boolean isRawType(androidx.health.services.client.data.DataType dataType);
+    method public static boolean isStatisticalAverageDataType(androidx.health.services.client.data.DataType dataType);
+    method public static boolean isStatisticalMaxDataType(androidx.health.services.client.data.DataType dataType);
+    field public static final androidx.health.services.client.data.DataTypes INSTANCE;
+  }
+
+  public final class ExerciseCapabilities implements android.os.Parcelable {
+    ctor public ExerciseCapabilities(java.util.Set<androidx.health.services.client.data.DataType> supportedDataTypes, java.util.Map<androidx.health.services.client.data.DataType,? extends java.util.Set<? extends androidx.health.services.client.data.ComparisonType>> supportedGoals, java.util.Map<androidx.health.services.client.data.DataType,? extends java.util.Set<? extends androidx.health.services.client.data.ComparisonType>> supportedMilestones, boolean supportsAutoPauseAndResume, boolean supportsLaps);
+    method public java.util.Set<androidx.health.services.client.data.DataType> component1();
+    method public java.util.Map<androidx.health.services.client.data.DataType,java.util.Set<androidx.health.services.client.data.ComparisonType>> component2();
+    method public java.util.Map<androidx.health.services.client.data.DataType,java.util.Set<androidx.health.services.client.data.ComparisonType>> component3();
+    method public boolean component4();
+    method public boolean component5();
+    method public androidx.health.services.client.data.ExerciseCapabilities copy(java.util.Set<androidx.health.services.client.data.DataType> supportedDataTypes, java.util.Map<androidx.health.services.client.data.DataType,? extends java.util.Set<? extends androidx.health.services.client.data.ComparisonType>> supportedGoals, java.util.Map<androidx.health.services.client.data.DataType,? extends java.util.Set<? extends androidx.health.services.client.data.ComparisonType>> supportedMilestones, boolean supportsAutoPauseAndResume, boolean supportsLaps);
+    method public int describeContents();
+    method public java.util.Set<androidx.health.services.client.data.DataType> getSupportedDataTypes();
+    method public java.util.Map<androidx.health.services.client.data.DataType,java.util.Set<androidx.health.services.client.data.ComparisonType>> getSupportedGoals();
+    method public java.util.Map<androidx.health.services.client.data.DataType,java.util.Set<androidx.health.services.client.data.ComparisonType>> getSupportedMilestones();
+    method public boolean getSupportsAutoPauseAndResume();
+    method public boolean getSupportsLaps();
+    method public void writeToParcel(android.os.Parcel dest, int flags);
+    property public final java.util.Set<androidx.health.services.client.data.DataType> supportedDataTypes;
+    property public final java.util.Map<androidx.health.services.client.data.DataType,java.util.Set<androidx.health.services.client.data.ComparisonType>> supportedGoals;
+    property public final java.util.Map<androidx.health.services.client.data.DataType,java.util.Set<androidx.health.services.client.data.ComparisonType>> supportedMilestones;
+    property public final boolean supportsAutoPauseAndResume;
+    property public final boolean supportsLaps;
+    field public static final android.os.Parcelable.Creator<androidx.health.services.client.data.ExerciseCapabilities> CREATOR;
+    field public static final androidx.health.services.client.data.ExerciseCapabilities.Companion Companion;
+  }
+
+  public static final class ExerciseCapabilities.Companion {
+  }
+
+  public final class ExerciseConfig implements android.os.Parcelable {
+    ctor protected ExerciseConfig(androidx.health.services.client.data.ExerciseType exerciseType, java.util.Set<androidx.health.services.client.data.DataType> dataTypes, boolean autoPauseAndResume, java.util.List<androidx.health.services.client.data.ExerciseGoal> exerciseGoals, android.os.Bundle exerciseParams);
+    method public static androidx.health.services.client.data.ExerciseConfig.Builder builder();
+    method public androidx.health.services.client.data.ExerciseType component1();
+    method public java.util.Set<androidx.health.services.client.data.DataType> component2();
+    method public boolean component3();
+    method public java.util.List<androidx.health.services.client.data.ExerciseGoal> component4();
+    method public android.os.Bundle component5();
+    method public androidx.health.services.client.data.ExerciseConfig copy(androidx.health.services.client.data.ExerciseType exerciseType, java.util.Set<androidx.health.services.client.data.DataType> dataTypes, boolean autoPauseAndResume, java.util.List<androidx.health.services.client.data.ExerciseGoal> exerciseGoals, android.os.Bundle exerciseParams);
+    method public int describeContents();
+    method public boolean getAutoPauseAndResume();
+    method public java.util.Set<androidx.health.services.client.data.DataType> getDataTypes();
+    method public java.util.List<androidx.health.services.client.data.ExerciseGoal> getExerciseGoals();
+    method public android.os.Bundle getExerciseParams();
+    method public androidx.health.services.client.data.ExerciseType getExerciseType();
+    method public void writeToParcel(android.os.Parcel dest, int flags);
+    property public final boolean autoPauseAndResume;
+    property public final java.util.Set<androidx.health.services.client.data.DataType> dataTypes;
+    property public final java.util.List<androidx.health.services.client.data.ExerciseGoal> exerciseGoals;
+    property public final android.os.Bundle exerciseParams;
+    property public final androidx.health.services.client.data.ExerciseType exerciseType;
+    field public static final android.os.Parcelable.Creator<androidx.health.services.client.data.ExerciseConfig> CREATOR;
+    field public static final androidx.health.services.client.data.ExerciseConfig.Companion Companion;
+  }
+
+  public static final class ExerciseConfig.Builder {
+    ctor public ExerciseConfig.Builder();
+    method public androidx.health.services.client.data.ExerciseConfig build();
+    method public androidx.health.services.client.data.ExerciseConfig.Builder setAutoPauseAndResume(boolean autoPauseAndResume);
+    method public androidx.health.services.client.data.ExerciseConfig.Builder setDataTypes(java.util.Set<androidx.health.services.client.data.DataType> dataTypes);
+    method public androidx.health.services.client.data.ExerciseConfig.Builder setExerciseGoals(java.util.List<androidx.health.services.client.data.ExerciseGoal> exerciseGoals);
+    method public androidx.health.services.client.data.ExerciseConfig.Builder setExerciseParams(android.os.Bundle exerciseParams);
+    method public androidx.health.services.client.data.ExerciseConfig.Builder setExerciseType(androidx.health.services.client.data.ExerciseType exerciseType);
+  }
+
+  public static final class ExerciseConfig.Companion {
+    method public androidx.health.services.client.data.ExerciseConfig.Builder builder();
+  }
+
+  public final class ExerciseGoal implements android.os.Parcelable {
+    ctor protected ExerciseGoal(androidx.health.services.client.data.ExerciseGoalType exerciseGoalType, androidx.health.services.client.data.DataTypeCondition dataTypeCondition, optional androidx.health.services.client.data.Value? period);
+    method public androidx.health.services.client.data.ExerciseGoalType component1();
+    method public androidx.health.services.client.data.DataTypeCondition component2();
+    method public androidx.health.services.client.data.Value? component3();
+    method public androidx.health.services.client.data.ExerciseGoal copy(androidx.health.services.client.data.ExerciseGoalType exerciseGoalType, androidx.health.services.client.data.DataTypeCondition dataTypeCondition, androidx.health.services.client.data.Value? period);
+    method public static androidx.health.services.client.data.ExerciseGoal createMilestone(androidx.health.services.client.data.DataTypeCondition condition, androidx.health.services.client.data.Value period);
+    method public static androidx.health.services.client.data.ExerciseGoal createMilestoneGoalWithUpdatedThreshold(androidx.health.services.client.data.ExerciseGoal goal, androidx.health.services.client.data.Value newThreshold);
+    method public static androidx.health.services.client.data.ExerciseGoal createOneTimeGoal(androidx.health.services.client.data.DataTypeCondition condition);
+    method public int describeContents();
+    method public androidx.health.services.client.data.DataTypeCondition getDataTypeCondition();
+    method public androidx.health.services.client.data.ExerciseGoalType getExerciseGoalType();
+    method public androidx.health.services.client.data.Value? getPeriod();
+    method public void writeToParcel(android.os.Parcel dest, int flags);
+    property public final androidx.health.services.client.data.DataTypeCondition dataTypeCondition;
+    property public final androidx.health.services.client.data.ExerciseGoalType exerciseGoalType;
+    property public final androidx.health.services.client.data.Value? period;
+    field public static final android.os.Parcelable.Creator<androidx.health.services.client.data.ExerciseGoal> CREATOR;
+    field public static final androidx.health.services.client.data.ExerciseGoal.Companion Companion;
+  }
+
+  public static final class ExerciseGoal.Companion {
+    method public androidx.health.services.client.data.ExerciseGoal createMilestone(androidx.health.services.client.data.DataTypeCondition condition, androidx.health.services.client.data.Value period);
+    method public androidx.health.services.client.data.ExerciseGoal createMilestoneGoalWithUpdatedThreshold(androidx.health.services.client.data.ExerciseGoal goal, androidx.health.services.client.data.Value newThreshold);
+    method public androidx.health.services.client.data.ExerciseGoal createOneTimeGoal(androidx.health.services.client.data.DataTypeCondition condition);
+  }
+
+  public enum ExerciseGoalType {
+    method public static final androidx.health.services.client.data.ExerciseGoalType? fromId(int id);
+    method public final int getId();
+    property public final int id;
+    enum_constant public static final androidx.health.services.client.data.ExerciseGoalType MILESTONE;
+    enum_constant public static final androidx.health.services.client.data.ExerciseGoalType ONE_TIME_GOAL;
+    field public static final androidx.health.services.client.data.ExerciseGoalType.Companion Companion;
+  }
+
+  public static final class ExerciseGoalType.Companion {
+    method public androidx.health.services.client.data.ExerciseGoalType? fromId(int id);
+  }
+
+  public final class ExerciseInfo implements android.os.Parcelable {
+    ctor public ExerciseInfo(androidx.health.services.client.data.ExerciseTrackedStatus exerciseTrackedStatus, androidx.health.services.client.data.ExerciseType exerciseType);
+    method public androidx.health.services.client.data.ExerciseTrackedStatus component1();
+    method public androidx.health.services.client.data.ExerciseType component2();
+    method public androidx.health.services.client.data.ExerciseInfo copy(androidx.health.services.client.data.ExerciseTrackedStatus exerciseTrackedStatus, androidx.health.services.client.data.ExerciseType exerciseType);
+    method public int describeContents();
+    method public androidx.health.services.client.data.ExerciseTrackedStatus getExerciseTrackedStatus();
+    method public androidx.health.services.client.data.ExerciseType getExerciseType();
+    method public void writeToParcel(android.os.Parcel dest, int flags);
+    property public final androidx.health.services.client.data.ExerciseTrackedStatus exerciseTrackedStatus;
+    property public final androidx.health.services.client.data.ExerciseType exerciseType;
+    field public static final android.os.Parcelable.Creator<androidx.health.services.client.data.ExerciseInfo> CREATOR;
+    field public static final androidx.health.services.client.data.ExerciseInfo.Companion Companion;
+  }
+
+  public static final class ExerciseInfo.Companion {
+  }
+
+  public final class ExerciseLapSummary implements android.os.Parcelable {
+    ctor public ExerciseLapSummary(int lapCount, java.time.Instant startTime, java.time.Instant endTime, java.time.Duration activeDuration, java.util.Map<androidx.health.services.client.data.DataType,androidx.health.services.client.data.DataPoint> lapMetrics);
+    method public int component1();
+    method public java.time.Instant component2();
+    method public java.time.Instant component3();
+    method public java.time.Duration component4();
+    method public java.util.Map<androidx.health.services.client.data.DataType,androidx.health.services.client.data.DataPoint> component5();
+    method public androidx.health.services.client.data.ExerciseLapSummary copy(int lapCount, java.time.Instant startTime, java.time.Instant endTime, java.time.Duration activeDuration, java.util.Map<androidx.health.services.client.data.DataType,androidx.health.services.client.data.DataPoint> lapMetrics);
+    method public int describeContents();
+    method public java.time.Duration getActiveDuration();
+    method public java.time.Instant getEndTime();
+    method public int getLapCount();
+    method public java.util.Map<androidx.health.services.client.data.DataType,androidx.health.services.client.data.DataPoint> getLapMetrics();
+    method public java.time.Instant getStartTime();
+    method public void writeToParcel(android.os.Parcel dest, int flags);
+    property public final java.time.Duration activeDuration;
+    property public final java.time.Instant endTime;
+    property public final int lapCount;
+    property public final java.util.Map<androidx.health.services.client.data.DataType,androidx.health.services.client.data.DataPoint> lapMetrics;
+    property public final java.time.Instant startTime;
+    field public static final android.os.Parcelable.Creator<androidx.health.services.client.data.ExerciseLapSummary> CREATOR;
+    field public static final androidx.health.services.client.data.ExerciseLapSummary.Companion Companion;
+  }
+
+  public static final class ExerciseLapSummary.Companion {
+  }
+
+  public enum ExerciseState {
+    method public static final androidx.health.services.client.data.ExerciseState? fromId(int id);
+    method public final int getId();
+    method public final boolean isEnded();
+    method public final boolean isPaused();
+    method public final boolean isResuming();
+    property public final int id;
+    property public final boolean isEnded;
+    property public final boolean isPaused;
+    property public final boolean isResuming;
+    enum_constant public static final androidx.health.services.client.data.ExerciseState ACTIVE;
+    enum_constant public static final androidx.health.services.client.data.ExerciseState AUTO_ENDED;
+    enum_constant public static final androidx.health.services.client.data.ExerciseState AUTO_ENDING;
+    enum_constant public static final androidx.health.services.client.data.ExerciseState AUTO_PAUSED;
+    enum_constant public static final androidx.health.services.client.data.ExerciseState AUTO_PAUSING;
+    enum_constant public static final androidx.health.services.client.data.ExerciseState AUTO_RESUMING;
+    enum_constant public static final androidx.health.services.client.data.ExerciseState TERMINATED;
+    enum_constant public static final androidx.health.services.client.data.ExerciseState TERMINATING;
+    enum_constant public static final androidx.health.services.client.data.ExerciseState USER_ENDED;
+    enum_constant public static final androidx.health.services.client.data.ExerciseState USER_ENDING;
+    enum_constant public static final androidx.health.services.client.data.ExerciseState USER_PAUSED;
+    enum_constant public static final androidx.health.services.client.data.ExerciseState USER_PAUSING;
+    enum_constant public static final androidx.health.services.client.data.ExerciseState USER_RESUMING;
+    enum_constant public static final androidx.health.services.client.data.ExerciseState USER_STARTING;
+    field public static final androidx.health.services.client.data.ExerciseState.Companion Companion;
+  }
+
+  public static final class ExerciseState.Companion {
+    method public androidx.health.services.client.data.ExerciseState? fromId(int id);
+  }
+
+  public enum ExerciseTrackedStatus {
+    method public static final androidx.health.services.client.data.ExerciseTrackedStatus? fromId(int id);
+    method public final int getId();
+    property public final int id;
+    enum_constant public static final androidx.health.services.client.data.ExerciseTrackedStatus NO_EXERCISE_IN_PROGRESS;
+    enum_constant public static final androidx.health.services.client.data.ExerciseTrackedStatus OTHER_APP_IN_PROGRESS;
+    enum_constant public static final androidx.health.services.client.data.ExerciseTrackedStatus OWNED_EXERCISE_IN_PROGRESS;
+    field public static final androidx.health.services.client.data.ExerciseTrackedStatus.Companion Companion;
+  }
+
+  public static final class ExerciseTrackedStatus.Companion {
+    method public androidx.health.services.client.data.ExerciseTrackedStatus? fromId(int id);
+  }
+
+  public enum ExerciseType {
+    method public static final androidx.health.services.client.data.ExerciseType fromId(int id);
+    method public final int getId();
+    property public final int id;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType BACK_EXTENSION;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType BADMINTON;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType BARBELL_SHOULDER_PRESS;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType BASEBALL;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType BASKETBALL;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType BENCH_PRESS;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType BENCH_SIT_UP;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType BIKING;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType BIKING_STATIONARY;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType BOOT_CAMP;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType BOXING;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType BURPEE;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType CALISTHENICS;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType CRICKET;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType CRUNCH;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType DANCING;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType DEADLIFT;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType DUMBBELL_CURL_LEFT_ARM;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType DUMBBELL_CURL_RIGHT_ARM;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType DUMBBELL_FRONT_RAISE;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType DUMBBELL_LATERAL_RAISE;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType DUMBBELL_TRICEPS_EXTENSION_LEFT_ARM;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType DUMBBELL_TRICEPS_EXTENSION_RIGHT_ARM;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType DUMBBELL_TRICEPS_EXTENSION_TWO_ARM;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType ELLIPTICAL;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType EXERCISE_CLASS;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType FENCING;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType FOOTBALL_AMERICAN;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType FOOTBALL_AUSTRALIAN;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType FRISBEE_DISC;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType GOLF;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType GUIDED_BREATHING;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType GYNMASTICS;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType HANDBALL;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType HIGH_INTENSITY_INTERVAL_TRAINING;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType HIKING;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType ICE_HOCKEY;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType ICE_SKATING;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType JUMPING_JACK;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType JUMP_ROPE;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType LAT_PULL_DOWN;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType LUNGE;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType MARTIAL_ARTS;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType MEDITATION;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType PADDLING;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType PARA_GLIDING;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType PILATES;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType PLANK;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType RACQUETBALL;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType ROCK_CLIMBING;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType ROLLER_HOCKEY;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType ROWING;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType ROWING_MACHINE;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType RUGBY;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType RUNNING;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType RUNNING_TREADMILL;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType SAILING;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType SCUBA_DIVING;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType SKATING;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType SKIING;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType SNOWBOARDING;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType SNOWSHOEING;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType SOCCER;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType SOFTBALL;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType SQUASH;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType SQUAT;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType STAIR_CLIMBING;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType STAIR_CLIMBING_MACHINE;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType STRENGTH_TRAINING;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType STRETCHING;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType SURFING;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType SWIMMING_OPEN_WATER;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType SWIMMING_POOL;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType TABLE_TENNIS;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType TENNIS;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType UNKNOWN;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType VOLLEYBALL;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType WALKING;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType WATER_POLO;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType WEIGHTLIFTING;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType WORKOUT_INDOOR;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType WORKOUT_OUTDOOR;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType YOGA;
+    field public static final androidx.health.services.client.data.ExerciseType.Companion Companion;
+  }
+
+  public static final class ExerciseType.Companion {
+    method public androidx.health.services.client.data.ExerciseType fromId(int id);
+  }
+
+  public final class ExerciseUpdate implements android.os.Parcelable {
+    ctor public ExerciseUpdate(androidx.health.services.client.data.ExerciseState state, java.time.Instant startTime, java.time.Duration activeDuration, java.util.Map<androidx.health.services.client.data.DataType,? extends java.util.List<androidx.health.services.client.data.DataPoint>> latestMetrics, java.util.Set<androidx.health.services.client.data.AchievedExerciseGoal> latestAchievedGoals, java.util.Set<androidx.health.services.client.data.MilestoneMarkerSummary> latestMilestoneMarkerSummaries, androidx.health.services.client.data.ExerciseConfig? exerciseConfig, androidx.health.services.client.data.AutoExerciseConfig? autoExerciseConfig, androidx.health.services.client.data.ExerciseType? autoExerciseDetected);
+    method public androidx.health.services.client.data.ExerciseState component1();
+    method public java.time.Instant component2();
+    method public java.time.Duration component3();
+    method public java.util.Map<androidx.health.services.client.data.DataType,java.util.List<androidx.health.services.client.data.DataPoint>> component4();
+    method public java.util.Set<androidx.health.services.client.data.AchievedExerciseGoal> component5();
+    method public java.util.Set<androidx.health.services.client.data.MilestoneMarkerSummary> component6();
+    method public androidx.health.services.client.data.ExerciseConfig? component7();
+    method public androidx.health.services.client.data.AutoExerciseConfig? component8();
+    method public androidx.health.services.client.data.ExerciseType? component9();
+    method public androidx.health.services.client.data.ExerciseUpdate copy(androidx.health.services.client.data.ExerciseState state, java.time.Instant startTime, java.time.Duration activeDuration, java.util.Map<androidx.health.services.client.data.DataType,? extends java.util.List<androidx.health.services.client.data.DataPoint>> latestMetrics, java.util.Set<androidx.health.services.client.data.AchievedExerciseGoal> latestAchievedGoals, java.util.Set<androidx.health.services.client.data.MilestoneMarkerSummary> latestMilestoneMarkerSummaries, androidx.health.services.client.data.ExerciseConfig? exerciseConfig, androidx.health.services.client.data.AutoExerciseConfig? autoExerciseConfig, androidx.health.services.client.data.ExerciseType? autoExerciseDetected);
+    method public int describeContents();
+    method public java.time.Duration getActiveDuration();
+    method public androidx.health.services.client.data.AutoExerciseConfig? getAutoExerciseConfig();
+    method public androidx.health.services.client.data.ExerciseType? getAutoExerciseDetected();
+    method public androidx.health.services.client.data.ExerciseConfig? getExerciseConfig();
+    method public androidx.health.services.client.data.ExerciseUpdate.ExerciseSessionType getExerciseSessionType();
+    method public java.util.Set<androidx.health.services.client.data.AchievedExerciseGoal> getLatestAchievedGoals();
+    method public java.util.Map<androidx.health.services.client.data.DataType,java.util.List<androidx.health.services.client.data.DataPoint>> getLatestMetrics();
+    method public java.util.Set<androidx.health.services.client.data.MilestoneMarkerSummary> getLatestMilestoneMarkerSummaries();
+    method public java.time.Instant getStartTime();
+    method public androidx.health.services.client.data.ExerciseState getState();
+    method public void writeToParcel(android.os.Parcel dest, int flags);
+    property public final java.time.Duration activeDuration;
+    property public final androidx.health.services.client.data.AutoExerciseConfig? autoExerciseConfig;
+    property public final androidx.health.services.client.data.ExerciseType? autoExerciseDetected;
+    property public final androidx.health.services.client.data.ExerciseConfig? exerciseConfig;
+    property public final java.util.Set<androidx.health.services.client.data.AchievedExerciseGoal> latestAchievedGoals;
+    property public final java.util.Map<androidx.health.services.client.data.DataType,java.util.List<androidx.health.services.client.data.DataPoint>> latestMetrics;
+    property public final java.util.Set<androidx.health.services.client.data.MilestoneMarkerSummary> latestMilestoneMarkerSummaries;
+    property public final java.time.Instant startTime;
+    property public final androidx.health.services.client.data.ExerciseState state;
+    field public static final android.os.Parcelable.Creator<androidx.health.services.client.data.ExerciseUpdate> CREATOR;
+    field public static final androidx.health.services.client.data.ExerciseUpdate.Companion Companion;
+  }
+
+  public static final class ExerciseUpdate.Companion {
+  }
+
+  public enum ExerciseUpdate.ExerciseSessionType {
+    enum_constant public static final androidx.health.services.client.data.ExerciseUpdate.ExerciseSessionType AUTO_EXERCISE_DETECTION;
+    enum_constant public static final androidx.health.services.client.data.ExerciseUpdate.ExerciseSessionType MANUALLY_STARTED_EXERCISE;
+  }
+
+  public final class MeasureCapabilities implements android.os.Parcelable {
+    ctor public MeasureCapabilities(java.util.Set<androidx.health.services.client.data.DataType> supportedDataTypesMeasure);
+    method public java.util.Set<androidx.health.services.client.data.DataType> component1();
+    method public androidx.health.services.client.data.MeasureCapabilities copy(java.util.Set<androidx.health.services.client.data.DataType> supportedDataTypesMeasure);
+    method public int describeContents();
+    method public java.util.Set<androidx.health.services.client.data.DataType> getSupportedDataTypesMeasure();
+    method public void writeToParcel(android.os.Parcel dest, int flags);
+    property public final java.util.Set<androidx.health.services.client.data.DataType> supportedDataTypesMeasure;
+    field public static final android.os.Parcelable.Creator<androidx.health.services.client.data.MeasureCapabilities> CREATOR;
+    field public static final androidx.health.services.client.data.MeasureCapabilities.Companion Companion;
+  }
+
+  public static final class MeasureCapabilities.Companion {
+  }
+
+  public final class MilestoneMarkerSummary implements android.os.Parcelable {
+    ctor public MilestoneMarkerSummary(java.time.Instant startTime, java.time.Instant endTime, java.time.Duration activeDuration, androidx.health.services.client.data.AchievedExerciseGoal achievedGoal, java.util.Map<androidx.health.services.client.data.DataType,androidx.health.services.client.data.DataPoint> summaryMetrics);
+    method public java.time.Instant component1();
+    method public java.time.Instant component2();
+    method public java.time.Duration component3();
+    method public androidx.health.services.client.data.AchievedExerciseGoal component4();
+    method public java.util.Map<androidx.health.services.client.data.DataType,androidx.health.services.client.data.DataPoint> component5();
+    method public androidx.health.services.client.data.MilestoneMarkerSummary copy(java.time.Instant startTime, java.time.Instant endTime, java.time.Duration activeDuration, androidx.health.services.client.data.AchievedExerciseGoal achievedGoal, java.util.Map<androidx.health.services.client.data.DataType,androidx.health.services.client.data.DataPoint> summaryMetrics);
+    method public int describeContents();
+    method public androidx.health.services.client.data.AchievedExerciseGoal getAchievedGoal();
+    method public java.time.Duration getActiveDuration();
+    method public java.time.Instant getEndTime();
+    method public java.time.Instant getStartTime();
+    method public java.util.Map<androidx.health.services.client.data.DataType,androidx.health.services.client.data.DataPoint> getSummaryMetrics();
+    method public void writeToParcel(android.os.Parcel dest, int flags);
+    property public final androidx.health.services.client.data.AchievedExerciseGoal achievedGoal;
+    property public final java.time.Duration activeDuration;
+    property public final java.time.Instant endTime;
+    property public final java.time.Instant startTime;
+    property public final java.util.Map<androidx.health.services.client.data.DataType,androidx.health.services.client.data.DataPoint> summaryMetrics;
+    field public static final android.os.Parcelable.Creator<androidx.health.services.client.data.MilestoneMarkerSummary> CREATOR;
+    field public static final androidx.health.services.client.data.MilestoneMarkerSummary.Companion Companion;
+  }
+
+  public static final class MilestoneMarkerSummary.Companion {
+  }
+
+  public final class PassiveActivityState implements android.os.Parcelable {
+    ctor public PassiveActivityState(java.util.List<androidx.health.services.client.data.DataPoint> dataPoints, androidx.health.services.client.data.UserActivityState userActivityState, androidx.health.services.client.data.ExerciseType? exerciseType, java.time.Instant stateChangeTime);
+    method public java.util.List<androidx.health.services.client.data.DataPoint> component1();
+    method public androidx.health.services.client.data.UserActivityState component2();
+    method public androidx.health.services.client.data.ExerciseType? component3();
+    method public java.time.Instant component4();
+    method public androidx.health.services.client.data.PassiveActivityState copy(java.util.List<androidx.health.services.client.data.DataPoint> dataPoints, androidx.health.services.client.data.UserActivityState userActivityState, androidx.health.services.client.data.ExerciseType? exerciseType, java.time.Instant stateChangeTime);
+    method public static androidx.health.services.client.data.PassiveActivityState createActiveExerciseState(java.util.List<androidx.health.services.client.data.DataPoint> dataPoints, androidx.health.services.client.data.ExerciseType exerciseType, java.time.Instant stateChangeTime);
+    method public static androidx.health.services.client.data.PassiveActivityState createInactiveState(java.util.List<androidx.health.services.client.data.DataPoint> dataPoints, java.time.Instant stateChangeTime);
+    method public static androidx.health.services.client.data.PassiveActivityState createPassiveActivityState(java.util.List<androidx.health.services.client.data.DataPoint> dataPoints, java.time.Instant stateChangeTime);
+    method public static androidx.health.services.client.data.PassiveActivityState createUnknownTypeState(java.util.List<androidx.health.services.client.data.DataPoint> dataPoints, java.time.Instant stateChangeTime);
+    method public int describeContents();
+    method public static androidx.health.services.client.data.PassiveActivityState? fromIntent(android.content.Intent intent);
+    method public java.util.List<androidx.health.services.client.data.DataPoint> getDataPoints();
+    method public androidx.health.services.client.data.ExerciseType? getExerciseType();
+    method public java.time.Instant getStateChangeTime();
+    method public androidx.health.services.client.data.UserActivityState getUserActivityState();
+    method public void putToIntent(android.content.Intent intent);
+    method public void writeToParcel(android.os.Parcel dest, int flags);
+    property public final java.util.List<androidx.health.services.client.data.DataPoint> dataPoints;
+    property public final androidx.health.services.client.data.ExerciseType? exerciseType;
+    property public final java.time.Instant stateChangeTime;
+    property public final androidx.health.services.client.data.UserActivityState userActivityState;
+    field public static final android.os.Parcelable.Creator<androidx.health.services.client.data.PassiveActivityState> CREATOR;
+    field public static final androidx.health.services.client.data.PassiveActivityState.Companion Companion;
+  }
+
+  public static final class PassiveActivityState.Companion {
+    method public androidx.health.services.client.data.PassiveActivityState createActiveExerciseState(java.util.List<androidx.health.services.client.data.DataPoint> dataPoints, androidx.health.services.client.data.ExerciseType exerciseType, java.time.Instant stateChangeTime);
+    method public androidx.health.services.client.data.PassiveActivityState createInactiveState(java.util.List<androidx.health.services.client.data.DataPoint> dataPoints, java.time.Instant stateChangeTime);
+    method public androidx.health.services.client.data.PassiveActivityState createPassiveActivityState(java.util.List<androidx.health.services.client.data.DataPoint> dataPoints, java.time.Instant stateChangeTime);
+    method public androidx.health.services.client.data.PassiveActivityState createUnknownTypeState(java.util.List<androidx.health.services.client.data.DataPoint> dataPoints, java.time.Instant stateChangeTime);
+    method public androidx.health.services.client.data.PassiveActivityState? fromIntent(android.content.Intent intent);
+  }
+
+  public final class PassiveMonitoringCapabilities implements android.os.Parcelable {
+    ctor public PassiveMonitoringCapabilities(java.util.Set<androidx.health.services.client.data.DataType> supportedDataTypesPassiveMonitoring, java.util.Set<androidx.health.services.client.data.DataType> supportedDataTypesEvents);
+    method public java.util.Set<androidx.health.services.client.data.DataType> component1();
+    method public java.util.Set<androidx.health.services.client.data.DataType> component2();
+    method public androidx.health.services.client.data.PassiveMonitoringCapabilities copy(java.util.Set<androidx.health.services.client.data.DataType> supportedDataTypesPassiveMonitoring, java.util.Set<androidx.health.services.client.data.DataType> supportedDataTypesEvents);
+    method public int describeContents();
+    method public java.util.Set<androidx.health.services.client.data.DataType> getSupportedDataTypesEvents();
+    method public java.util.Set<androidx.health.services.client.data.DataType> getSupportedDataTypesPassiveMonitoring();
+    method public void writeToParcel(android.os.Parcel dest, int flags);
+    property public final java.util.Set<androidx.health.services.client.data.DataType> supportedDataTypesEvents;
+    property public final java.util.Set<androidx.health.services.client.data.DataType> supportedDataTypesPassiveMonitoring;
+    field public static final android.os.Parcelable.Creator<androidx.health.services.client.data.PassiveMonitoringCapabilities> CREATOR;
+    field public static final androidx.health.services.client.data.PassiveMonitoringCapabilities.Companion Companion;
+  }
+
+  public static final class PassiveMonitoringCapabilities.Companion {
+  }
+
+  public enum UserActivityState {
+    method public static final androidx.health.services.client.data.UserActivityState? fromId(int id);
+    method public final int getId();
+    property public final int id;
+    enum_constant public static final androidx.health.services.client.data.UserActivityState USER_ACTIVITY_EXERCISE;
+    enum_constant public static final androidx.health.services.client.data.UserActivityState USER_ACTIVITY_INACTIVE;
+    enum_constant public static final androidx.health.services.client.data.UserActivityState USER_ACTIVITY_PASSIVE;
+    enum_constant public static final androidx.health.services.client.data.UserActivityState USER_ACTIVITY_UNKNOWN;
+    field public static final androidx.health.services.client.data.UserActivityState.Companion Companion;
+  }
+
+  public static final class UserActivityState.Companion {
+    method public androidx.health.services.client.data.UserActivityState? fromId(int id);
+  }
+
+  public final class Value implements android.os.Parcelable {
+    method public boolean asBoolean();
+    method public double asDouble();
+    method public double[] asDoubleArray();
+    method public long asLong();
+    method public int component1();
+    method public java.util.List<java.lang.Double> component2();
+    method public long component3();
+    method public androidx.health.services.client.data.Value copy(int format, java.util.List<java.lang.Double> doubleList, long longValue);
+    method public int describeContents();
+    method public java.util.List<java.lang.Double> getDoubleList();
+    method public int getFormat();
+    method public long getLongValue();
+    method public boolean isBoolean();
+    method public boolean isDouble();
+    method public boolean isDoubleArray();
+    method public boolean isLong();
+    method public static androidx.health.services.client.data.Value ofBoolean(boolean value);
+    method public static androidx.health.services.client.data.Value ofDouble(double value);
+    method public static androidx.health.services.client.data.Value ofDoubleArray(double... doubleArray);
+    method public static androidx.health.services.client.data.Value ofLong(long value);
+    method public static androidx.health.services.client.data.Value sum(androidx.health.services.client.data.Value first, androidx.health.services.client.data.Value second);
+    method public void writeToParcel(android.os.Parcel dest, int flags);
+    property public final java.util.List<java.lang.Double> doubleList;
+    property public final int format;
+    property public final boolean isBoolean;
+    property public final boolean isDouble;
+    property public final boolean isDoubleArray;
+    property public final boolean isLong;
+    property public final long longValue;
+    field public static final android.os.Parcelable.Creator<androidx.health.services.client.data.Value> CREATOR;
+    field public static final androidx.health.services.client.data.Value.Companion Companion;
+    field public static final int FORMAT_BOOLEAN = 4; // 0x4
+    field public static final int FORMAT_DOUBLE = 1; // 0x1
+    field public static final int FORMAT_DOUBLE_ARRAY = 3; // 0x3
+    field public static final int FORMAT_LONG = 2; // 0x2
+  }
+
+  public static final class Value.Companion {
+    method public androidx.health.services.client.data.Value ofBoolean(boolean value);
+    method public androidx.health.services.client.data.Value ofDouble(double value);
+    method public androidx.health.services.client.data.Value ofDoubleArray(double... doubleArray);
+    method public androidx.health.services.client.data.Value ofLong(long value);
+    method public androidx.health.services.client.data.Value sum(androidx.health.services.client.data.Value first, androidx.health.services.client.data.Value second);
+  }
+
+}
+
+package androidx.health.services.client.data.event {
+
+  public final class Event implements android.os.Parcelable {
+    ctor public Event(androidx.health.services.client.data.DataTypeCondition dataTypeCondition, androidx.health.services.client.data.event.Event.TriggerType triggerType);
+    method public androidx.health.services.client.data.DataTypeCondition component1();
+    method public androidx.health.services.client.data.event.Event.TriggerType component2();
+    method public androidx.health.services.client.data.event.Event copy(androidx.health.services.client.data.DataTypeCondition dataTypeCondition, androidx.health.services.client.data.event.Event.TriggerType triggerType);
+    method public int describeContents();
+    method public androidx.health.services.client.data.DataTypeCondition getDataTypeCondition();
+    method public androidx.health.services.client.data.event.Event.TriggerType getTriggerType();
+    method public boolean isTriggered(androidx.health.services.client.data.DataPoint dataPoint);
+    method public void writeToParcel(android.os.Parcel dest, int flags);
+    property public final androidx.health.services.client.data.DataTypeCondition dataTypeCondition;
+    property public final androidx.health.services.client.data.event.Event.TriggerType triggerType;
+    field public static final android.os.Parcelable.Creator<androidx.health.services.client.data.event.Event> CREATOR;
+    field public static final androidx.health.services.client.data.event.Event.Companion Companion;
+  }
+
+  public static final class Event.Companion {
+  }
+
+  public enum Event.TriggerType {
+    method public static final androidx.health.services.client.data.event.Event.TriggerType? fromId(int id);
+    method public final int getId();
+    property public final int id;
+    enum_constant public static final androidx.health.services.client.data.event.Event.TriggerType ONCE;
+    enum_constant public static final androidx.health.services.client.data.event.Event.TriggerType REPEATED;
+    field public static final androidx.health.services.client.data.event.Event.TriggerType.Companion Companion;
+  }
+
+  public static final class Event.TriggerType.Companion {
+    method public androidx.health.services.client.data.event.Event.TriggerType? fromId(int id);
+  }
+
+}
+
diff --git a/health/health-services-client/api/res-current.txt b/health/health-services-client/api/res-current.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/health/health-services-client/api/res-current.txt
diff --git a/health/health-services-client/api/restricted_current.txt b/health/health-services-client/api/restricted_current.txt
new file mode 100644
index 0000000..6abe37c
--- /dev/null
+++ b/health/health-services-client/api/restricted_current.txt
@@ -0,0 +1,869 @@
+// Signature format: 4.0
+package androidx.health.services.client {
+
+  public interface ExerciseClient {
+    method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> addGoalToActiveExercise(androidx.health.services.client.data.ExerciseGoal exerciseGoal);
+    method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> clearUpdateListener(androidx.health.services.client.ExerciseUpdateListener listener);
+    method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> endExercise();
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.health.services.client.data.Capabilities> getCapabilities();
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.health.services.client.data.ExerciseInfo> getCurrentExerciseInfo();
+    method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> markLap();
+    method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> overrideAutoPauseAndResumeForActiveExercise(boolean enabled);
+    method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> pauseExercise();
+    method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> resumeExercise();
+    method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> setUpdateListener(androidx.health.services.client.ExerciseUpdateListener listener);
+    method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> setUpdateListener(androidx.health.services.client.ExerciseUpdateListener listener, java.util.concurrent.Executor executor);
+    method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> startExercise(androidx.health.services.client.data.ExerciseConfig configuration);
+    property public abstract com.google.common.util.concurrent.ListenableFuture<androidx.health.services.client.data.Capabilities> capabilities;
+    property public abstract com.google.common.util.concurrent.ListenableFuture<androidx.health.services.client.data.ExerciseInfo> currentExerciseInfo;
+  }
+
+  public interface ExerciseUpdateListener {
+    method public void onExerciseUpdate(androidx.health.services.client.data.ExerciseUpdate update);
+    method public void onLapSummary(androidx.health.services.client.data.ExerciseLapSummary lapSummary);
+  }
+
+  public final class HealthServices {
+    method public static androidx.health.services.client.HealthServicesClient getClient(android.content.Context context);
+    field public static final androidx.health.services.client.HealthServices INSTANCE;
+  }
+
+  public interface HealthServicesClient {
+    method public androidx.health.services.client.ExerciseClient getExerciseClient();
+    method public androidx.health.services.client.MeasureClient getMeasureClient();
+    method public androidx.health.services.client.PassiveMonitoringClient getPassiveMonitoringClient();
+    property public abstract androidx.health.services.client.ExerciseClient exerciseClient;
+    property public abstract androidx.health.services.client.MeasureClient measureClient;
+    property public abstract androidx.health.services.client.PassiveMonitoringClient passiveMonitoringClient;
+  }
+
+  public interface MeasureCallback {
+    method public void onAvailabilityChanged(androidx.health.services.client.data.DataType dataType, androidx.health.services.client.data.Availability availability);
+    method public void onData(java.util.List<androidx.health.services.client.data.DataPoint> data);
+  }
+
+  public interface MeasureClient {
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.health.services.client.data.MeasureCapabilities> getCapabilities();
+    method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> registerCallback(androidx.health.services.client.data.DataType dataType, androidx.health.services.client.MeasureCallback callback);
+    method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> registerCallback(androidx.health.services.client.data.DataType dataType, androidx.health.services.client.MeasureCallback callback, java.util.concurrent.Executor executor);
+    method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> unregisterCallback(androidx.health.services.client.data.DataType dataType, androidx.health.services.client.MeasureCallback callback);
+    property public abstract com.google.common.util.concurrent.ListenableFuture<androidx.health.services.client.data.MeasureCapabilities> capabilities;
+  }
+
+  public interface PassiveMonitoringCallback {
+    method public void onPassiveActivityState(androidx.health.services.client.data.PassiveActivityState state);
+  }
+
+  public interface PassiveMonitoringClient {
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.health.services.client.data.PassiveMonitoringCapabilities> getCapabilities();
+    method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> registerDataCallback(java.util.Set<androidx.health.services.client.data.DataType> dataTypes, android.app.PendingIntent callbackIntent);
+    method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> registerDataCallback(java.util.Set<androidx.health.services.client.data.DataType> dataTypes, android.app.PendingIntent callbackIntent, androidx.health.services.client.PassiveMonitoringCallback callback);
+    method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> registerEventCallback(androidx.health.services.client.data.event.Event event, android.app.PendingIntent callbackIntent);
+    method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> unregisterDataCallback();
+    method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> unregisterEventCallback(androidx.health.services.client.data.event.Event event);
+    property public abstract com.google.common.util.concurrent.ListenableFuture<androidx.health.services.client.data.PassiveMonitoringCapabilities> capabilities;
+  }
+
+}
+
+package androidx.health.services.client.data {
+
+  public final class AchievedExerciseGoal implements android.os.Parcelable {
+    ctor public AchievedExerciseGoal(androidx.health.services.client.data.ExerciseGoal goal);
+    method public androidx.health.services.client.data.ExerciseGoal component1();
+    method public androidx.health.services.client.data.AchievedExerciseGoal copy(androidx.health.services.client.data.ExerciseGoal goal);
+    method public int describeContents();
+    method public androidx.health.services.client.data.ExerciseGoal getGoal();
+    method public void writeToParcel(android.os.Parcel dest, int flags);
+    property public final androidx.health.services.client.data.ExerciseGoal goal;
+    field public static final android.os.Parcelable.Creator<androidx.health.services.client.data.AchievedExerciseGoal> CREATOR;
+    field public static final androidx.health.services.client.data.AchievedExerciseGoal.Companion Companion;
+  }
+
+  public static final class AchievedExerciseGoal.Companion {
+  }
+
+  public final class AutoExerciseConfig implements android.os.Parcelable {
+    ctor public AutoExerciseConfig(optional java.util.Set<? extends androidx.health.services.client.data.ExerciseType> exercisesToDetect, optional android.app.PendingIntent? launchIntent, optional android.os.Bundle exerciseParams);
+    ctor public AutoExerciseConfig(optional java.util.Set<? extends androidx.health.services.client.data.ExerciseType> exercisesToDetect, optional android.app.PendingIntent? launchIntent);
+    ctor public AutoExerciseConfig(optional java.util.Set<? extends androidx.health.services.client.data.ExerciseType> exercisesToDetect);
+    method public java.util.Set<androidx.health.services.client.data.ExerciseType> component1();
+    method public android.app.PendingIntent? component2();
+    method public android.os.Bundle component3();
+    method public androidx.health.services.client.data.AutoExerciseConfig copy(java.util.Set<? extends androidx.health.services.client.data.ExerciseType> exercisesToDetect, android.app.PendingIntent? launchIntent, android.os.Bundle exerciseParams);
+    method public int describeContents();
+    method public android.os.Bundle getExerciseParams();
+    method public java.util.Set<androidx.health.services.client.data.ExerciseType> getExercisesToDetect();
+    method public android.app.PendingIntent? getLaunchIntent();
+    method public void writeToParcel(android.os.Parcel dest, int flags);
+    property public final android.os.Bundle exerciseParams;
+    property public final java.util.Set<androidx.health.services.client.data.ExerciseType> exercisesToDetect;
+    property public final android.app.PendingIntent? launchIntent;
+    field public static final android.os.Parcelable.Creator<androidx.health.services.client.data.AutoExerciseConfig> CREATOR;
+    field public static final androidx.health.services.client.data.AutoExerciseConfig.Companion Companion;
+  }
+
+  public static final class AutoExerciseConfig.Companion {
+  }
+
+  public enum Availability {
+    method public static final androidx.health.services.client.data.Availability? fromId(int id);
+    method public final int getId();
+    property public final int id;
+    enum_constant public static final androidx.health.services.client.data.Availability ACQUIRING;
+    enum_constant public static final androidx.health.services.client.data.Availability AVAILABLE;
+    enum_constant public static final androidx.health.services.client.data.Availability UNAVAILABLE;
+    enum_constant public static final androidx.health.services.client.data.Availability UNKNOWN;
+    field public static final androidx.health.services.client.data.Availability.Companion Companion;
+  }
+
+  public static final class Availability.Companion {
+    method public androidx.health.services.client.data.Availability? fromId(int id);
+  }
+
+  public final class Capabilities implements android.os.Parcelable {
+    ctor public Capabilities(java.util.Map<androidx.health.services.client.data.ExerciseType,androidx.health.services.client.data.ExerciseCapabilities> exerciseTypeToExerciseCapabilities);
+    method public java.util.Map<androidx.health.services.client.data.ExerciseType,androidx.health.services.client.data.ExerciseCapabilities> component1();
+    method public androidx.health.services.client.data.Capabilities copy(java.util.Map<androidx.health.services.client.data.ExerciseType,androidx.health.services.client.data.ExerciseCapabilities> exerciseTypeToExerciseCapabilities);
+    method public int describeContents();
+    method public java.util.Set<androidx.health.services.client.data.ExerciseType> getAutoPauseAndResumeEnabledExercises();
+    method public androidx.health.services.client.data.ExerciseCapabilities getExerciseCapabilities(androidx.health.services.client.data.ExerciseType exercise);
+    method public java.util.Map<androidx.health.services.client.data.ExerciseType,androidx.health.services.client.data.ExerciseCapabilities> getExerciseTypeToExerciseCapabilities();
+    method public java.util.Set<androidx.health.services.client.data.ExerciseType> getSupportedExerciseTypes();
+    method public void writeToParcel(android.os.Parcel dest, int flags);
+    property public final java.util.Set<androidx.health.services.client.data.ExerciseType> autoPauseAndResumeEnabledExercises;
+    property public final java.util.Map<androidx.health.services.client.data.ExerciseType,androidx.health.services.client.data.ExerciseCapabilities> exerciseTypeToExerciseCapabilities;
+    property public final java.util.Set<androidx.health.services.client.data.ExerciseType> supportedExerciseTypes;
+    field public static final android.os.Parcelable.Creator<androidx.health.services.client.data.Capabilities> CREATOR;
+    field public static final androidx.health.services.client.data.Capabilities.Companion Companion;
+  }
+
+  public static final class Capabilities.Companion {
+  }
+
+  public enum ComparisonType {
+    method public static final androidx.health.services.client.data.ComparisonType? fromId(int id);
+    method public final int getId();
+    property public final int id;
+    enum_constant public static final androidx.health.services.client.data.ComparisonType GREATER_THAN;
+    enum_constant public static final androidx.health.services.client.data.ComparisonType GREATER_THAN_OR_EQUAL;
+    enum_constant public static final androidx.health.services.client.data.ComparisonType LESS_THAN;
+    enum_constant public static final androidx.health.services.client.data.ComparisonType LESS_THAN_OR_EQUAL;
+    field public static final androidx.health.services.client.data.ComparisonType.Companion Companion;
+  }
+
+  public static final class ComparisonType.Companion {
+    method public androidx.health.services.client.data.ComparisonType? fromId(int id);
+  }
+
+  public final class DataPoint implements android.os.Parcelable {
+    method public androidx.health.services.client.data.DataType component1();
+    method public androidx.health.services.client.data.Value component2();
+    method public java.time.Duration component3();
+    method public java.time.Duration component4();
+    method public android.os.Bundle component5();
+    method public androidx.health.services.client.data.DataPoint copy(androidx.health.services.client.data.DataType dataType, androidx.health.services.client.data.Value value, java.time.Duration startDurationFromBoot, java.time.Duration endDurationFromBoot, android.os.Bundle metadata);
+    method public static androidx.health.services.client.data.DataPoint createInterval(androidx.health.services.client.data.DataType dataType, androidx.health.services.client.data.Value value, java.time.Duration startDurationFromBoot, java.time.Duration endDurationFromBoot, optional android.os.Bundle metadata);
+    method public static androidx.health.services.client.data.DataPoint createInterval(androidx.health.services.client.data.DataType dataType, androidx.health.services.client.data.Value value, java.time.Duration startDurationFromBoot, java.time.Duration endDurationFromBoot);
+    method public static androidx.health.services.client.data.DataPoint createSample(androidx.health.services.client.data.DataType dataType, androidx.health.services.client.data.Value value, java.time.Duration durationFromBoot, optional android.os.Bundle metadata);
+    method public static androidx.health.services.client.data.DataPoint createSample(androidx.health.services.client.data.DataType dataType, androidx.health.services.client.data.Value value, java.time.Duration durationFromBoot);
+    method public int describeContents();
+    method public androidx.health.services.client.data.DataType getDataType();
+    method public java.time.Duration getEndDurationFromBoot();
+    method public java.time.Instant getEndInstant(java.time.Instant bootInstant);
+    method public android.os.Bundle getMetadata();
+    method public java.time.Duration getStartDurationFromBoot();
+    method public java.time.Instant getStartInstant(java.time.Instant bootInstant);
+    method public androidx.health.services.client.data.Value getValue();
+    method public void writeToParcel(android.os.Parcel dest, int flags);
+    property public final androidx.health.services.client.data.DataType dataType;
+    property public final java.time.Duration endDurationFromBoot;
+    property public final android.os.Bundle metadata;
+    property public final java.time.Duration startDurationFromBoot;
+    property public final androidx.health.services.client.data.Value value;
+    field public static final android.os.Parcelable.Creator<androidx.health.services.client.data.DataPoint> CREATOR;
+    field public static final androidx.health.services.client.data.DataPoint.Companion Companion;
+  }
+
+  public static final class DataPoint.Companion {
+    method public androidx.health.services.client.data.DataPoint createInterval(androidx.health.services.client.data.DataType dataType, androidx.health.services.client.data.Value value, java.time.Duration startDurationFromBoot, java.time.Duration endDurationFromBoot, optional android.os.Bundle metadata);
+    method public androidx.health.services.client.data.DataPoint createInterval(androidx.health.services.client.data.DataType dataType, androidx.health.services.client.data.Value value, java.time.Duration startDurationFromBoot, java.time.Duration endDurationFromBoot);
+    method public androidx.health.services.client.data.DataPoint createSample(androidx.health.services.client.data.DataType dataType, androidx.health.services.client.data.Value value, java.time.Duration durationFromBoot, optional android.os.Bundle metadata);
+    method public androidx.health.services.client.data.DataPoint createSample(androidx.health.services.client.data.DataType dataType, androidx.health.services.client.data.Value value, java.time.Duration durationFromBoot);
+  }
+
+  @Keep public final class DataPoints {
+    method public static androidx.health.services.client.data.DataPoint aggregateCalories(double kcalories, java.time.Duration startDurationFromBoot, java.time.Duration endDurationFromBoot);
+    method public static androidx.health.services.client.data.DataPoint aggregateDistance(double distance, java.time.Duration startDurationFromBoot, java.time.Duration endDurationFromBoot);
+    method public static androidx.health.services.client.data.DataPoint aggregateSteps(long steps, java.time.Duration startDurationFromBoot, java.time.Duration endDurationFromBoot);
+    method public static androidx.health.services.client.data.DataPoint aggregateSwimmingStrokes(long swimmingStrokes, java.time.Duration startDurationFromBoot, java.time.Duration endDurationFromBoot);
+    method public static androidx.health.services.client.data.DataPoint altitude(double meters, java.time.Duration durationFromBoot);
+    method public static androidx.health.services.client.data.DataPoint averagePace(double millisPerKm, java.time.Duration durationFromBoot);
+    method public static androidx.health.services.client.data.DataPoint averageSpeed(double metersPerSecond, java.time.Duration durationFromBoot);
+    method public static androidx.health.services.client.data.DataPoint calories(double kcalories, java.time.Duration startDurationFromBoot, java.time.Duration endDurationFromBoot);
+    method public static androidx.health.services.client.data.DataPoint distance(double meters, java.time.Duration startDurationFromBoot, java.time.Duration endDurationFromBoot);
+    method public static androidx.health.services.client.data.DataPoint elevation(double meters, java.time.Duration startDurationFromBoot, java.time.Duration endDurationFromBoot);
+    method public static androidx.health.services.client.data.DataPoint floors(double floors, java.time.Duration startDurationFromBoot, java.time.Duration endDurationFromBoot);
+    method @Keep public static java.util.List<androidx.health.services.client.data.DataPoint> getDataPoints(android.content.Intent intent);
+    method public static boolean getPermissionsGranted(android.content.Intent intent);
+    method public static androidx.health.services.client.data.DataPoint heartRate(double bpm, java.time.Duration durationFromBoot);
+    method public static androidx.health.services.client.data.DataPoint location(double latitude, double longitude, java.time.Duration durationFromBoot);
+    method public static androidx.health.services.client.data.DataPoint location(double latitude, double longitude, double altitude, java.time.Duration durationFromBoot);
+    method public static androidx.health.services.client.data.DataPoint maxSpeed(double metersPerSecond, java.time.Duration durationFromBoot);
+    method public static androidx.health.services.client.data.DataPoint pace(double millisPerKm, java.time.Duration durationFromBoot);
+    method public static void putDataPoints(android.content.Intent intent, java.util.Collection<androidx.health.services.client.data.DataPoint> dataPoints);
+    method public static void putPermissionsGranted(android.content.Intent intent, boolean granted);
+    method public static androidx.health.services.client.data.DataPoint speed(double metersPerSecond, java.time.Duration durationFromBoot);
+    method public static androidx.health.services.client.data.DataPoint spo2(double percent, java.time.Duration durationFromBoot);
+    method public static androidx.health.services.client.data.DataPoint steps(long steps, java.time.Duration startDurationFromBoot, java.time.Duration endDurationFromBoot);
+    method public static androidx.health.services.client.data.DataPoint stepsPerMinute(long stepsPerMinute, java.time.Duration startDurationFromBoot);
+    method public static androidx.health.services.client.data.DataPoint swimmingStrokes(long strokes, java.time.Duration startDurationFromBoot, java.time.Duration endDurationFromBoot);
+    field public static final androidx.health.services.client.data.DataPoints INSTANCE;
+    field public static final int LOCATION_DATA_POINT_ALTITUDE_INDEX = 2; // 0x2
+    field public static final int LOCATION_DATA_POINT_LATITUDE_INDEX = 0; // 0x0
+    field public static final int LOCATION_DATA_POINT_LONGITUDE_INDEX = 1; // 0x1
+  }
+
+  public final class DataType implements android.os.Parcelable {
+    ctor public DataType(String name, androidx.health.services.client.data.DataType.TimeType timeType, int format);
+    method public String component1();
+    method public androidx.health.services.client.data.DataType.TimeType component2();
+    method public int component3();
+    method public androidx.health.services.client.data.DataType copy(String name, androidx.health.services.client.data.DataType.TimeType timeType, int format);
+    method public int describeContents();
+    method public int getFormat();
+    method public String getName();
+    method public androidx.health.services.client.data.DataType.TimeType getTimeType();
+    method public void writeToParcel(android.os.Parcel dest, int flags);
+    property public final int format;
+    property public final String name;
+    property public final androidx.health.services.client.data.DataType.TimeType timeType;
+    field public static final androidx.health.services.client.data.DataType ABSOLUTE_ELEVATION;
+    field public static final androidx.health.services.client.data.DataType ACTIVE_EXERCISE_DURATION;
+    field public static final androidx.health.services.client.data.DataType AGGREGATE_CALORIES_EXPENDED;
+    field public static final androidx.health.services.client.data.DataType AGGREGATE_DECLINE_DISTANCE;
+    field public static final androidx.health.services.client.data.DataType AGGREGATE_DECLINE_TIME;
+    field public static final androidx.health.services.client.data.DataType AGGREGATE_DISTANCE;
+    field public static final androidx.health.services.client.data.DataType AGGREGATE_ELEVATION;
+    field public static final androidx.health.services.client.data.DataType AGGREGATE_FLAT_DISTANCE;
+    field public static final androidx.health.services.client.data.DataType AGGREGATE_FLAT_TIME;
+    field public static final androidx.health.services.client.data.DataType AGGREGATE_FLOORS;
+    field public static final androidx.health.services.client.data.DataType AGGREGATE_INCLINE_DISTANCE;
+    field public static final androidx.health.services.client.data.DataType AGGREGATE_INCLINE_TIME;
+    field public static final androidx.health.services.client.data.DataType AGGREGATE_RUNNING_STEP_COUNT;
+    field public static final androidx.health.services.client.data.DataType AGGREGATE_STEP_COUNT;
+    field public static final androidx.health.services.client.data.DataType AGGREGATE_SWIMMING_STROKE_COUNT;
+    field public static final androidx.health.services.client.data.DataType AGGREGATE_WALKING_STEP_COUNT;
+    field public static final androidx.health.services.client.data.DataType ALTITUDE;
+    field public static final androidx.health.services.client.data.DataType AVERAGE_HEART_RATE_BPM;
+    field public static final androidx.health.services.client.data.DataType AVERAGE_PACE;
+    field public static final androidx.health.services.client.data.DataType AVERAGE_SPEED;
+    field public static final android.os.Parcelable.Creator<androidx.health.services.client.data.DataType> CREATOR;
+    field public static final androidx.health.services.client.data.DataType.Companion Companion;
+    field public static final androidx.health.services.client.data.DataType DECLINE_DISTANCE;
+    field public static final androidx.health.services.client.data.DataType DECLINE_TIME;
+    field public static final androidx.health.services.client.data.DataType DISTANCE;
+    field public static final androidx.health.services.client.data.DataType ELEVATION;
+    field public static final androidx.health.services.client.data.DataType FLAT_DISTANCE;
+    field public static final androidx.health.services.client.data.DataType FLAT_TIME;
+    field public static final androidx.health.services.client.data.DataType FLOORS;
+    field public static final androidx.health.services.client.data.DataType HEART_RATE_BPM;
+    field public static final androidx.health.services.client.data.DataType INCLINE_DISTANCE;
+    field public static final androidx.health.services.client.data.DataType INCLINE_TIME;
+    field public static final androidx.health.services.client.data.DataType LOCATION;
+    field public static final androidx.health.services.client.data.DataType MAX_ALTITUDE;
+    field public static final androidx.health.services.client.data.DataType MAX_HEART_RATE_BPM;
+    field public static final androidx.health.services.client.data.DataType MAX_PACE;
+    field public static final androidx.health.services.client.data.DataType MAX_SPEED;
+    field public static final androidx.health.services.client.data.DataType MIN_ALTITUDE;
+    field public static final androidx.health.services.client.data.DataType PACE;
+    field public static final androidx.health.services.client.data.DataType REP_COUNT;
+    field public static final androidx.health.services.client.data.DataType RESTING_EXERCISE_DURATION;
+    field public static final androidx.health.services.client.data.DataType RUNNING_STEPS;
+    field public static final androidx.health.services.client.data.DataType SPEED;
+    field public static final androidx.health.services.client.data.DataType SPO2;
+    field public static final androidx.health.services.client.data.DataType STEPS;
+    field public static final androidx.health.services.client.data.DataType STEPS_PER_MINUTE;
+    field public static final androidx.health.services.client.data.DataType SWIMMING_LAP_COUNT;
+    field public static final androidx.health.services.client.data.DataType SWIMMING_STROKES;
+    field public static final androidx.health.services.client.data.DataType TOTAL_CALORIES;
+    field public static final androidx.health.services.client.data.DataType VO2;
+    field public static final androidx.health.services.client.data.DataType VO2_MAX;
+    field public static final androidx.health.services.client.data.DataType WALKING_STEPS;
+  }
+
+  public static final class DataType.Companion {
+  }
+
+  public enum DataType.TimeType {
+    enum_constant public static final androidx.health.services.client.data.DataType.TimeType INTERVAL;
+    enum_constant public static final androidx.health.services.client.data.DataType.TimeType SAMPLE;
+  }
+
+  public final class DataTypeCondition implements android.os.Parcelable {
+    ctor public DataTypeCondition(androidx.health.services.client.data.DataType dataType, androidx.health.services.client.data.Value threshold, androidx.health.services.client.data.ComparisonType comparisonType);
+    method public androidx.health.services.client.data.DataType component1();
+    method public androidx.health.services.client.data.Value component2();
+    method public androidx.health.services.client.data.ComparisonType component3();
+    method public androidx.health.services.client.data.DataTypeCondition copy(androidx.health.services.client.data.DataType dataType, androidx.health.services.client.data.Value threshold, androidx.health.services.client.data.ComparisonType comparisonType);
+    method public int describeContents();
+    method public androidx.health.services.client.data.ComparisonType getComparisonType();
+    method public androidx.health.services.client.data.DataType getDataType();
+    method public androidx.health.services.client.data.Value getThreshold();
+    method public boolean isSatisfied(androidx.health.services.client.data.DataPoint dataPoint);
+    method public boolean isThresholdSatisfied(androidx.health.services.client.data.Value value);
+    method public void writeToParcel(android.os.Parcel dest, int flags);
+    property public final androidx.health.services.client.data.ComparisonType comparisonType;
+    property public final androidx.health.services.client.data.DataType dataType;
+    property public final androidx.health.services.client.data.Value threshold;
+    field public static final android.os.Parcelable.Creator<androidx.health.services.client.data.DataTypeCondition> CREATOR;
+    field public static final androidx.health.services.client.data.DataTypeCondition.Companion Companion;
+  }
+
+  public static final class DataTypeCondition.Companion {
+  }
+
+  public final class DataTypes {
+    method public static androidx.health.services.client.data.DataType? getAggregateTypeFromRawType(androidx.health.services.client.data.DataType rawType);
+    method public static java.util.Set<androidx.health.services.client.data.DataType> getAggregatedDataTypesFromRawType(androidx.health.services.client.data.DataType rawType);
+    method public static androidx.health.services.client.data.DataType? getAverageTypeFromRawType(androidx.health.services.client.data.DataType rawType);
+    method public static androidx.health.services.client.data.DataType? getMaxTypeFromRawType(androidx.health.services.client.data.DataType rawType);
+    method public static androidx.health.services.client.data.DataType? getRawTypeFromAggregateType(androidx.health.services.client.data.DataType aggregateType);
+    method public static androidx.health.services.client.data.DataType? getRawTypeFromAverageType(androidx.health.services.client.data.DataType averageType);
+    method public static androidx.health.services.client.data.DataType? getRawTypeFromMaxType(androidx.health.services.client.data.DataType maxType);
+    method public static boolean isAggregateDataType(androidx.health.services.client.data.DataType dataType);
+    method public static boolean isRawType(androidx.health.services.client.data.DataType dataType);
+    method public static boolean isStatisticalAverageDataType(androidx.health.services.client.data.DataType dataType);
+    method public static boolean isStatisticalMaxDataType(androidx.health.services.client.data.DataType dataType);
+    field public static final androidx.health.services.client.data.DataTypes INSTANCE;
+  }
+
+  public final class ExerciseCapabilities implements android.os.Parcelable {
+    ctor public ExerciseCapabilities(java.util.Set<androidx.health.services.client.data.DataType> supportedDataTypes, java.util.Map<androidx.health.services.client.data.DataType,? extends java.util.Set<? extends androidx.health.services.client.data.ComparisonType>> supportedGoals, java.util.Map<androidx.health.services.client.data.DataType,? extends java.util.Set<? extends androidx.health.services.client.data.ComparisonType>> supportedMilestones, boolean supportsAutoPauseAndResume, boolean supportsLaps);
+    method public java.util.Set<androidx.health.services.client.data.DataType> component1();
+    method public java.util.Map<androidx.health.services.client.data.DataType,java.util.Set<androidx.health.services.client.data.ComparisonType>> component2();
+    method public java.util.Map<androidx.health.services.client.data.DataType,java.util.Set<androidx.health.services.client.data.ComparisonType>> component3();
+    method public boolean component4();
+    method public boolean component5();
+    method public androidx.health.services.client.data.ExerciseCapabilities copy(java.util.Set<androidx.health.services.client.data.DataType> supportedDataTypes, java.util.Map<androidx.health.services.client.data.DataType,? extends java.util.Set<? extends androidx.health.services.client.data.ComparisonType>> supportedGoals, java.util.Map<androidx.health.services.client.data.DataType,? extends java.util.Set<? extends androidx.health.services.client.data.ComparisonType>> supportedMilestones, boolean supportsAutoPauseAndResume, boolean supportsLaps);
+    method public int describeContents();
+    method public java.util.Set<androidx.health.services.client.data.DataType> getSupportedDataTypes();
+    method public java.util.Map<androidx.health.services.client.data.DataType,java.util.Set<androidx.health.services.client.data.ComparisonType>> getSupportedGoals();
+    method public java.util.Map<androidx.health.services.client.data.DataType,java.util.Set<androidx.health.services.client.data.ComparisonType>> getSupportedMilestones();
+    method public boolean getSupportsAutoPauseAndResume();
+    method public boolean getSupportsLaps();
+    method public void writeToParcel(android.os.Parcel dest, int flags);
+    property public final java.util.Set<androidx.health.services.client.data.DataType> supportedDataTypes;
+    property public final java.util.Map<androidx.health.services.client.data.DataType,java.util.Set<androidx.health.services.client.data.ComparisonType>> supportedGoals;
+    property public final java.util.Map<androidx.health.services.client.data.DataType,java.util.Set<androidx.health.services.client.data.ComparisonType>> supportedMilestones;
+    property public final boolean supportsAutoPauseAndResume;
+    property public final boolean supportsLaps;
+    field public static final android.os.Parcelable.Creator<androidx.health.services.client.data.ExerciseCapabilities> CREATOR;
+    field public static final androidx.health.services.client.data.ExerciseCapabilities.Companion Companion;
+  }
+
+  public static final class ExerciseCapabilities.Companion {
+  }
+
+  public final class ExerciseConfig implements android.os.Parcelable {
+    ctor protected ExerciseConfig(androidx.health.services.client.data.ExerciseType exerciseType, java.util.Set<androidx.health.services.client.data.DataType> dataTypes, boolean autoPauseAndResume, java.util.List<androidx.health.services.client.data.ExerciseGoal> exerciseGoals, android.os.Bundle exerciseParams);
+    method public static androidx.health.services.client.data.ExerciseConfig.Builder builder();
+    method public androidx.health.services.client.data.ExerciseType component1();
+    method public java.util.Set<androidx.health.services.client.data.DataType> component2();
+    method public boolean component3();
+    method public java.util.List<androidx.health.services.client.data.ExerciseGoal> component4();
+    method public android.os.Bundle component5();
+    method public androidx.health.services.client.data.ExerciseConfig copy(androidx.health.services.client.data.ExerciseType exerciseType, java.util.Set<androidx.health.services.client.data.DataType> dataTypes, boolean autoPauseAndResume, java.util.List<androidx.health.services.client.data.ExerciseGoal> exerciseGoals, android.os.Bundle exerciseParams);
+    method public int describeContents();
+    method public boolean getAutoPauseAndResume();
+    method public java.util.Set<androidx.health.services.client.data.DataType> getDataTypes();
+    method public java.util.List<androidx.health.services.client.data.ExerciseGoal> getExerciseGoals();
+    method public android.os.Bundle getExerciseParams();
+    method public androidx.health.services.client.data.ExerciseType getExerciseType();
+    method public void writeToParcel(android.os.Parcel dest, int flags);
+    property public final boolean autoPauseAndResume;
+    property public final java.util.Set<androidx.health.services.client.data.DataType> dataTypes;
+    property public final java.util.List<androidx.health.services.client.data.ExerciseGoal> exerciseGoals;
+    property public final android.os.Bundle exerciseParams;
+    property public final androidx.health.services.client.data.ExerciseType exerciseType;
+    field public static final android.os.Parcelable.Creator<androidx.health.services.client.data.ExerciseConfig> CREATOR;
+    field public static final androidx.health.services.client.data.ExerciseConfig.Companion Companion;
+  }
+
+  public static final class ExerciseConfig.Builder {
+    ctor public ExerciseConfig.Builder();
+    method public androidx.health.services.client.data.ExerciseConfig build();
+    method public androidx.health.services.client.data.ExerciseConfig.Builder setAutoPauseAndResume(boolean autoPauseAndResume);
+    method public androidx.health.services.client.data.ExerciseConfig.Builder setDataTypes(java.util.Set<androidx.health.services.client.data.DataType> dataTypes);
+    method public androidx.health.services.client.data.ExerciseConfig.Builder setExerciseGoals(java.util.List<androidx.health.services.client.data.ExerciseGoal> exerciseGoals);
+    method public androidx.health.services.client.data.ExerciseConfig.Builder setExerciseParams(android.os.Bundle exerciseParams);
+    method public androidx.health.services.client.data.ExerciseConfig.Builder setExerciseType(androidx.health.services.client.data.ExerciseType exerciseType);
+  }
+
+  public static final class ExerciseConfig.Companion {
+    method public androidx.health.services.client.data.ExerciseConfig.Builder builder();
+  }
+
+  public final class ExerciseGoal implements android.os.Parcelable {
+    ctor protected ExerciseGoal(androidx.health.services.client.data.ExerciseGoalType exerciseGoalType, androidx.health.services.client.data.DataTypeCondition dataTypeCondition, optional androidx.health.services.client.data.Value? period);
+    method public androidx.health.services.client.data.ExerciseGoalType component1();
+    method public androidx.health.services.client.data.DataTypeCondition component2();
+    method public androidx.health.services.client.data.Value? component3();
+    method public androidx.health.services.client.data.ExerciseGoal copy(androidx.health.services.client.data.ExerciseGoalType exerciseGoalType, androidx.health.services.client.data.DataTypeCondition dataTypeCondition, androidx.health.services.client.data.Value? period);
+    method public static androidx.health.services.client.data.ExerciseGoal createMilestone(androidx.health.services.client.data.DataTypeCondition condition, androidx.health.services.client.data.Value period);
+    method public static androidx.health.services.client.data.ExerciseGoal createMilestoneGoalWithUpdatedThreshold(androidx.health.services.client.data.ExerciseGoal goal, androidx.health.services.client.data.Value newThreshold);
+    method public static androidx.health.services.client.data.ExerciseGoal createOneTimeGoal(androidx.health.services.client.data.DataTypeCondition condition);
+    method public int describeContents();
+    method public androidx.health.services.client.data.DataTypeCondition getDataTypeCondition();
+    method public androidx.health.services.client.data.ExerciseGoalType getExerciseGoalType();
+    method public androidx.health.services.client.data.Value? getPeriod();
+    method public void writeToParcel(android.os.Parcel dest, int flags);
+    property public final androidx.health.services.client.data.DataTypeCondition dataTypeCondition;
+    property public final androidx.health.services.client.data.ExerciseGoalType exerciseGoalType;
+    property public final androidx.health.services.client.data.Value? period;
+    field public static final android.os.Parcelable.Creator<androidx.health.services.client.data.ExerciseGoal> CREATOR;
+    field public static final androidx.health.services.client.data.ExerciseGoal.Companion Companion;
+  }
+
+  public static final class ExerciseGoal.Companion {
+    method public androidx.health.services.client.data.ExerciseGoal createMilestone(androidx.health.services.client.data.DataTypeCondition condition, androidx.health.services.client.data.Value period);
+    method public androidx.health.services.client.data.ExerciseGoal createMilestoneGoalWithUpdatedThreshold(androidx.health.services.client.data.ExerciseGoal goal, androidx.health.services.client.data.Value newThreshold);
+    method public androidx.health.services.client.data.ExerciseGoal createOneTimeGoal(androidx.health.services.client.data.DataTypeCondition condition);
+  }
+
+  public enum ExerciseGoalType {
+    method public static final androidx.health.services.client.data.ExerciseGoalType? fromId(int id);
+    method public final int getId();
+    property public final int id;
+    enum_constant public static final androidx.health.services.client.data.ExerciseGoalType MILESTONE;
+    enum_constant public static final androidx.health.services.client.data.ExerciseGoalType ONE_TIME_GOAL;
+    field public static final androidx.health.services.client.data.ExerciseGoalType.Companion Companion;
+  }
+
+  public static final class ExerciseGoalType.Companion {
+    method public androidx.health.services.client.data.ExerciseGoalType? fromId(int id);
+  }
+
+  public final class ExerciseInfo implements android.os.Parcelable {
+    ctor public ExerciseInfo(androidx.health.services.client.data.ExerciseTrackedStatus exerciseTrackedStatus, androidx.health.services.client.data.ExerciseType exerciseType);
+    method public androidx.health.services.client.data.ExerciseTrackedStatus component1();
+    method public androidx.health.services.client.data.ExerciseType component2();
+    method public androidx.health.services.client.data.ExerciseInfo copy(androidx.health.services.client.data.ExerciseTrackedStatus exerciseTrackedStatus, androidx.health.services.client.data.ExerciseType exerciseType);
+    method public int describeContents();
+    method public androidx.health.services.client.data.ExerciseTrackedStatus getExerciseTrackedStatus();
+    method public androidx.health.services.client.data.ExerciseType getExerciseType();
+    method public void writeToParcel(android.os.Parcel dest, int flags);
+    property public final androidx.health.services.client.data.ExerciseTrackedStatus exerciseTrackedStatus;
+    property public final androidx.health.services.client.data.ExerciseType exerciseType;
+    field public static final android.os.Parcelable.Creator<androidx.health.services.client.data.ExerciseInfo> CREATOR;
+    field public static final androidx.health.services.client.data.ExerciseInfo.Companion Companion;
+  }
+
+  public static final class ExerciseInfo.Companion {
+  }
+
+  public final class ExerciseLapSummary implements android.os.Parcelable {
+    ctor public ExerciseLapSummary(int lapCount, java.time.Instant startTime, java.time.Instant endTime, java.time.Duration activeDuration, java.util.Map<androidx.health.services.client.data.DataType,androidx.health.services.client.data.DataPoint> lapMetrics);
+    method public int component1();
+    method public java.time.Instant component2();
+    method public java.time.Instant component3();
+    method public java.time.Duration component4();
+    method public java.util.Map<androidx.health.services.client.data.DataType,androidx.health.services.client.data.DataPoint> component5();
+    method public androidx.health.services.client.data.ExerciseLapSummary copy(int lapCount, java.time.Instant startTime, java.time.Instant endTime, java.time.Duration activeDuration, java.util.Map<androidx.health.services.client.data.DataType,androidx.health.services.client.data.DataPoint> lapMetrics);
+    method public int describeContents();
+    method public java.time.Duration getActiveDuration();
+    method public java.time.Instant getEndTime();
+    method public int getLapCount();
+    method public java.util.Map<androidx.health.services.client.data.DataType,androidx.health.services.client.data.DataPoint> getLapMetrics();
+    method public java.time.Instant getStartTime();
+    method public void writeToParcel(android.os.Parcel dest, int flags);
+    property public final java.time.Duration activeDuration;
+    property public final java.time.Instant endTime;
+    property public final int lapCount;
+    property public final java.util.Map<androidx.health.services.client.data.DataType,androidx.health.services.client.data.DataPoint> lapMetrics;
+    property public final java.time.Instant startTime;
+    field public static final android.os.Parcelable.Creator<androidx.health.services.client.data.ExerciseLapSummary> CREATOR;
+    field public static final androidx.health.services.client.data.ExerciseLapSummary.Companion Companion;
+  }
+
+  public static final class ExerciseLapSummary.Companion {
+  }
+
+  public enum ExerciseState {
+    method public static final androidx.health.services.client.data.ExerciseState? fromId(int id);
+    method public final int getId();
+    method public final boolean isEnded();
+    method public final boolean isPaused();
+    method public final boolean isResuming();
+    property public final int id;
+    property public final boolean isEnded;
+    property public final boolean isPaused;
+    property public final boolean isResuming;
+    enum_constant public static final androidx.health.services.client.data.ExerciseState ACTIVE;
+    enum_constant public static final androidx.health.services.client.data.ExerciseState AUTO_ENDED;
+    enum_constant public static final androidx.health.services.client.data.ExerciseState AUTO_ENDING;
+    enum_constant public static final androidx.health.services.client.data.ExerciseState AUTO_PAUSED;
+    enum_constant public static final androidx.health.services.client.data.ExerciseState AUTO_PAUSING;
+    enum_constant public static final androidx.health.services.client.data.ExerciseState AUTO_RESUMING;
+    enum_constant public static final androidx.health.services.client.data.ExerciseState TERMINATED;
+    enum_constant public static final androidx.health.services.client.data.ExerciseState TERMINATING;
+    enum_constant public static final androidx.health.services.client.data.ExerciseState USER_ENDED;
+    enum_constant public static final androidx.health.services.client.data.ExerciseState USER_ENDING;
+    enum_constant public static final androidx.health.services.client.data.ExerciseState USER_PAUSED;
+    enum_constant public static final androidx.health.services.client.data.ExerciseState USER_PAUSING;
+    enum_constant public static final androidx.health.services.client.data.ExerciseState USER_RESUMING;
+    enum_constant public static final androidx.health.services.client.data.ExerciseState USER_STARTING;
+    field public static final androidx.health.services.client.data.ExerciseState.Companion Companion;
+  }
+
+  public static final class ExerciseState.Companion {
+    method public androidx.health.services.client.data.ExerciseState? fromId(int id);
+  }
+
+  public enum ExerciseTrackedStatus {
+    method public static final androidx.health.services.client.data.ExerciseTrackedStatus? fromId(int id);
+    method public final int getId();
+    property public final int id;
+    enum_constant public static final androidx.health.services.client.data.ExerciseTrackedStatus NO_EXERCISE_IN_PROGRESS;
+    enum_constant public static final androidx.health.services.client.data.ExerciseTrackedStatus OTHER_APP_IN_PROGRESS;
+    enum_constant public static final androidx.health.services.client.data.ExerciseTrackedStatus OWNED_EXERCISE_IN_PROGRESS;
+    field public static final androidx.health.services.client.data.ExerciseTrackedStatus.Companion Companion;
+  }
+
+  public static final class ExerciseTrackedStatus.Companion {
+    method public androidx.health.services.client.data.ExerciseTrackedStatus? fromId(int id);
+  }
+
+  public enum ExerciseType {
+    method public static final androidx.health.services.client.data.ExerciseType fromId(int id);
+    method public final int getId();
+    property public final int id;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType BACK_EXTENSION;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType BADMINTON;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType BARBELL_SHOULDER_PRESS;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType BASEBALL;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType BASKETBALL;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType BENCH_PRESS;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType BENCH_SIT_UP;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType BIKING;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType BIKING_STATIONARY;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType BOOT_CAMP;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType BOXING;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType BURPEE;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType CALISTHENICS;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType CRICKET;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType CRUNCH;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType DANCING;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType DEADLIFT;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType DUMBBELL_CURL_LEFT_ARM;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType DUMBBELL_CURL_RIGHT_ARM;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType DUMBBELL_FRONT_RAISE;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType DUMBBELL_LATERAL_RAISE;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType DUMBBELL_TRICEPS_EXTENSION_LEFT_ARM;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType DUMBBELL_TRICEPS_EXTENSION_RIGHT_ARM;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType DUMBBELL_TRICEPS_EXTENSION_TWO_ARM;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType ELLIPTICAL;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType EXERCISE_CLASS;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType FENCING;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType FOOTBALL_AMERICAN;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType FOOTBALL_AUSTRALIAN;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType FRISBEE_DISC;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType GOLF;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType GUIDED_BREATHING;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType GYNMASTICS;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType HANDBALL;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType HIGH_INTENSITY_INTERVAL_TRAINING;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType HIKING;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType ICE_HOCKEY;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType ICE_SKATING;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType JUMPING_JACK;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType JUMP_ROPE;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType LAT_PULL_DOWN;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType LUNGE;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType MARTIAL_ARTS;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType MEDITATION;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType PADDLING;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType PARA_GLIDING;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType PILATES;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType PLANK;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType RACQUETBALL;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType ROCK_CLIMBING;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType ROLLER_HOCKEY;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType ROWING;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType ROWING_MACHINE;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType RUGBY;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType RUNNING;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType RUNNING_TREADMILL;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType SAILING;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType SCUBA_DIVING;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType SKATING;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType SKIING;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType SNOWBOARDING;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType SNOWSHOEING;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType SOCCER;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType SOFTBALL;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType SQUASH;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType SQUAT;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType STAIR_CLIMBING;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType STAIR_CLIMBING_MACHINE;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType STRENGTH_TRAINING;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType STRETCHING;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType SURFING;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType SWIMMING_OPEN_WATER;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType SWIMMING_POOL;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType TABLE_TENNIS;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType TENNIS;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType UNKNOWN;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType VOLLEYBALL;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType WALKING;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType WATER_POLO;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType WEIGHTLIFTING;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType WORKOUT_INDOOR;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType WORKOUT_OUTDOOR;
+    enum_constant public static final androidx.health.services.client.data.ExerciseType YOGA;
+    field public static final androidx.health.services.client.data.ExerciseType.Companion Companion;
+  }
+
+  public static final class ExerciseType.Companion {
+    method public androidx.health.services.client.data.ExerciseType fromId(int id);
+  }
+
+  public final class ExerciseUpdate implements android.os.Parcelable {
+    ctor public ExerciseUpdate(androidx.health.services.client.data.ExerciseState state, java.time.Instant startTime, java.time.Duration activeDuration, java.util.Map<androidx.health.services.client.data.DataType,? extends java.util.List<androidx.health.services.client.data.DataPoint>> latestMetrics, java.util.Set<androidx.health.services.client.data.AchievedExerciseGoal> latestAchievedGoals, java.util.Set<androidx.health.services.client.data.MilestoneMarkerSummary> latestMilestoneMarkerSummaries, androidx.health.services.client.data.ExerciseConfig? exerciseConfig, androidx.health.services.client.data.AutoExerciseConfig? autoExerciseConfig, androidx.health.services.client.data.ExerciseType? autoExerciseDetected);
+    method public androidx.health.services.client.data.ExerciseState component1();
+    method public java.time.Instant component2();
+    method public java.time.Duration component3();
+    method public java.util.Map<androidx.health.services.client.data.DataType,java.util.List<androidx.health.services.client.data.DataPoint>> component4();
+    method public java.util.Set<androidx.health.services.client.data.AchievedExerciseGoal> component5();
+    method public java.util.Set<androidx.health.services.client.data.MilestoneMarkerSummary> component6();
+    method public androidx.health.services.client.data.ExerciseConfig? component7();
+    method public androidx.health.services.client.data.AutoExerciseConfig? component8();
+    method public androidx.health.services.client.data.ExerciseType? component9();
+    method public androidx.health.services.client.data.ExerciseUpdate copy(androidx.health.services.client.data.ExerciseState state, java.time.Instant startTime, java.time.Duration activeDuration, java.util.Map<androidx.health.services.client.data.DataType,? extends java.util.List<androidx.health.services.client.data.DataPoint>> latestMetrics, java.util.Set<androidx.health.services.client.data.AchievedExerciseGoal> latestAchievedGoals, java.util.Set<androidx.health.services.client.data.MilestoneMarkerSummary> latestMilestoneMarkerSummaries, androidx.health.services.client.data.ExerciseConfig? exerciseConfig, androidx.health.services.client.data.AutoExerciseConfig? autoExerciseConfig, androidx.health.services.client.data.ExerciseType? autoExerciseDetected);
+    method public int describeContents();
+    method public java.time.Duration getActiveDuration();
+    method public androidx.health.services.client.data.AutoExerciseConfig? getAutoExerciseConfig();
+    method public androidx.health.services.client.data.ExerciseType? getAutoExerciseDetected();
+    method public androidx.health.services.client.data.ExerciseConfig? getExerciseConfig();
+    method public androidx.health.services.client.data.ExerciseUpdate.ExerciseSessionType getExerciseSessionType();
+    method public java.util.Set<androidx.health.services.client.data.AchievedExerciseGoal> getLatestAchievedGoals();
+    method public java.util.Map<androidx.health.services.client.data.DataType,java.util.List<androidx.health.services.client.data.DataPoint>> getLatestMetrics();
+    method public java.util.Set<androidx.health.services.client.data.MilestoneMarkerSummary> getLatestMilestoneMarkerSummaries();
+    method public java.time.Instant getStartTime();
+    method public androidx.health.services.client.data.ExerciseState getState();
+    method public void writeToParcel(android.os.Parcel dest, int flags);
+    property public final java.time.Duration activeDuration;
+    property public final androidx.health.services.client.data.AutoExerciseConfig? autoExerciseConfig;
+    property public final androidx.health.services.client.data.ExerciseType? autoExerciseDetected;
+    property public final androidx.health.services.client.data.ExerciseConfig? exerciseConfig;
+    property public final java.util.Set<androidx.health.services.client.data.AchievedExerciseGoal> latestAchievedGoals;
+    property public final java.util.Map<androidx.health.services.client.data.DataType,java.util.List<androidx.health.services.client.data.DataPoint>> latestMetrics;
+    property public final java.util.Set<androidx.health.services.client.data.MilestoneMarkerSummary> latestMilestoneMarkerSummaries;
+    property public final java.time.Instant startTime;
+    property public final androidx.health.services.client.data.ExerciseState state;
+    field public static final android.os.Parcelable.Creator<androidx.health.services.client.data.ExerciseUpdate> CREATOR;
+    field public static final androidx.health.services.client.data.ExerciseUpdate.Companion Companion;
+  }
+
+  public static final class ExerciseUpdate.Companion {
+  }
+
+  public enum ExerciseUpdate.ExerciseSessionType {
+    enum_constant public static final androidx.health.services.client.data.ExerciseUpdate.ExerciseSessionType AUTO_EXERCISE_DETECTION;
+    enum_constant public static final androidx.health.services.client.data.ExerciseUpdate.ExerciseSessionType MANUALLY_STARTED_EXERCISE;
+  }
+
+  public final class MeasureCapabilities implements android.os.Parcelable {
+    ctor public MeasureCapabilities(java.util.Set<androidx.health.services.client.data.DataType> supportedDataTypesMeasure);
+    method public java.util.Set<androidx.health.services.client.data.DataType> component1();
+    method public androidx.health.services.client.data.MeasureCapabilities copy(java.util.Set<androidx.health.services.client.data.DataType> supportedDataTypesMeasure);
+    method public int describeContents();
+    method public java.util.Set<androidx.health.services.client.data.DataType> getSupportedDataTypesMeasure();
+    method public void writeToParcel(android.os.Parcel dest, int flags);
+    property public final java.util.Set<androidx.health.services.client.data.DataType> supportedDataTypesMeasure;
+    field public static final android.os.Parcelable.Creator<androidx.health.services.client.data.MeasureCapabilities> CREATOR;
+    field public static final androidx.health.services.client.data.MeasureCapabilities.Companion Companion;
+  }
+
+  public static final class MeasureCapabilities.Companion {
+  }
+
+  public final class MilestoneMarkerSummary implements android.os.Parcelable {
+    ctor public MilestoneMarkerSummary(java.time.Instant startTime, java.time.Instant endTime, java.time.Duration activeDuration, androidx.health.services.client.data.AchievedExerciseGoal achievedGoal, java.util.Map<androidx.health.services.client.data.DataType,androidx.health.services.client.data.DataPoint> summaryMetrics);
+    method public java.time.Instant component1();
+    method public java.time.Instant component2();
+    method public java.time.Duration component3();
+    method public androidx.health.services.client.data.AchievedExerciseGoal component4();
+    method public java.util.Map<androidx.health.services.client.data.DataType,androidx.health.services.client.data.DataPoint> component5();
+    method public androidx.health.services.client.data.MilestoneMarkerSummary copy(java.time.Instant startTime, java.time.Instant endTime, java.time.Duration activeDuration, androidx.health.services.client.data.AchievedExerciseGoal achievedGoal, java.util.Map<androidx.health.services.client.data.DataType,androidx.health.services.client.data.DataPoint> summaryMetrics);
+    method public int describeContents();
+    method public androidx.health.services.client.data.AchievedExerciseGoal getAchievedGoal();
+    method public java.time.Duration getActiveDuration();
+    method public java.time.Instant getEndTime();
+    method public java.time.Instant getStartTime();
+    method public java.util.Map<androidx.health.services.client.data.DataType,androidx.health.services.client.data.DataPoint> getSummaryMetrics();
+    method public void writeToParcel(android.os.Parcel dest, int flags);
+    property public final androidx.health.services.client.data.AchievedExerciseGoal achievedGoal;
+    property public final java.time.Duration activeDuration;
+    property public final java.time.Instant endTime;
+    property public final java.time.Instant startTime;
+    property public final java.util.Map<androidx.health.services.client.data.DataType,androidx.health.services.client.data.DataPoint> summaryMetrics;
+    field public static final android.os.Parcelable.Creator<androidx.health.services.client.data.MilestoneMarkerSummary> CREATOR;
+    field public static final androidx.health.services.client.data.MilestoneMarkerSummary.Companion Companion;
+  }
+
+  public static final class MilestoneMarkerSummary.Companion {
+  }
+
+  public final class PassiveActivityState implements android.os.Parcelable {
+    ctor public PassiveActivityState(java.util.List<androidx.health.services.client.data.DataPoint> dataPoints, androidx.health.services.client.data.UserActivityState userActivityState, androidx.health.services.client.data.ExerciseType? exerciseType, java.time.Instant stateChangeTime);
+    method public java.util.List<androidx.health.services.client.data.DataPoint> component1();
+    method public androidx.health.services.client.data.UserActivityState component2();
+    method public androidx.health.services.client.data.ExerciseType? component3();
+    method public java.time.Instant component4();
+    method public androidx.health.services.client.data.PassiveActivityState copy(java.util.List<androidx.health.services.client.data.DataPoint> dataPoints, androidx.health.services.client.data.UserActivityState userActivityState, androidx.health.services.client.data.ExerciseType? exerciseType, java.time.Instant stateChangeTime);
+    method public static androidx.health.services.client.data.PassiveActivityState createActiveExerciseState(java.util.List<androidx.health.services.client.data.DataPoint> dataPoints, androidx.health.services.client.data.ExerciseType exerciseType, java.time.Instant stateChangeTime);
+    method public static androidx.health.services.client.data.PassiveActivityState createInactiveState(java.util.List<androidx.health.services.client.data.DataPoint> dataPoints, java.time.Instant stateChangeTime);
+    method public static androidx.health.services.client.data.PassiveActivityState createPassiveActivityState(java.util.List<androidx.health.services.client.data.DataPoint> dataPoints, java.time.Instant stateChangeTime);
+    method public static androidx.health.services.client.data.PassiveActivityState createUnknownTypeState(java.util.List<androidx.health.services.client.data.DataPoint> dataPoints, java.time.Instant stateChangeTime);
+    method public int describeContents();
+    method public static androidx.health.services.client.data.PassiveActivityState? fromIntent(android.content.Intent intent);
+    method public java.util.List<androidx.health.services.client.data.DataPoint> getDataPoints();
+    method public androidx.health.services.client.data.ExerciseType? getExerciseType();
+    method public java.time.Instant getStateChangeTime();
+    method public androidx.health.services.client.data.UserActivityState getUserActivityState();
+    method public void putToIntent(android.content.Intent intent);
+    method public void writeToParcel(android.os.Parcel dest, int flags);
+    property public final java.util.List<androidx.health.services.client.data.DataPoint> dataPoints;
+    property public final androidx.health.services.client.data.ExerciseType? exerciseType;
+    property public final java.time.Instant stateChangeTime;
+    property public final androidx.health.services.client.data.UserActivityState userActivityState;
+    field public static final android.os.Parcelable.Creator<androidx.health.services.client.data.PassiveActivityState> CREATOR;
+    field public static final androidx.health.services.client.data.PassiveActivityState.Companion Companion;
+  }
+
+  public static final class PassiveActivityState.Companion {
+    method public androidx.health.services.client.data.PassiveActivityState createActiveExerciseState(java.util.List<androidx.health.services.client.data.DataPoint> dataPoints, androidx.health.services.client.data.ExerciseType exerciseType, java.time.Instant stateChangeTime);
+    method public androidx.health.services.client.data.PassiveActivityState createInactiveState(java.util.List<androidx.health.services.client.data.DataPoint> dataPoints, java.time.Instant stateChangeTime);
+    method public androidx.health.services.client.data.PassiveActivityState createPassiveActivityState(java.util.List<androidx.health.services.client.data.DataPoint> dataPoints, java.time.Instant stateChangeTime);
+    method public androidx.health.services.client.data.PassiveActivityState createUnknownTypeState(java.util.List<androidx.health.services.client.data.DataPoint> dataPoints, java.time.Instant stateChangeTime);
+    method public androidx.health.services.client.data.PassiveActivityState? fromIntent(android.content.Intent intent);
+  }
+
+  public final class PassiveMonitoringCapabilities implements android.os.Parcelable {
+    ctor public PassiveMonitoringCapabilities(java.util.Set<androidx.health.services.client.data.DataType> supportedDataTypesPassiveMonitoring, java.util.Set<androidx.health.services.client.data.DataType> supportedDataTypesEvents);
+    method public java.util.Set<androidx.health.services.client.data.DataType> component1();
+    method public java.util.Set<androidx.health.services.client.data.DataType> component2();
+    method public androidx.health.services.client.data.PassiveMonitoringCapabilities copy(java.util.Set<androidx.health.services.client.data.DataType> supportedDataTypesPassiveMonitoring, java.util.Set<androidx.health.services.client.data.DataType> supportedDataTypesEvents);
+    method public int describeContents();
+    method public java.util.Set<androidx.health.services.client.data.DataType> getSupportedDataTypesEvents();
+    method public java.util.Set<androidx.health.services.client.data.DataType> getSupportedDataTypesPassiveMonitoring();
+    method public void writeToParcel(android.os.Parcel dest, int flags);
+    property public final java.util.Set<androidx.health.services.client.data.DataType> supportedDataTypesEvents;
+    property public final java.util.Set<androidx.health.services.client.data.DataType> supportedDataTypesPassiveMonitoring;
+    field public static final android.os.Parcelable.Creator<androidx.health.services.client.data.PassiveMonitoringCapabilities> CREATOR;
+    field public static final androidx.health.services.client.data.PassiveMonitoringCapabilities.Companion Companion;
+  }
+
+  public static final class PassiveMonitoringCapabilities.Companion {
+  }
+
+  public enum UserActivityState {
+    method public static final androidx.health.services.client.data.UserActivityState? fromId(int id);
+    method public final int getId();
+    property public final int id;
+    enum_constant public static final androidx.health.services.client.data.UserActivityState USER_ACTIVITY_EXERCISE;
+    enum_constant public static final androidx.health.services.client.data.UserActivityState USER_ACTIVITY_INACTIVE;
+    enum_constant public static final androidx.health.services.client.data.UserActivityState USER_ACTIVITY_PASSIVE;
+    enum_constant public static final androidx.health.services.client.data.UserActivityState USER_ACTIVITY_UNKNOWN;
+    field public static final androidx.health.services.client.data.UserActivityState.Companion Companion;
+  }
+
+  public static final class UserActivityState.Companion {
+    method public androidx.health.services.client.data.UserActivityState? fromId(int id);
+  }
+
+  public final class Value implements android.os.Parcelable {
+    method public boolean asBoolean();
+    method public double asDouble();
+    method public double[] asDoubleArray();
+    method public long asLong();
+    method public int component1();
+    method public java.util.List<java.lang.Double> component2();
+    method public long component3();
+    method public androidx.health.services.client.data.Value copy(int format, java.util.List<java.lang.Double> doubleList, long longValue);
+    method public int describeContents();
+    method public java.util.List<java.lang.Double> getDoubleList();
+    method public int getFormat();
+    method public long getLongValue();
+    method public boolean isBoolean();
+    method public boolean isDouble();
+    method public boolean isDoubleArray();
+    method public boolean isLong();
+    method public static androidx.health.services.client.data.Value ofBoolean(boolean value);
+    method public static androidx.health.services.client.data.Value ofDouble(double value);
+    method public static androidx.health.services.client.data.Value ofDoubleArray(double... doubleArray);
+    method public static androidx.health.services.client.data.Value ofLong(long value);
+    method public static androidx.health.services.client.data.Value sum(androidx.health.services.client.data.Value first, androidx.health.services.client.data.Value second);
+    method public void writeToParcel(android.os.Parcel dest, int flags);
+    property public final java.util.List<java.lang.Double> doubleList;
+    property public final int format;
+    property public final boolean isBoolean;
+    property public final boolean isDouble;
+    property public final boolean isDoubleArray;
+    property public final boolean isLong;
+    property public final long longValue;
+    field public static final android.os.Parcelable.Creator<androidx.health.services.client.data.Value> CREATOR;
+    field public static final androidx.health.services.client.data.Value.Companion Companion;
+    field public static final int FORMAT_BOOLEAN = 4; // 0x4
+    field public static final int FORMAT_DOUBLE = 1; // 0x1
+    field public static final int FORMAT_DOUBLE_ARRAY = 3; // 0x3
+    field public static final int FORMAT_LONG = 2; // 0x2
+  }
+
+  public static final class Value.Companion {
+    method public androidx.health.services.client.data.Value ofBoolean(boolean value);
+    method public androidx.health.services.client.data.Value ofDouble(double value);
+    method public androidx.health.services.client.data.Value ofDoubleArray(double... doubleArray);
+    method public androidx.health.services.client.data.Value ofLong(long value);
+    method public androidx.health.services.client.data.Value sum(androidx.health.services.client.data.Value first, androidx.health.services.client.data.Value second);
+  }
+
+}
+
+package androidx.health.services.client.data.event {
+
+  public final class Event implements android.os.Parcelable {
+    ctor public Event(androidx.health.services.client.data.DataTypeCondition dataTypeCondition, androidx.health.services.client.data.event.Event.TriggerType triggerType);
+    method public androidx.health.services.client.data.DataTypeCondition component1();
+    method public androidx.health.services.client.data.event.Event.TriggerType component2();
+    method public androidx.health.services.client.data.event.Event copy(androidx.health.services.client.data.DataTypeCondition dataTypeCondition, androidx.health.services.client.data.event.Event.TriggerType triggerType);
+    method public int describeContents();
+    method public androidx.health.services.client.data.DataTypeCondition getDataTypeCondition();
+    method public androidx.health.services.client.data.event.Event.TriggerType getTriggerType();
+    method public boolean isTriggered(androidx.health.services.client.data.DataPoint dataPoint);
+    method public void writeToParcel(android.os.Parcel dest, int flags);
+    property public final androidx.health.services.client.data.DataTypeCondition dataTypeCondition;
+    property public final androidx.health.services.client.data.event.Event.TriggerType triggerType;
+    field public static final android.os.Parcelable.Creator<androidx.health.services.client.data.event.Event> CREATOR;
+    field public static final androidx.health.services.client.data.event.Event.Companion Companion;
+  }
+
+  public static final class Event.Companion {
+  }
+
+  public enum Event.TriggerType {
+    method public static final androidx.health.services.client.data.event.Event.TriggerType? fromId(int id);
+    method public final int getId();
+    property public final int id;
+    enum_constant public static final androidx.health.services.client.data.event.Event.TriggerType ONCE;
+    enum_constant public static final androidx.health.services.client.data.event.Event.TriggerType REPEATED;
+    field public static final androidx.health.services.client.data.event.Event.TriggerType.Companion Companion;
+  }
+
+  public static final class Event.TriggerType.Companion {
+    method public androidx.health.services.client.data.event.Event.TriggerType? fromId(int id);
+  }
+
+}
+
diff --git a/health/health-services-client/build.gradle b/health/health-services-client/build.gradle
new file mode 100644
index 0000000..248825c
--- /dev/null
+++ b/health/health-services-client/build.gradle
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 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.
+ */
+import static androidx.build.dependencies.DependenciesKt.*
+import androidx.build.LibraryGroups
+import androidx.build.LibraryType
+import androidx.build.LibraryVersions
+
+plugins {
+    id("AndroidXPlugin")
+    id("com.android.library")
+    id("org.jetbrains.kotlin.android")
+}
+
+dependencies {
+    api(KOTLIN_STDLIB)
+    api("androidx.annotation:annotation:1.1.0")
+    implementation(GUAVA_LISTENABLE_FUTURE)
+    implementation(GUAVA_ANDROID)
+    implementation("androidx.core:core-ktx:1.5.0-alpha04")
+}
+
+android {
+    defaultConfig {
+        minSdkVersion 26
+    }
+    buildFeatures {
+        aidl = true
+    }
+}
+
+androidx {
+    name = "AndroidX Health Services Client Library"
+    type = LibraryType.PUBLISHED_LIBRARY
+    mavenVersion = LibraryVersions.HEALTH_SERVICES_CLIENT
+    mavenGroup = LibraryGroups.HEALTH
+    inceptionYear = "2021"
+    description = "This library helps developers create performant health applications in a platform agnostic way"
+}
diff --git a/health/health-services-client/lint-baseline.xml b/health/health-services-client/lint-baseline.xml
new file mode 100644
index 0000000..11eec21
--- /dev/null
+++ b/health/health-services-client/lint-baseline.xml
@@ -0,0 +1,1291 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
+
+    <issue
+        id="BanKeepAnnotation"
+        message="Uses @Keep annotation"
+        errorLine1="@Keep"
+        errorLine2="~~~~~">
+        <location
+            file="src/main/java/androidx/health/services/client/data/DataPoints.kt"
+            line="27"
+            column="1"/>
+    </issue>
+
+    <issue
+        id="BanKeepAnnotation"
+        message="Uses @Keep annotation"
+        errorLine1="    @Keep"
+        errorLine2="    ~~~~~">
+        <location
+            file="src/main/java/androidx/health/services/client/data/DataPoints.kt"
+            line="56"
+            column="5"/>
+    </issue>
+
+    <issue
+        id="BanParcelableUsage"
+        message="Class implements android.os.Parcelable"
+        errorLine1="public data class AchievedExerciseGoal("
+        errorLine2="                  ~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/androidx/health/services/client/data/AchievedExerciseGoal.kt"
+            line="23"
+            column="19"/>
+    </issue>
+
+    <issue
+        id="BanParcelableUsage"
+        message="Class implements android.os.Parcelable"
+        errorLine1="public data class AutoExerciseConfig"
+        errorLine2="                  ~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/androidx/health/services/client/data/AutoExerciseConfig.kt"
+            line="26"
+            column="19"/>
+    </issue>
+
+    <issue
+        id="BanParcelableUsage"
+        message="Class implements android.os.Parcelable"
+        errorLine1="public data class AutoPauseAndResumeConfigRequest("
+        errorLine2="                  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/androidx/health/services/client/impl/request/AutoPauseAndResumeConfigRequest.kt"
+            line="27"
+            column="19"/>
+    </issue>
+
+    <issue
+        id="BanParcelableUsage"
+        message="Class implements android.os.Parcelable"
+        errorLine1="public data class AvailabilityResponse("
+        errorLine2="                  ~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/androidx/health/services/client/impl/response/AvailabilityResponse.kt"
+            line="29"
+            column="19"/>
+    </issue>
+
+    <issue
+        id="BanParcelableUsage"
+        message="Class implements android.os.Parcelable"
+        errorLine1="public data class BackgroundRegistrationRequest("
+        errorLine2="                  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/androidx/health/services/client/impl/request/BackgroundRegistrationRequest.kt"
+            line="28"
+            column="19"/>
+    </issue>
+
+    <issue
+        id="BanParcelableUsage"
+        message="Class implements android.os.Parcelable"
+        errorLine1="public data class BackgroundUnregistrationRequest(val packageName: String) : Parcelable {"
+        errorLine2="                  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/androidx/health/services/client/impl/request/BackgroundUnregistrationRequest.kt"
+            line="27"
+            column="19"/>
+    </issue>
+
+    <issue
+        id="BanParcelableUsage"
+        message="Class implements android.os.Parcelable"
+        errorLine1="public data class Capabilities("
+        errorLine2="                  ~~~~~~~~~~~~">
+        <location
+            file="src/main/java/androidx/health/services/client/data/Capabilities.kt"
+            line="24"
+            column="19"/>
+    </issue>
+
+    <issue
+        id="BanParcelableUsage"
+        message="Class implements android.os.Parcelable"
+        errorLine1="public data class CapabilitiesRequest(val packageName: String) : Parcelable {"
+        errorLine2="                  ~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/androidx/health/services/client/impl/request/CapabilitiesRequest.kt"
+            line="27"
+            column="19"/>
+    </issue>
+
+    <issue
+        id="BanParcelableUsage"
+        message="Class implements android.os.Parcelable"
+        errorLine1="public data class CapabilitiesResponse("
+        errorLine2="                  ~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/androidx/health/services/client/impl/response/CapabilitiesResponse.kt"
+            line="28"
+            column="19"/>
+    </issue>
+
+    <issue
+        id="BanParcelableUsage"
+        message="Class implements android.os.Parcelable"
+        errorLine1="public data class DataPoint"
+        errorLine2="                  ~~~~~~~~~">
+        <location
+            file="src/main/java/androidx/health/services/client/data/DataPoint.kt"
+            line="31"
+            column="19"/>
+    </issue>
+
+    <issue
+        id="BanParcelableUsage"
+        message="Class implements android.os.Parcelable"
+        errorLine1="public data class DataPointsResponse(val dataPoints: List&lt;DataPoint>) : Parcelable {"
+        errorLine2="                  ~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/androidx/health/services/client/impl/response/DataPointsResponse.kt"
+            line="28"
+            column="19"/>
+    </issue>
+
+    <issue
+        id="BanParcelableUsage"
+        message="Class implements android.os.Parcelable"
+        errorLine1="public data class DataType("
+        errorLine2="                  ~~~~~~~~">
+        <location
+            file="src/main/java/androidx/health/services/client/data/DataType.kt"
+            line="32"
+            column="19"/>
+    </issue>
+
+    <issue
+        id="BanParcelableUsage"
+        message="Class implements android.os.Parcelable"
+        errorLine1="public data class DataTypeCondition("
+        errorLine2="                  ~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/androidx/health/services/client/data/DataTypeCondition.kt"
+            line="23"
+            column="19"/>
+    </issue>
+
+    <issue
+        id="BanParcelableUsage"
+        message="Class implements android.os.Parcelable"
+        errorLine1="public data class Event("
+        errorLine2="                  ~~~~~">
+        <location
+            file="src/main/java/androidx/health/services/client/data/event/Event.kt"
+            line="25"
+            column="19"/>
+    </issue>
+
+    <issue
+        id="BanParcelableUsage"
+        message="Class implements android.os.Parcelable"
+        errorLine1="public data class EventRequest(val packageName: String, val event: Event) : Parcelable {"
+        errorLine2="                  ~~~~~~~~~~~~">
+        <location
+            file="src/main/java/androidx/health/services/client/impl/request/EventRequest.kt"
+            line="28"
+            column="19"/>
+    </issue>
+
+    <issue
+        id="BanParcelableUsage"
+        message="Class implements android.os.Parcelable"
+        errorLine1="public data class ExerciseCapabilities("
+        errorLine2="                  ~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/androidx/health/services/client/data/ExerciseCapabilities.kt"
+            line="23"
+            column="19"/>
+    </issue>
+
+    <issue
+        id="BanParcelableUsage"
+        message="Class implements android.os.Parcelable"
+        errorLine1="public data class ExerciseConfig"
+        errorLine2="                  ~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/androidx/health/services/client/data/ExerciseConfig.kt"
+            line="26"
+            column="19"/>
+    </issue>
+
+    <issue
+        id="BanParcelableUsage"
+        message="Class implements android.os.Parcelable"
+        errorLine1="public data class ExerciseGoal"
+        errorLine2="                  ~~~~~~~~~~~~">
+        <location
+            file="src/main/java/androidx/health/services/client/data/ExerciseGoal.kt"
+            line="26"
+            column="19"/>
+    </issue>
+
+    <issue
+        id="BanParcelableUsage"
+        message="Class implements android.os.Parcelable"
+        errorLine1="public data class ExerciseGoalRequest(val packageName: String, val exerciseGoal: ExerciseGoal) :"
+        errorLine2="                  ~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/androidx/health/services/client/impl/request/ExerciseGoalRequest.kt"
+            line="28"
+            column="19"/>
+    </issue>
+
+    <issue
+        id="BanParcelableUsage"
+        message="Class implements android.os.Parcelable"
+        errorLine1="public data class ExerciseInfo("
+        errorLine2="                  ~~~~~~~~~~~~">
+        <location
+            file="src/main/java/androidx/health/services/client/data/ExerciseInfo.kt"
+            line="23"
+            column="19"/>
+    </issue>
+
+    <issue
+        id="BanParcelableUsage"
+        message="Class implements android.os.Parcelable"
+        errorLine1="public data class ExerciseInfoResponse(val exerciseInfo: ExerciseInfo) : Parcelable {"
+        errorLine2="                  ~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/androidx/health/services/client/impl/response/ExerciseInfoResponse.kt"
+            line="28"
+            column="19"/>
+    </issue>
+
+    <issue
+        id="BanParcelableUsage"
+        message="Class implements android.os.Parcelable"
+        errorLine1="public data class ExerciseLapSummary("
+        errorLine2="                  ~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/androidx/health/services/client/data/ExerciseLapSummary.kt"
+            line="25"
+            column="19"/>
+    </issue>
+
+    <issue
+        id="BanParcelableUsage"
+        message="Class implements android.os.Parcelable"
+        errorLine1="public data class ExerciseLapSummaryResponse(val exerciseLapSummary: ExerciseLapSummary) :"
+        errorLine2="                  ~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/androidx/health/services/client/impl/response/ExerciseLapSummaryResponse.kt"
+            line="28"
+            column="19"/>
+    </issue>
+
+    <issue
+        id="BanParcelableUsage"
+        message="Class implements android.os.Parcelable"
+        errorLine1="public data class ExerciseUpdate("
+        errorLine2="                  ~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/androidx/health/services/client/data/ExerciseUpdate.kt"
+            line="26"
+            column="19"/>
+    </issue>
+
+    <issue
+        id="BanParcelableUsage"
+        message="Class implements android.os.Parcelable"
+        errorLine1="public data class ExerciseUpdateResponse(val exerciseUpdate: ExerciseUpdate) : Parcelable {"
+        errorLine2="                  ~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/androidx/health/services/client/impl/response/ExerciseUpdateResponse.kt"
+            line="28"
+            column="19"/>
+    </issue>
+
+    <issue
+        id="BanParcelableUsage"
+        message="Class implements android.os.Parcelable"
+        errorLine1="public data class MeasureCapabilities("
+        errorLine2="                  ~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/androidx/health/services/client/data/MeasureCapabilities.kt"
+            line="25"
+            column="19"/>
+    </issue>
+
+    <issue
+        id="BanParcelableUsage"
+        message="Class implements android.os.Parcelable"
+        errorLine1="public data class MeasureCapabilitiesResponse("
+        errorLine2="                  ~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/androidx/health/services/client/impl/response/MeasureCapabilitiesResponse.kt"
+            line="28"
+            column="19"/>
+    </issue>
+
+    <issue
+        id="BanParcelableUsage"
+        message="Class implements android.os.Parcelable"
+        errorLine1="public data class MeasureRegistrationRequest("
+        errorLine2="                  ~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/androidx/health/services/client/impl/request/MeasureRegistrationRequest.kt"
+            line="28"
+            column="19"/>
+    </issue>
+
+    <issue
+        id="BanParcelableUsage"
+        message="Class implements android.os.Parcelable"
+        errorLine1="public data class MeasureUnregistrationRequest("
+        errorLine2="                  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/androidx/health/services/client/impl/request/MeasureUnregistrationRequest.kt"
+            line="28"
+            column="19"/>
+    </issue>
+
+    <issue
+        id="BanParcelableUsage"
+        message="Class implements android.os.Parcelable"
+        errorLine1="public data class MilestoneMarkerSummary("
+        errorLine2="                  ~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/androidx/health/services/client/data/MilestoneMarkerSummary.kt"
+            line="27"
+            column="19"/>
+    </issue>
+
+    <issue
+        id="BanParcelableUsage"
+        message="Class implements android.os.Parcelable"
+        errorLine1="public data class PassiveActivityState("
+        errorLine2="                  ~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/androidx/health/services/client/data/PassiveActivityState.kt"
+            line="34"
+            column="19"/>
+    </issue>
+
+    <issue
+        id="BanParcelableUsage"
+        message="Class implements android.os.Parcelable"
+        errorLine1="public data class PassiveActivityStateResponse(val passiveActivityState: PassiveActivityState) :"
+        errorLine2="                  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/androidx/health/services/client/impl/response/PassiveActivityStateResponse.kt"
+            line="28"
+            column="19"/>
+    </issue>
+
+    <issue
+        id="BanParcelableUsage"
+        message="Class implements android.os.Parcelable"
+        errorLine1="public data class PassiveMonitoringCapabilities("
+        errorLine2="                  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/androidx/health/services/client/data/PassiveMonitoringCapabilities.kt"
+            line="26"
+            column="19"/>
+    </issue>
+
+    <issue
+        id="BanParcelableUsage"
+        message="Class implements android.os.Parcelable"
+        errorLine1="public data class PassiveMonitoringCapabilitiesResponse("
+        errorLine2="                  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/androidx/health/services/client/impl/response/PassiveMonitoringCapabilitiesResponse.kt"
+            line="28"
+            column="19"/>
+    </issue>
+
+    <issue
+        id="BanParcelableUsage"
+        message="Class implements android.os.Parcelable"
+        errorLine1="public data class StartExerciseRequest("
+        errorLine2="                  ~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/androidx/health/services/client/impl/request/StartExerciseRequest.kt"
+            line="28"
+            column="19"/>
+    </issue>
+
+    <issue
+        id="BanParcelableUsage"
+        message="Class implements android.os.Parcelable"
+        errorLine1="public data class Value"
+        errorLine2="                  ~~~~~">
+        <location
+            file="src/main/java/androidx/health/services/client/data/Value.kt"
+            line="24"
+            column="19"/>
+    </issue>
+
+    <issue
+        id="BanSynchronizedMethods"
+        message="Use of synchronized methods is not recommended"
+        errorLine1="        @Synchronized"
+        errorLine2="        ^">
+        <location
+            file="src/main/java/androidx/health/services/client/impl/ExerciseUpdateListenerStub.kt"
+            line="55"
+            column="9"/>
+    </issue>
+
+    <issue
+        id="BanSynchronizedMethods"
+        message="Use of synchronized methods is not recommended"
+        errorLine1="        @Synchronized"
+        errorLine2="        ^">
+        <location
+            file="src/main/java/androidx/health/services/client/impl/ExerciseUpdateListenerStub.kt"
+            line="63"
+            column="9"/>
+    </issue>
+
+    <issue
+        id="BanSynchronizedMethods"
+        message="Use of synchronized methods is not recommended"
+        errorLine1="        @Synchronized"
+        errorLine2="        ^">
+        <location
+            file="src/main/java/androidx/health/services/client/impl/MeasureCallbackStub.kt"
+            line="63"
+            column="9"/>
+    </issue>
+
+    <issue
+        id="BanSynchronizedMethods"
+        message="Use of synchronized methods is not recommended"
+        errorLine1="        @Synchronized"
+        errorLine2="        ^">
+        <location
+            file="src/main/java/androidx/health/services/client/impl/MeasureCallbackStub.kt"
+            line="86"
+            column="9"/>
+    </issue>
+
+    <issue
+        id="BanSynchronizedMethods"
+        message="Use of synchronized methods is not recommended"
+        errorLine1="    private synchronized void handleRetriableDisconnection(Throwable throwable) {"
+        errorLine2="    ^">
+        <location
+            file="src/main/java/androidx/health/services/client/impl/ipc/internal/ServiceConnection.java"
+            line="155"
+            column="5"/>
+    </issue>
+
+    <issue
+        id="SyntheticAccessor"
+        message="Access to `private` method `getExerciseToExerciseCapabilityMap` of class `Companion` requires synthetic accessor"
+        errorLine1="                        getExerciseToExerciseCapabilityMap(parcel)"
+        errorLine2="                        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/androidx/health/services/client/data/Capabilities.kt"
+            line="79"
+            column="25"/>
+    </issue>
+
+    <issue
+        id="SyntheticAccessor"
+        message="Access to `private` method `createQueueOperation` of class `Client` requires synthetic accessor"
+        errorLine1="                                                createQueueOperation("
+        errorLine2="                                                ~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/androidx/health/services/client/impl/ipc/Client.java"
+            line="133"
+            column="49"/>
+    </issue>
+
+    <issue
+        id="SyntheticAccessor"
+        message="Access to `private` method `writeSupportedDataTypes` of class `Companion` requires synthetic accessor"
+        errorLine1="        writeSupportedDataTypes(supportedGoals, dest, flags)"
+        errorLine2="        ~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/androidx/health/services/client/data/ExerciseCapabilities.kt"
+            line="36"
+            column="9"/>
+    </issue>
+
+    <issue
+        id="SyntheticAccessor"
+        message="Access to `private` method `writeSupportedDataTypes` of class `Companion` requires synthetic accessor"
+        errorLine1="        writeSupportedDataTypes(supportedMilestones, dest, flags)"
+        errorLine2="        ~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/androidx/health/services/client/data/ExerciseCapabilities.kt"
+            line="37"
+            column="9"/>
+    </issue>
+
+    <issue
+        id="SyntheticAccessor"
+        message="Access to `private` method `readSupportedDataTypes` of class `Companion` requires synthetic accessor"
+        errorLine1="                    val supportedGoals = readSupportedDataTypes(source) ?: return null"
+        errorLine2="                                         ~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/androidx/health/services/client/data/ExerciseCapabilities.kt"
+            line="51"
+            column="42"/>
+    </issue>
+
+    <issue
+        id="SyntheticAccessor"
+        message="Access to `private` method `readSupportedDataTypes` of class `Companion` requires synthetic accessor"
+        errorLine1="                    val supportedMilestones = readSupportedDataTypes(source) ?: return null"
+        errorLine2="                                              ~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/androidx/health/services/client/data/ExerciseCapabilities.kt"
+            line="52"
+            column="47"/>
+    </issue>
+
+    <issue
+        id="SyntheticAccessor"
+        message="Access to `private` method `initialize` of class `Companion` requires synthetic accessor"
+        errorLine1="        private val IDS = initialize()"
+        errorLine2="                          ~~~~~~~~~~">
+        <location
+            file="src/main/java/androidx/health/services/client/data/ExerciseType.kt"
+            line="114"
+            column="27"/>
+    </issue>
+
+    <issue
+        id="SyntheticAccessor"
+        message="Access to `private` constructor of class `ExerciseUpdateListenerStub` requires synthetic accessor"
+        errorLine1="            return listeners.getOrPut(listener) { ExerciseUpdateListenerStub(listener, executor) }"
+        errorLine2="                                                  ~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/androidx/health/services/client/impl/ExerciseUpdateListenerStub.kt"
+            line="60"
+            column="51"/>
+    </issue>
+
+    <issue
+        id="SyntheticAccessor"
+        message="Access to `private` constructor of class `MeasureCallbackStub` requires synthetic accessor"
+        errorLine1="                measureCallbackStub = MeasureCallbackStub(callbackKey, measureCallback)"
+        errorLine2="                                      ~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/androidx/health/services/client/impl/MeasureCallbackStub.kt"
+            line="79"
+            column="39"/>
+    </issue>
+
+    <issue
+        id="SyntheticAccessor"
+        message="Access to `private` constructor of class `Value` requires synthetic accessor"
+        errorLine1="                            return Value(format, listOf(), parcel.readLong())"
+        errorLine2="                                   ~~~~~">
+        <location
+            file="src/main/java/androidx/health/services/client/data/Value.kt"
+            line="142"
+            column="36"/>
+    </issue>
+
+    <issue
+        id="SyntheticAccessor"
+        message="Access to `private` constructor of class `Value` requires synthetic accessor"
+        errorLine1="                            return Value(format, listOf(parcel.readDouble()), /* longValue= */ 0)"
+        errorLine2="                                   ~~~~~">
+        <location
+            file="src/main/java/androidx/health/services/client/data/Value.kt"
+            line="144"
+            column="36"/>
+    </issue>
+
+    <issue
+        id="SyntheticAccessor"
+        message="Access to `private` constructor of class `Value` requires synthetic accessor"
+        errorLine1="                            return Value(format, doubleArray.toList(), /* longValue= */ 0)"
+        errorLine2="                                   ~~~~~">
+        <location
+            file="src/main/java/androidx/health/services/client/data/Value.kt"
+            line="148"
+            column="36"/>
+    </issue>
+
+    <issue
+        id="LambdaLast"
+        message="Functional interface parameters (such as parameter 1, &quot;operation&quot;, in androidx.health.services.client.impl.ipc.Client.executeWithVersionCheck) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions"
+        errorLine1="            ServiceOperation&lt;R> operation, int minApiVersion) {"
+        errorLine2="                                           ~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/androidx/health/services/client/impl/ipc/Client.java"
+            line="115"
+            column="44"/>
+    </issue>
+
+    <issue
+        id="UnknownNullness"
+        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
+        errorLine1="    public BaseQueueOperation(ConnectionConfiguration connectionConfiguration) {"
+        errorLine2="                              ~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/androidx/health/services/client/impl/ipc/internal/BaseQueueOperation.java"
+            line="37"
+            column="31"/>
+    </issue>
+
+    <issue
+        id="UnknownNullness"
+        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
+        errorLine1="    public void execute(IBinder binder) throws RemoteException {}"
+        errorLine2="                        ~~~~~~~">
+        <location
+            file="src/main/java/androidx/health/services/client/impl/ipc/internal/BaseQueueOperation.java"
+            line="42"
+            column="25"/>
+    </issue>
+
+    <issue
+        id="UnknownNullness"
+        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
+        errorLine1="    public void setException(Throwable exception) {}"
+        errorLine2="                             ~~~~~~~~~">
+        <location
+            file="src/main/java/androidx/health/services/client/impl/ipc/internal/BaseQueueOperation.java"
+            line="45"
+            column="30"/>
+    </issue>
+
+    <issue
+        id="UnknownNullness"
+        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
+        errorLine1="    public QueueOperation trackExecution(ExecutionTracker tracker) {"
+        errorLine2="           ~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/androidx/health/services/client/impl/ipc/internal/BaseQueueOperation.java"
+            line="48"
+            column="12"/>
+    </issue>
+
+    <issue
+        id="UnknownNullness"
+        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
+        errorLine1="    public QueueOperation trackExecution(ExecutionTracker tracker) {"
+        errorLine2="                                         ~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/androidx/health/services/client/impl/ipc/internal/BaseQueueOperation.java"
+            line="48"
+            column="42"/>
+    </issue>
+
+    <issue
+        id="UnknownNullness"
+        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
+        errorLine1="    public ConnectionConfiguration getConnectionConfiguration() {"
+        errorLine2="           ~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/androidx/health/services/client/impl/ipc/internal/BaseQueueOperation.java"
+            line="54"
+            column="12"/>
+    </issue>
+
+    <issue
+        id="UnknownNullness"
+        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
+        errorLine1="        Integer readVersion(IBinder binder) throws RemoteException;"
+        errorLine2="        ~~~~~~~">
+        <location
+            file="src/main/java/androidx/health/services/client/impl/ipc/Client.java"
+            line="53"
+            column="9"/>
+    </issue>
+
+    <issue
+        id="UnknownNullness"
+        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
+        errorLine1="        Integer readVersion(IBinder binder) throws RemoteException;"
+        errorLine2="                            ~~~~~~~">
+        <location
+            file="src/main/java/androidx/health/services/client/impl/ipc/Client.java"
+            line="53"
+            column="29"/>
+    </issue>
+
+    <issue
+        id="UnknownNullness"
+        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
+        errorLine1="            ClientConfiguration clientConfiguration,"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/androidx/health/services/client/impl/ipc/Client.java"
+            line="65"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnknownNullness"
+        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
+        errorLine1="            ConnectionManager connectionManager,"
+        errorLine2="            ~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/androidx/health/services/client/impl/ipc/Client.java"
+            line="66"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnknownNullness"
+        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
+        errorLine1="            VersionGetter versionGetter) {"
+        errorLine2="            ~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/androidx/health/services/client/impl/ipc/Client.java"
+            line="67"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnknownNullness"
+        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
+        errorLine1="    protected &lt;R> ListenableFuture&lt;R> execute(ServiceOperation&lt;R> operation) {"
+        errorLine2="                  ~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/androidx/health/services/client/impl/ipc/Client.java"
+            line="107"
+            column="19"/>
+    </issue>
+
+    <issue
+        id="UnknownNullness"
+        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
+        errorLine1="    protected &lt;R> ListenableFuture&lt;R> execute(ServiceOperation&lt;R> operation) {"
+        errorLine2="                                              ~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/androidx/health/services/client/impl/ipc/Client.java"
+            line="107"
+            column="47"/>
+    </issue>
+
+    <issue
+        id="UnknownNullness"
+        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
+        errorLine1="    protected &lt;R> ListenableFuture&lt;R> executeWithVersionCheck("
+        errorLine2="                  ~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/androidx/health/services/client/impl/ipc/Client.java"
+            line="114"
+            column="19"/>
+    </issue>
+
+    <issue
+        id="UnknownNullness"
+        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
+        errorLine1="            ServiceOperation&lt;R> operation, int minApiVersion) {"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/androidx/health/services/client/impl/ipc/Client.java"
+            line="115"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnknownNullness"
+        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
+        errorLine1="    protected &lt;R> ListenableFuture&lt;R> registerListener("
+        errorLine2="                  ~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/androidx/health/services/client/impl/ipc/Client.java"
+            line="173"
+            column="19"/>
+    </issue>
+
+    <issue
+        id="UnknownNullness"
+        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
+        errorLine1="            ListenerKey listenerKey, ServiceOperation&lt;R> registerListenerOperation) {"
+        errorLine2="            ~~~~~~~~~~~">
+        <location
+            file="src/main/java/androidx/health/services/client/impl/ipc/Client.java"
+            line="174"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnknownNullness"
+        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
+        errorLine1="            ListenerKey listenerKey, ServiceOperation&lt;R> registerListenerOperation) {"
+        errorLine2="                                     ~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/androidx/health/services/client/impl/ipc/Client.java"
+            line="174"
+            column="38"/>
+    </issue>
+
+    <issue
+        id="UnknownNullness"
+        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
+        errorLine1="    protected &lt;R> ListenableFuture&lt;R> unregisterListener("
+        errorLine2="                  ~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/androidx/health/services/client/impl/ipc/Client.java"
+            line="193"
+            column="19"/>
+    </issue>
+
+    <issue
+        id="UnknownNullness"
+        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
+        errorLine1="            ListenerKey listenerKey, ServiceOperation&lt;R> unregisterListenerOperation) {"
+        errorLine2="            ~~~~~~~~~~~">
+        <location
+            file="src/main/java/androidx/health/services/client/impl/ipc/Client.java"
+            line="194"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnknownNullness"
+        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
+        errorLine1="            ListenerKey listenerKey, ServiceOperation&lt;R> unregisterListenerOperation) {"
+        errorLine2="                                     ~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/androidx/health/services/client/impl/ipc/Client.java"
+            line="194"
+            column="38"/>
+    </issue>
+
+    <issue
+        id="UnknownNullness"
+        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
+        errorLine1="    protected Exception getApiVersionCheckFailureException(int currentVersion, int minApiVersion) {"
+        errorLine2="              ~~~~~~~~~">
+        <location
+            file="src/main/java/androidx/health/services/client/impl/ipc/Client.java"
+            line="203"
+            column="15"/>
+    </issue>
+
+    <issue
+        id="UnknownNullness"
+        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
+        errorLine1="    public ClientConfiguration(String apiClientName, String servicePackageName, String bindAction) {"
+        errorLine2="                               ~~~~~~">
+        <location
+            file="src/main/java/androidx/health/services/client/impl/ipc/ClientConfiguration.java"
+            line="34"
+            column="32"/>
+    </issue>
+
+    <issue
+        id="UnknownNullness"
+        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
+        errorLine1="    public ClientConfiguration(String apiClientName, String servicePackageName, String bindAction) {"
+        errorLine2="                                                     ~~~~~~">
+        <location
+            file="src/main/java/androidx/health/services/client/impl/ipc/ClientConfiguration.java"
+            line="34"
+            column="54"/>
+    </issue>
+
+    <issue
+        id="UnknownNullness"
+        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
+        errorLine1="    public ClientConfiguration(String apiClientName, String servicePackageName, String bindAction) {"
+        errorLine2="                                                                                ~~~~~~">
+        <location
+            file="src/main/java/androidx/health/services/client/impl/ipc/ClientConfiguration.java"
+            line="34"
+            column="81"/>
+    </issue>
+
+    <issue
+        id="UnknownNullness"
+        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
+        errorLine1="    public String getServicePackageName() {"
+        errorLine2="           ~~~~~~">
+        <location
+            file="src/main/java/androidx/health/services/client/impl/ipc/ClientConfiguration.java"
+            line="41"
+            column="12"/>
+    </issue>
+
+    <issue
+        id="UnknownNullness"
+        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
+        errorLine1="    public String getBindAction() {"
+        errorLine2="           ~~~~~~">
+        <location
+            file="src/main/java/androidx/health/services/client/impl/ipc/ClientConfiguration.java"
+            line="46"
+            column="12"/>
+    </issue>
+
+    <issue
+        id="UnknownNullness"
+        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
+        errorLine1="    public String getApiClientName() {"
+        errorLine2="           ~~~~~~">
+        <location
+            file="src/main/java/androidx/health/services/client/impl/ipc/ClientConfiguration.java"
+            line="51"
+            column="12"/>
+    </issue>
+
+    <issue
+        id="UnknownNullness"
+        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
+        errorLine1="            String packageName,"
+        errorLine2="            ~~~~~~">
+        <location
+            file="src/main/java/androidx/health/services/client/impl/ipc/internal/ConnectionConfiguration.java"
+            line="38"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnknownNullness"
+        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
+        errorLine1="            String clientName,"
+        errorLine2="            ~~~~~~">
+        <location
+            file="src/main/java/androidx/health/services/client/impl/ipc/internal/ConnectionConfiguration.java"
+            line="39"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnknownNullness"
+        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
+        errorLine1="            String bindAction,"
+        errorLine2="            ~~~~~~">
+        <location
+            file="src/main/java/androidx/health/services/client/impl/ipc/internal/ConnectionConfiguration.java"
+            line="40"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnknownNullness"
+        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
+        errorLine1="            QueueOperation refreshVersionOperation) {"
+        errorLine2="            ~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/androidx/health/services/client/impl/ipc/internal/ConnectionConfiguration.java"
+            line="41"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="UnknownNullness"
+        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
+        errorLine1="    public ConnectionManager(Context context, Looper looper) {"
+        errorLine2="                             ~~~~~~~">
+        <location
+            file="src/main/java/androidx/health/services/client/impl/ipc/internal/ConnectionManager.java"
+            line="52"
+            column="30"/>
+    </issue>
+
+    <issue
+        id="UnknownNullness"
+        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
+        errorLine1="    public ConnectionManager(Context context, Looper looper) {"
+        errorLine2="                                              ~~~~~~">
+        <location
+            file="src/main/java/androidx/health/services/client/impl/ipc/internal/ConnectionManager.java"
+            line="52"
+            column="47"/>
+    </issue>
+
+    <issue
+        id="UnknownNullness"
+        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
+        errorLine1="    public void scheduleForExecution(QueueOperation operation) {"
+        errorLine2="                                     ~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/androidx/health/services/client/impl/ipc/internal/ConnectionManager.java"
+            line="62"
+            column="38"/>
+    </issue>
+
+    <issue
+        id="UnknownNullness"
+        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
+        errorLine1="    public void registerListener(ListenerKey listenerKey, QueueOperation registerOperation) {"
+        errorLine2="                                 ~~~~~~~~~~~">
+        <location
+            file="src/main/java/androidx/health/services/client/impl/ipc/internal/ConnectionManager.java"
+            line="73"
+            column="34"/>
+    </issue>
+
+    <issue
+        id="UnknownNullness"
+        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
+        errorLine1="    public void registerListener(ListenerKey listenerKey, QueueOperation registerOperation) {"
+        errorLine2="                                                          ~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/androidx/health/services/client/impl/ipc/internal/ConnectionManager.java"
+            line="73"
+            column="59"/>
+    </issue>
+
+    <issue
+        id="UnknownNullness"
+        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
+        errorLine1="    public void unregisterListener(ListenerKey listenerKey, QueueOperation unregisterOperation) {"
+        errorLine2="                                   ~~~~~~~~~~~">
+        <location
+            file="src/main/java/androidx/health/services/client/impl/ipc/internal/ConnectionManager.java"
+            line="86"
+            column="36"/>
+    </issue>
+
+    <issue
+        id="UnknownNullness"
+        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
+        errorLine1="    public void unregisterListener(ListenerKey listenerKey, QueueOperation unregisterOperation) {"
+        errorLine2="                                                            ~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/androidx/health/services/client/impl/ipc/internal/ConnectionManager.java"
+            line="86"
+            column="61"/>
+    </issue>
+
+    <issue
+        id="UnknownNullness"
+        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
+        errorLine1="    public void onConnected(ServiceConnection connection) {"
+        errorLine2="                            ~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/androidx/health/services/client/impl/ipc/internal/ConnectionManager.java"
+            line="94"
+            column="29"/>
+    </issue>
+
+    <issue
+        id="UnknownNullness"
+        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
+        errorLine1="    public void onDisconnected(ServiceConnection connection, long reconnectDelayMs) {"
+        errorLine2="                               ~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/androidx/health/services/client/impl/ipc/internal/ConnectionManager.java"
+            line="99"
+            column="32"/>
+    </issue>
+
+    <issue
+        id="UnknownNullness"
+        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
+        errorLine1="    public boolean handleMessage(Message msg) {"
+        errorLine2="                                 ~~~~~~~">
+        <location
+            file="src/main/java/androidx/health/services/client/impl/ipc/internal/ConnectionManager.java"
+            line="110"
+            column="34"/>
+    </issue>
+
+    <issue
+        id="UnknownNullness"
+        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
+        errorLine1="    public void track(SettableFuture&lt;?> future) {"
+        errorLine2="                      ~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/androidx/health/services/client/impl/ipc/internal/DefaultExecutionTracker.java"
+            line="39"
+            column="23"/>
+    </issue>
+
+    <issue
+        id="UnknownNullness"
+        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
+        errorLine1="    public void cancelPendingFutures(Throwable throwable) {"
+        errorLine2="                                     ~~~~~~~~~">
+        <location
+            file="src/main/java/androidx/health/services/client/impl/ipc/internal/DefaultExecutionTracker.java"
+            line="45"
+            column="38"/>
+    </issue>
+
+    <issue
+        id="UnknownNullness"
+        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
+        errorLine1="    void track(SettableFuture&lt;?> future);"
+        errorLine2="               ~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/androidx/health/services/client/impl/ipc/internal/ExecutionTracker.java"
+            line="33"
+            column="16"/>
+    </issue>
+
+    <issue
+        id="UnknownNullness"
+        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
+        errorLine1="    void cancelPendingFutures(Throwable throwable);"
+        errorLine2="                              ~~~~~~~~~">
+        <location
+            file="src/main/java/androidx/health/services/client/impl/ipc/internal/ExecutionTracker.java"
+            line="36"
+            column="31"/>
+    </issue>
+
+    <issue
+        id="UnknownNullness"
+        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
+        errorLine1="    public ListenerKey(Object listenerKey) {"
+        errorLine2="                       ~~~~~~">
+        <location
+            file="src/main/java/androidx/health/services/client/impl/ipc/internal/ListenerKey.java"
+            line="32"
+            column="24"/>
+    </issue>
+
+    <issue
+        id="UnknownNullness"
+        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
+        errorLine1="    void execute(IBinder binder) throws RemoteException;"
+        errorLine2="                 ~~~~~~~">
+        <location
+            file="src/main/java/androidx/health/services/client/impl/ipc/internal/QueueOperation.java"
+            line="38"
+            column="18"/>
+    </issue>
+
+    <issue
+        id="UnknownNullness"
+        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
+        errorLine1="    void setException(Throwable exception);"
+        errorLine2="                      ~~~~~~~~~">
+        <location
+            file="src/main/java/androidx/health/services/client/impl/ipc/internal/QueueOperation.java"
+            line="41"
+            column="23"/>
+    </issue>
+
+    <issue
+        id="UnknownNullness"
+        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
+        errorLine1="    QueueOperation trackExecution(ExecutionTracker tracker);"
+        errorLine2="    ~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/androidx/health/services/client/impl/ipc/internal/QueueOperation.java"
+            line="48"
+            column="5"/>
+    </issue>
+
+    <issue
+        id="UnknownNullness"
+        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
+        errorLine1="    QueueOperation trackExecution(ExecutionTracker tracker);"
+        errorLine2="                                  ~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/androidx/health/services/client/impl/ipc/internal/QueueOperation.java"
+            line="48"
+            column="35"/>
+    </issue>
+
+    <issue
+        id="UnknownNullness"
+        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
+        errorLine1="    ConnectionConfiguration getConnectionConfiguration();"
+        errorLine2="    ~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/androidx/health/services/client/impl/ipc/internal/QueueOperation.java"
+            line="51"
+            column="5"/>
+    </issue>
+
+    <issue
+        id="UnknownNullness"
+        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
+        errorLine1="        void onConnected(ServiceConnection connection);"
+        errorLine2="                         ~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/androidx/health/services/client/impl/ipc/internal/ServiceConnection.java"
+            line="61"
+            column="26"/>
+    </issue>
+
+    <issue
+        id="UnknownNullness"
+        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
+        errorLine1="        void onDisconnected(ServiceConnection connection, long reconnectDelayMs);"
+        errorLine2="                            ~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/androidx/health/services/client/impl/ipc/internal/ServiceConnection.java"
+            line="69"
+            column="29"/>
+    </issue>
+
+    <issue
+        id="UnknownNullness"
+        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
+        errorLine1="    public void onServiceConnected(ComponentName componentName, IBinder binder) {"
+        errorLine2="                                   ~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/androidx/health/services/client/impl/ipc/internal/ServiceConnection.java"
+            line="287"
+            column="36"/>
+    </issue>
+
+    <issue
+        id="UnknownNullness"
+        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
+        errorLine1="    public void onServiceConnected(ComponentName componentName, IBinder binder) {"
+        errorLine2="                                                                ~~~~~~~">
+        <location
+            file="src/main/java/androidx/health/services/client/impl/ipc/internal/ServiceConnection.java"
+            line="287"
+            column="65"/>
+    </issue>
+
+    <issue
+        id="UnknownNullness"
+        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
+        errorLine1="    public void onServiceDisconnected(ComponentName componentName) {"
+        errorLine2="                                      ~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/androidx/health/services/client/impl/ipc/internal/ServiceConnection.java"
+            line="320"
+            column="39"/>
+    </issue>
+
+    <issue
+        id="UnknownNullness"
+        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
+        errorLine1="    public void onBindingDied(ComponentName name) {"
+        errorLine2="                              ~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/androidx/health/services/client/impl/ipc/internal/ServiceConnection.java"
+            line="326"
+            column="31"/>
+    </issue>
+
+    <issue
+        id="UnknownNullness"
+        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
+        errorLine1="    public void onNullBinding(ComponentName name) {"
+        errorLine2="                              ~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/androidx/health/services/client/impl/ipc/internal/ServiceConnection.java"
+            line="332"
+            column="31"/>
+    </issue>
+
+    <issue
+        id="UnknownNullness"
+        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
+        errorLine1="    void execute(IBinder binder, SettableFuture&lt;R> resultFuture) throws RemoteException;"
+        errorLine2="                 ~~~~~~~">
+        <location
+            file="src/main/java/androidx/health/services/client/impl/ipc/ServiceOperation.java"
+            line="44"
+            column="18"/>
+    </issue>
+
+    <issue
+        id="UnknownNullness"
+        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
+        errorLine1="    void execute(IBinder binder, SettableFuture&lt;R> resultFuture) throws RemoteException;"
+        errorLine2="                                 ~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/androidx/health/services/client/impl/ipc/ServiceOperation.java"
+            line="44"
+            column="34"/>
+    </issue>
+
+</issues>
diff --git a/health/health-services-client/src/androidTest/AndroidManifest.xml b/health/health-services-client/src/androidTest/AndroidManifest.xml
new file mode 100644
index 0000000..d6673ca
--- /dev/null
+++ b/health/health-services-client/src/androidTest/AndroidManifest.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  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.
+  -->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="androidx.health.services.client.test">
+</manifest>
+
diff --git a/health/health-services-client/src/main/AndroidManifest.xml b/health/health-services-client/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..75a5742
--- /dev/null
+++ b/health/health-services-client/src/main/AndroidManifest.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  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.
+  -->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="androidx.health.services.client">
+</manifest>
+
diff --git a/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/IExerciseApiService.aidl b/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/IExerciseApiService.aidl
new file mode 100644
index 0000000..6a08a9a
--- /dev/null
+++ b/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/IExerciseApiService.aidl
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 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.health.services.client.impl;
+
+import androidx.health.services.client.impl.IExerciseUpdateListener;
+import androidx.health.services.client.impl.internal.IExerciseInfoCallback;
+import androidx.health.services.client.impl.internal.IStatusCallback;
+import androidx.health.services.client.impl.request.AutoPauseAndResumeConfigRequest;
+import androidx.health.services.client.impl.request.CapabilitiesRequest;
+import androidx.health.services.client.impl.request.ExerciseGoalRequest;
+import androidx.health.services.client.impl.request.StartExerciseRequest;
+import androidx.health.services.client.impl.response.CapabilitiesResponse;
+
+/**
+ * Interface to make ipc calls for health services exercise api.
+ *
+ * The next method added to the interface should use ID: 13
+ * (this id needs to be incremented for each added method)
+ *
+ * @hide
+ */
+interface IExerciseApiService {
+    /**
+     * API version of the AIDL interface. Should be incremented every time a new
+     * method is added.
+     *
+     */
+    const int API_VERSION = 1;
+
+    /**
+     * Handles a given request to start an exercise.
+     */
+    void startExercise(in StartExerciseRequest startExerciseRequest, IStatusCallback statusCallback) = 5;
+
+    /**
+     * Method to pause the active exercise for the calling app.
+     */
+    void pauseExercise(in String packageName, IStatusCallback statusCallback) = 0;
+
+    /**
+     * Method to resume the active exercise for the calling app.
+     */
+    void resumeExercise(in String packageName, IStatusCallback statusCallback) = 1;
+
+    /**
+     * Method to end the active exercise for the calling app.
+     */
+    void endExercise(in String packageName, IStatusCallback statusCallback) = 2;
+
+    /**
+    * Method to end the current lap in the active exercise for the calling app.
+    */
+    void markLap(in String packageName, IStatusCallback statusCallback) = 9;
+
+    /**
+     * Returns version of this AIDL interface.
+     *
+     * <p> Can be used by client to detect version of the API on the service
+     * side. Returned version should be always > 0.
+     */
+    int getApiVersion() = 3;
+
+    /**
+     * Returns the current exercise info.
+     */
+    void getCurrentExerciseInfo(in String packageName, IExerciseInfoCallback exerciseInfoCallback) = 12;
+
+    /**
+     * Sets the listener for the current exercise state.
+     */
+    void setUpdateListener(in String packageName, in IExerciseUpdateListener listener, IStatusCallback statusCallback)  = 6;
+
+    /**
+     * Clears the listener set using {@link #setUpdateListener}.
+     */
+    void clearUpdateListener(in String packageName, in IExerciseUpdateListener listener, IStatusCallback statusCallback) = 7;
+
+    /**
+     * Adds an exercise goal for an active exercise.
+     *
+     * <p>An exercise goal is a one-time goal, such as achieving a target total step count.
+     *
+     * <p>Goals apply to only active exercises owned by the client, and will be invalidated once the
+     * exercise is complete. A goal can be added only after an exercise has been started.
+     */
+    void addGoalToActiveExercise(in ExerciseGoalRequest request, IStatusCallback statusCallback) = 8;
+
+    /**
+     * Sets whether auto-pause should be enabled
+     */
+    void overrideAutoPauseAndResumeForActiveExercise(in AutoPauseAndResumeConfigRequest request, IStatusCallback statusCallback) = 10;
+
+    /**
+     * Method to get capabilities.
+     */
+    CapabilitiesResponse getCapabilities(in CapabilitiesRequest request) = 11;
+}
\ No newline at end of file
diff --git a/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/IExerciseUpdateListener.aidl b/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/IExerciseUpdateListener.aidl
new file mode 100644
index 0000000..df6420bd
--- /dev/null
+++ b/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/IExerciseUpdateListener.aidl
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 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.health.services.client.impl;
+
+import androidx.health.services.client.impl.response.ExerciseLapSummaryResponse;
+import androidx.health.services.client.impl.response.ExerciseUpdateResponse;
+
+/**
+ * Interface to get exercise updates.
+ *
+ * @hide
+ */
+oneway interface IExerciseUpdateListener {
+    /** Called when there is an update of exercise state or metrics. */
+    void onExerciseUpdate(in ExerciseUpdateResponse update) = 0;
+
+    /** Called when a lap has been marked. */
+    void onLapSummary(in ExerciseLapSummaryResponse summaryResponse) = 1;
+}
\ No newline at end of file
diff --git a/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/IHealthServicesApiService.aidl b/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/IHealthServicesApiService.aidl
new file mode 100644
index 0000000..ddccc54
--- /dev/null
+++ b/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/IHealthServicesApiService.aidl
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 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.health.services.client.impl;
+
+/**
+ * Interface to make ipc calls for health services api.
+ *
+ * @hide
+ */
+interface IHealthServicesApiService {
+    /**
+     * API version of the AIDL interface. Should be incremented every time a new
+     * method is added.
+     */
+    const int API_VERSION = 1;
+
+    /**
+     * Returns version of this AIDL interface.
+     *
+     * <p> Can be used by client to detect version of the API on the service
+     * side. Returned version should be always > 0.
+     */
+    int getApiVersion() = 1;
+}
diff --git a/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/IMeasureApiService.aidl b/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/IMeasureApiService.aidl
new file mode 100644
index 0000000..2710467
--- /dev/null
+++ b/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/IMeasureApiService.aidl
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 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.health.services.client.impl;
+
+import androidx.health.services.client.impl.IMeasureCallback;
+import androidx.health.services.client.impl.internal.IStatusCallback;
+import androidx.health.services.client.impl.request.CapabilitiesRequest;
+import androidx.health.services.client.impl.request.MeasureRegistrationRequest;
+import androidx.health.services.client.impl.request.MeasureUnregistrationRequest;
+import androidx.health.services.client.impl.response.MeasureCapabilitiesResponse;
+
+/**
+ * Interface to make ipc calls for health services api.
+ *
+ * @hide
+ */
+interface IMeasureApiService {
+    /**
+     * API version of the AIDL interface. Should be incremented every time a new
+     * method is added.
+     */
+    const int API_VERSION = 1;
+
+    /**
+     * Method to register measure listener.
+     */
+    void registerCallback(in MeasureRegistrationRequest request, in IMeasureCallback callback, in IStatusCallback statusCallback) = 0;
+
+    /**
+     * Method to unregister measure listener.
+     */
+    void unregisterCallback(in MeasureUnregistrationRequest request, in IMeasureCallback callback, in IStatusCallback statusCallback) = 1;
+
+    /**
+     * Returns version of this AIDL interface.
+     *
+     * <p> Can be used by client to detect version of the API on the service
+     * side. Returned version should be always > 0.
+     */
+    int getApiVersion() = 2;
+
+   /** Method to get capabilities. */
+    MeasureCapabilitiesResponse getCapabilities(in CapabilitiesRequest request) = 3;
+}
diff --git a/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/IMeasureCallback.aidl b/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/IMeasureCallback.aidl
new file mode 100644
index 0000000..ff7f983
--- /dev/null
+++ b/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/IMeasureCallback.aidl
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 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.health.services.client.impl;
+
+import androidx.health.services.client.impl.response.AvailabilityResponse;
+import androidx.health.services.client.impl.response.DataPointsResponse;
+
+/**
+ * Interface to get callback for measure api.
+ *
+ * @hide
+ */
+oneway interface IMeasureCallback {
+    void onAvailabilityChanged(in AvailabilityResponse response) = 0;
+
+    void onData(in DataPointsResponse response) = 1;
+}
\ No newline at end of file
diff --git a/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/IPassiveMonitoringApiService.aidl b/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/IPassiveMonitoringApiService.aidl
new file mode 100644
index 0000000..d6be3f0
--- /dev/null
+++ b/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/IPassiveMonitoringApiService.aidl
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 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.health.services.client.impl;
+
+import android.app.PendingIntent;
+import androidx.health.services.client.impl.IPassiveMonitoringCallback;
+import androidx.health.services.client.impl.internal.IStatusCallback;
+import androidx.health.services.client.impl.request.BackgroundRegistrationRequest;
+import androidx.health.services.client.impl.request.CapabilitiesRequest;
+import androidx.health.services.client.impl.request.EventRequest;
+import androidx.health.services.client.impl.response.PassiveMonitoringCapabilitiesResponse;
+
+/** @hide */
+interface IPassiveMonitoringApiService {
+    /**
+     * API version of the AIDL interface. Should be incremented every time a new
+     * method is added.
+     */
+    const int API_VERSION = 1;
+
+    /**
+     * Method to subscribe to an event with corresponding callback intent.
+     */
+    void registerEventCallback(in EventRequest request, in PendingIntent intent, in IStatusCallback statusCallback) = 0;
+
+    /**
+     * Method to subscribe to a set of data types with corresponding callback
+     * intent and an optional callback.
+     *
+     * <p>If a callback is present and is active, updates are provided via the callback. Otherwise,
+     * the provided PendingIntent gets the updates.
+     */
+    void registerDataCallback(in BackgroundRegistrationRequest request, in PendingIntent fallbackIntent, in IPassiveMonitoringCallback callback, in IStatusCallback statusCallback) = 1;
+
+    /**
+     * Method to subscribe to a set of data types with corresponding callback intent.
+     */
+    void unregisterDataCallback(in String packageName, in IStatusCallback statusCallback) = 3;
+
+    /**
+     * Method to subscribe to a set of data types with corresponding callback intent.
+     */
+    void unregisterEventCallback(in EventRequest request, in IStatusCallback statusCallback) = 4;
+
+    /**
+     * Returns version of this AIDL interface.
+     *
+     * <p> Can be used by client to detect version of the API on the service
+     * side. Returned version should be always > 0.
+     */
+    int getApiVersion() = 2;
+
+   /** Method to get capabilities. */
+   PassiveMonitoringCapabilitiesResponse getCapabilities(in CapabilitiesRequest request) = 5;
+}
diff --git a/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/IPassiveMonitoringCallback.aidl b/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/IPassiveMonitoringCallback.aidl
new file mode 100644
index 0000000..2eafb48
--- /dev/null
+++ b/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/IPassiveMonitoringCallback.aidl
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 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.health.services.client.impl;
+
+import androidx.health.services.client.impl.response.PassiveActivityStateResponse;
+
+/**
+ * Interface to get passive monitoring updates.
+ *
+ * @hide
+ */
+oneway interface IPassiveMonitoringCallback {
+    /** Called when a new passive activity state response is available. */
+    void onPassiveActivityState(in PassiveActivityStateResponse response) = 0;
+}
\ No newline at end of file
diff --git a/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/internal/IExerciseInfoCallback.aidl b/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/internal/IExerciseInfoCallback.aidl
new file mode 100644
index 0000000..cfc56ca
--- /dev/null
+++ b/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/internal/IExerciseInfoCallback.aidl
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 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.health.services.client.impl.internal;
+
+import androidx.health.services.client.impl.response.ExerciseInfoResponse;
+
+/**
+ * Callback for an operation that returns an ExerciseInfo on successful
+ * completion.
+ *
+ * @hide
+ */
+oneway interface IExerciseInfoCallback {
+    /**
+     * Method invoked when the operation is a success and exercise info is
+     * successfully obtained.
+     */
+    void onExerciseInfo(in ExerciseInfoResponse response) = 0;
+
+    /**
+     * Method invoked when the operation is a failure.
+     */
+    void onFailure(String message) = 1;
+}
\ No newline at end of file
diff --git a/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/internal/IStatusCallback.aidl b/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/internal/IStatusCallback.aidl
new file mode 100644
index 0000000..2a58b96
--- /dev/null
+++ b/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/internal/IStatusCallback.aidl
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 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.health.services.client.impl.internal;
+
+/**
+ * Generic callback for an operation that returns a status on completion.
+ *
+ * @hide
+ */
+oneway interface IStatusCallback {
+    /**
+     * Method invoked when the operation is a success.
+     */
+    void onSuccess() = 0;
+
+    /**
+     * Method invoked when the operation is a failure.
+     */
+    void onFailure(String msg) = 1;
+}
\ No newline at end of file
diff --git a/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/request/AutoPauseAndResumeConfigRequest.aidl b/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/request/AutoPauseAndResumeConfigRequest.aidl
new file mode 100644
index 0000000..3f7984c
--- /dev/null
+++ b/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/request/AutoPauseAndResumeConfigRequest.aidl
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 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.health.services.client.impl.request;
+
+/** @hide */
+parcelable AutoPauseAndResumeConfigRequest;
\ No newline at end of file
diff --git a/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/request/BackgroundRegistrationRequest.aidl b/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/request/BackgroundRegistrationRequest.aidl
new file mode 100644
index 0000000..7493c72
--- /dev/null
+++ b/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/request/BackgroundRegistrationRequest.aidl
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 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.health.services.client.impl.request;
+
+/** @hide */
+parcelable BackgroundRegistrationRequest;
\ No newline at end of file
diff --git a/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/request/BackgroundUnregistrationRequest.aidl b/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/request/BackgroundUnregistrationRequest.aidl
new file mode 100644
index 0000000..8264c63
--- /dev/null
+++ b/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/request/BackgroundUnregistrationRequest.aidl
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 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.health.services.client.impl.request;
+
+/** @hide */
+parcelable BackgroundUnregistrationRequest;
\ No newline at end of file
diff --git a/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/request/CapabilitiesRequest.aidl b/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/request/CapabilitiesRequest.aidl
new file mode 100644
index 0000000..b71b20a
--- /dev/null
+++ b/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/request/CapabilitiesRequest.aidl
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 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.health.services.client.impl.request;
+
+/** @hide */
+parcelable CapabilitiesRequest;
\ No newline at end of file
diff --git a/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/request/EventRequest.aidl b/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/request/EventRequest.aidl
new file mode 100644
index 0000000..1b59985
--- /dev/null
+++ b/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/request/EventRequest.aidl
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 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.health.services.client.impl.request;
+
+/** @hide */
+parcelable EventRequest;
\ No newline at end of file
diff --git a/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/request/ExerciseGoalRequest.aidl b/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/request/ExerciseGoalRequest.aidl
new file mode 100644
index 0000000..d8a9fbb
--- /dev/null
+++ b/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/request/ExerciseGoalRequest.aidl
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 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.health.services.client.impl.request;
+
+/** @hide */
+parcelable ExerciseGoalRequest;
\ No newline at end of file
diff --git a/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/request/MeasureRegistrationRequest.aidl b/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/request/MeasureRegistrationRequest.aidl
new file mode 100644
index 0000000..96acaec
--- /dev/null
+++ b/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/request/MeasureRegistrationRequest.aidl
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 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.health.services.client.impl.request;
+
+/** @hide */
+parcelable MeasureRegistrationRequest;
\ No newline at end of file
diff --git a/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/request/MeasureUnregistrationRequest.aidl b/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/request/MeasureUnregistrationRequest.aidl
new file mode 100644
index 0000000..fb2f060
--- /dev/null
+++ b/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/request/MeasureUnregistrationRequest.aidl
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 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.health.services.client.impl.request;
+
+/** @hide */
+parcelable MeasureUnregistrationRequest;
\ No newline at end of file
diff --git a/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/request/StartExerciseRequest.aidl b/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/request/StartExerciseRequest.aidl
new file mode 100644
index 0000000..dd5e594
--- /dev/null
+++ b/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/request/StartExerciseRequest.aidl
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 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.health.services.client.impl.request;
+
+/** @hide */
+parcelable StartExerciseRequest;
\ No newline at end of file
diff --git a/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/response/AutoExerciseCapabilitiesResponse.aidl b/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/response/AutoExerciseCapabilitiesResponse.aidl
new file mode 100644
index 0000000..2641359
--- /dev/null
+++ b/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/response/AutoExerciseCapabilitiesResponse.aidl
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 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.health.services.client.impl.response;
+
+/** @hide */
+parcelable AutoExerciseCapabilitiesResponse;
\ No newline at end of file
diff --git a/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/response/AutoExerciseDetectionStateResponse.aidl b/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/response/AutoExerciseDetectionStateResponse.aidl
new file mode 100644
index 0000000..4b38fe1
--- /dev/null
+++ b/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/response/AutoExerciseDetectionStateResponse.aidl
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 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.health.services.client.impl.response;
+
+/** @hide */
+parcelable AutoExerciseDetectionStateResponse;
\ No newline at end of file
diff --git a/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/response/AvailabilityResponse.aidl b/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/response/AvailabilityResponse.aidl
new file mode 100644
index 0000000..902c1ff
--- /dev/null
+++ b/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/response/AvailabilityResponse.aidl
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 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.health.services.client.impl.response;
+
+/** @hide */
+parcelable AvailabilityResponse;
\ No newline at end of file
diff --git a/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/response/CapabilitiesResponse.aidl b/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/response/CapabilitiesResponse.aidl
new file mode 100644
index 0000000..c48b0d0
--- /dev/null
+++ b/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/response/CapabilitiesResponse.aidl
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 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.health.services.client.impl.response;
+
+/** @hide */
+parcelable CapabilitiesResponse;
\ No newline at end of file
diff --git a/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/response/DataPointsResponse.aidl b/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/response/DataPointsResponse.aidl
new file mode 100644
index 0000000..31ab81c
--- /dev/null
+++ b/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/response/DataPointsResponse.aidl
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 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.health.services.client.impl.response;
+
+/** @hide */
+parcelable DataPointsResponse;
\ No newline at end of file
diff --git a/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/response/ExerciseInfoResponse.aidl b/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/response/ExerciseInfoResponse.aidl
new file mode 100644
index 0000000..8d429af
--- /dev/null
+++ b/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/response/ExerciseInfoResponse.aidl
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 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.health.services.client.impl.response;
+
+/** @hide */
+parcelable ExerciseInfoResponse;
\ No newline at end of file
diff --git a/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/response/ExerciseLapSummaryResponse.aidl b/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/response/ExerciseLapSummaryResponse.aidl
new file mode 100644
index 0000000..55dd959
--- /dev/null
+++ b/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/response/ExerciseLapSummaryResponse.aidl
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 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.health.services.client.impl.response;
+
+/** @hide */
+parcelable ExerciseLapSummaryResponse;
\ No newline at end of file
diff --git a/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/response/ExerciseUpdateResponse.aidl b/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/response/ExerciseUpdateResponse.aidl
new file mode 100644
index 0000000..d10ab6c
--- /dev/null
+++ b/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/response/ExerciseUpdateResponse.aidl
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 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.health.services.client.impl.response;
+
+/** @hide */
+parcelable ExerciseUpdateResponse;
\ No newline at end of file
diff --git a/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/response/HeartRateAlertParamsResponse.aidl b/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/response/HeartRateAlertParamsResponse.aidl
new file mode 100644
index 0000000..6464078
--- /dev/null
+++ b/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/response/HeartRateAlertParamsResponse.aidl
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 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.health.services.client.impl.response;
+
+/** @hide */
+parcelable HeartRateAlertParamsResponse;
\ No newline at end of file
diff --git a/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/response/MeasureCapabilitiesResponse.aidl b/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/response/MeasureCapabilitiesResponse.aidl
new file mode 100644
index 0000000..d9755f1
--- /dev/null
+++ b/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/response/MeasureCapabilitiesResponse.aidl
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 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.health.services.client.impl.response;
+
+/** @hide */
+parcelable MeasureCapabilitiesResponse;
\ No newline at end of file
diff --git a/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/response/PassiveActivityStateResponse.aidl b/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/response/PassiveActivityStateResponse.aidl
new file mode 100644
index 0000000..5e3b130
--- /dev/null
+++ b/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/response/PassiveActivityStateResponse.aidl
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 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.health.services.client.impl.response;
+
+/** @hide */
+parcelable PassiveActivityStateResponse;
\ No newline at end of file
diff --git a/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/response/PassiveMonitoringCapabilitiesResponse.aidl b/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/response/PassiveMonitoringCapabilitiesResponse.aidl
new file mode 100644
index 0000000..8a5ab5f
--- /dev/null
+++ b/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/response/PassiveMonitoringCapabilitiesResponse.aidl
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 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.health.services.client.impl.response;
+
+/** @hide */
+parcelable PassiveMonitoringCapabilitiesResponse;
\ No newline at end of file
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/ExerciseClient.kt b/health/health-services-client/src/main/java/androidx/health/services/client/ExerciseClient.kt
new file mode 100644
index 0000000..42a190e
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/ExerciseClient.kt
@@ -0,0 +1,151 @@
+/*
+ * Copyright (C) 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.health.services.client
+
+import androidx.health.services.client.data.Capabilities
+import androidx.health.services.client.data.ExerciseConfig
+import androidx.health.services.client.data.ExerciseGoal
+import androidx.health.services.client.data.ExerciseInfo
+import androidx.health.services.client.data.ExerciseUpdate
+import com.google.common.util.concurrent.ListenableFuture
+import java.util.concurrent.Executor
+
+/** Client which provides a way to subscribe to the health data of a device during an exercise. */
+public interface ExerciseClient {
+    /**
+     * Starts a new exercise.
+     *
+     * Once started, WHS will begin collecting data associated with the exercise.
+     *
+     * Since WHS only allows a single active exercise at a time, this will terminate any active
+     * exercise currently in progress before starting the new one.
+     *
+     * @return a [ListenableFuture] that completes once the exercise has been started.
+     */
+    public fun startExercise(configuration: ExerciseConfig): ListenableFuture<Void>
+
+    /**
+     * Pauses the current exercise, if it is currently started.
+     *
+     * While the exercise is paused active time and cumulative metrics such as distance will not
+     * accumulate. Instantaneous measurements such as speed and heart rate will continue to update
+     * if requested in the [ExerciseConfig].
+     *
+     * If the exercise remains paused for a long period of time WHS will reduce or suspend access to
+     * sensors and GPS in order to conserve battery. Should this happen, access will automatically
+     * resume when the exercise is resumed.
+     *
+     * If the exercise is already paused then this method has no effect. If the exercise has ended
+     * then the returned future will fail.
+     *
+     * @return a [ListenableFuture] that completes once the exercise has been paused.
+     */
+    public fun pauseExercise(): ListenableFuture<Void>
+
+    /**
+     * Resumes the current exercise, if it is currently paused.
+     *
+     * Once resumed active time and cumulative metrics such as distance will resume accumulating.
+     *
+     * If the exercise has been started but is not currently paused this method has no effect. If
+     * the exercise has ended then the returned future will fail.
+     *
+     * @return a [ListenableFuture] that completes once the exercise has been resumed.
+     */
+    public fun resumeExercise(): ListenableFuture<Void>
+
+    /**
+     * Ends the current exercise, if it has been started. If the exercise has ended then this future
+     * will fail.
+     *
+     * No additional metrics will be produced for the exercise and any on device persisted data
+     * about the exercise will be deleted after the summary has been sent back.
+     */
+    public fun endExercise(): ListenableFuture<Void>
+
+    /**
+     * Ends the current lap, calls [ExerciseStateListener.onLapSummary] with data spanning the
+     * marked lap and starts a new lap. If the exercise supports laps this method can be called at
+     * any point after an exercise has been started and before it has been ended regardless of the
+     * exercise status.
+     *
+     * The metrics in the lap summary will start from either the start time of the exercise or the
+     * last time a lap was marked to the time this method is being called.
+     *
+     * If there's no exercise being tracked or if the exercise does not support laps then this
+     * future will fail.
+     */
+    public fun markLap(): ListenableFuture<Void>
+
+    /** Returns the [ExerciseInfo]. */
+    public val currentExerciseInfo: ListenableFuture<ExerciseInfo>
+
+    /**
+     * Sets the listener for the current [ExerciseUpdate].
+     *
+     * This listener won't be called until an exercise is in progress. It will also only receive
+     * updates from exercises tracked in this app.
+     *
+     * If an exercise is in progress, the [ExerciseUpdateListener] is immediately called with the
+     * associated [ExerciseUpdate], and subsequently whenever the state is updated or an event is
+     * triggered.
+     *
+     * Calls to the listener will be executed on the main application thread. To control where to
+     * execute the listener, see the overload taking an [Executor]. To remove the listener use
+     * [clearUpdateListener].
+     */
+    public fun setUpdateListener(listener: ExerciseUpdateListener): ListenableFuture<Void>
+
+    /**
+     * Calls to the listener will be executed using the specified [Executor]. To execute the
+     * listener on the main application thread use the overload without the [Executor].
+     */
+    public fun setUpdateListener(
+        listener: ExerciseUpdateListener,
+        executor: Executor
+    ): ListenableFuture<Void>
+
+    /**
+     * Clears the listener set using [setUpdateListener].
+     *
+     * If the listener wasn't set, the returned [ListenableFuture] will fail.
+     */
+    public fun clearUpdateListener(listener: ExerciseUpdateListener): ListenableFuture<Void>
+
+    /**
+     * Adds an [ExerciseGoal] for an active exercise.
+     *
+     * An [ExerciseGoal] is a one-time goal, such as achieving a target total step count.
+     *
+     * Goals apply to only active exercises owned by the client, and will be invalidated once the
+     * exercise is complete.
+     *
+     * @return a [ListenableFuture] that completes once the exercise goal has been added. This
+     * returned [ListenableFuture] fails if the exercise is not active.
+     */
+    public fun addGoalToActiveExercise(exerciseGoal: ExerciseGoal): ListenableFuture<Void>
+
+    /**
+     * Enables or disables the auto pause/resume for the current exercise.
+     *
+     * @param enabled a boolean to indicate if should be enabled or disabled
+     */
+    public fun overrideAutoPauseAndResumeForActiveExercise(enabled: Boolean): ListenableFuture<Void>
+
+    /** Returns the [Capabilities] of this client for the device. */
+    public val capabilities: ListenableFuture<Capabilities>
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/ExerciseUpdateListener.kt b/health/health-services-client/src/main/java/androidx/health/services/client/ExerciseUpdateListener.kt
new file mode 100644
index 0000000..d622d43
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/ExerciseUpdateListener.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 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.health.services.client
+
+import androidx.health.services.client.data.ExerciseLapSummary
+import androidx.health.services.client.data.ExerciseState
+import androidx.health.services.client.data.ExerciseUpdate
+
+/** Listener that is called when the state of the current exercise is updated. */
+// TODO(b/179756577): Add onExerciseEnd(ExerciseSummary) method.
+public interface ExerciseUpdateListener {
+    /** Called during an ACTIVE exercise or on any changes in [ExerciseState]. */
+    public fun onExerciseUpdate(update: ExerciseUpdate)
+
+    /** Called during an ACTIVE exercise once a lap has been marked. */
+    public fun onLapSummary(lapSummary: ExerciseLapSummary)
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/HealthServices.kt b/health/health-services-client/src/main/java/androidx/health/services/client/HealthServices.kt
new file mode 100644
index 0000000..8fba5b9
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/HealthServices.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 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.health.services.client
+
+import android.content.Context
+import androidx.health.services.client.impl.ServiceBackedHealthServicesClient
+
+/** Entry point for all Health Services APIs. */
+public object HealthServices {
+    /** Returns an instance of [HealthServicesClient]. */
+    @JvmStatic
+    public fun getClient(context: Context): HealthServicesClient {
+        return ServiceBackedHealthServicesClient(context)
+    }
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/HealthServicesClient.kt b/health/health-services-client/src/main/java/androidx/health/services/client/HealthServicesClient.kt
new file mode 100644
index 0000000..82c3d41
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/HealthServicesClient.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 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.health.services.client
+
+/**
+ * Client which provides a way to subscribe to the health data of a device, in the background or in
+ * the foreground.
+ */
+public interface HealthServicesClient {
+    /** Returns a [ExerciseClient]. */
+    public val exerciseClient: ExerciseClient
+
+    /** Returns a [PassiveMonitoringClient]. */
+    public val passiveMonitoringClient: PassiveMonitoringClient
+
+    /** Returns a [MeasureClient]. */
+    public val measureClient: MeasureClient
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/MeasureCallback.kt b/health/health-services-client/src/main/java/androidx/health/services/client/MeasureCallback.kt
new file mode 100644
index 0000000..7706b42
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/MeasureCallback.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 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.health.services.client
+
+import androidx.health.services.client.data.Availability
+import androidx.health.services.client.data.DataPoint
+import androidx.health.services.client.data.DataType
+
+/** Callback for [MeasureClient.registerCallback]. */
+public interface MeasureCallback {
+    /** Called when the availability of a [DataType] changes. */
+    public fun onAvailabilityChanged(dataType: DataType, availability: Availability)
+
+    /** Called when new data is available. Data can be batched in a list of [DataPoint]. */
+    public fun onData(data: List<@JvmSuppressWildcards DataPoint>)
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/MeasureClient.kt b/health/health-services-client/src/main/java/androidx/health/services/client/MeasureClient.kt
new file mode 100644
index 0000000..782ae0d
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/MeasureClient.kt
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 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.health.services.client
+
+import androidx.health.services.client.data.DataType
+import androidx.health.services.client.data.MeasureCapabilities
+import com.google.common.util.concurrent.ListenableFuture
+import java.util.concurrent.Executor
+
+/**
+ * Client which provides a way to make measurements of health data on a device.
+ *
+ * This is optimized for apps to register live callbacks on data which may be sampled at a faster
+ * rate; this is not meant to be used for long-lived subscriptions to data (for this, consider using
+ * [ExerciseClient] or [PassiveMonitoringClient] depending on your use case).
+ *
+ * Existing subscriptions made with the [PassiveMonitoringClient] are also expected to get the data
+ * generated by this client.
+ */
+public interface MeasureClient {
+    /**
+     * Registers the app for live measurement of the specified [DataType].
+     *
+     * The callback will be called on the main application thread. To move calls to an alternative
+     * thread use [registerCallback].
+     *
+     * Even if data is registered for live capture, it can still be sent out in batches depending on
+     * the application processor state.
+     *
+     * Registering a [DataType] for live measurement capture is expected to increase the sample rate
+     * on the associated sensor(s); this is typically used for one-off measurements. Do not use this
+     * method for background capture or workout tracking.
+     *
+     * The callback will continue to be called until the app is killed or [unregisterCallback] is
+     * called.
+     *
+     * If the same [callback] is already registered for the given [DataType], this operation is a
+     * no-op.
+     */
+    public fun registerCallback(
+        dataType: DataType,
+        callback: MeasureCallback
+    ): ListenableFuture<Void>
+
+    /** Same as [registerCallback], except the [callback] is called on the given [Executor]. */
+    public fun registerCallback(
+        dataType: DataType,
+        callback: MeasureCallback,
+        executor: Executor
+    ): ListenableFuture<Void>
+
+    /** Unregisters the given [MeasureCallback] for updates of the given [DataType]. */
+    public fun unregisterCallback(
+        dataType: DataType,
+        callback: MeasureCallback
+    ): ListenableFuture<Void>
+
+    /** Returns the [MeasureCapabilities] of this client for the device. */
+    public val capabilities: ListenableFuture<MeasureCapabilities>
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/PassiveMonitoringCallback.kt b/health/health-services-client/src/main/java/androidx/health/services/client/PassiveMonitoringCallback.kt
new file mode 100644
index 0000000..2463188
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/PassiveMonitoringCallback.kt
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 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.health.services.client
+
+import androidx.health.services.client.data.PassiveActivityState
+
+/** A callback for receiving passive monitoring updates. */
+public interface PassiveMonitoringCallback {
+    /** Called when new state is available. */
+    public fun onPassiveActivityState(state: PassiveActivityState)
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/PassiveMonitoringClient.kt b/health/health-services-client/src/main/java/androidx/health/services/client/PassiveMonitoringClient.kt
new file mode 100644
index 0000000..23793f7
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/PassiveMonitoringClient.kt
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 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.health.services.client
+
+import android.app.PendingIntent
+import androidx.health.services.client.data.DataType
+import androidx.health.services.client.data.PassiveMonitoringCapabilities
+import androidx.health.services.client.data.event.Event
+import com.google.common.util.concurrent.ListenableFuture
+
+/**
+ * Client which provides a means to passively monitor data without requiring an ongoing workout.
+ *
+ * The lifetimes of registrations made through this client are independent of the lifetime of the
+ * subscribing app. These registrations are therefore suitable for notifying of ongoing measurements
+ * or triggered events, regardless of whether or not the subscribing app is currently running, in
+ * the foreground or engaged in a workout.
+ */
+public interface PassiveMonitoringClient {
+    /**
+     * Subscribes for updates on a set of data types to be periodically delivered to the app.
+     *
+     * Data will be batched. Higher frequency updates are available through [ExerciseClient] or
+     * [MeasureClient].
+     *
+     * The provided [PendingIntent] will be invoked periodically with the collected data.
+     *
+     * Subscribing apps are responsible for ensuring they can receive the [callbackIntent] by e.g.
+     * declaring a suitable [android.content.BroadcastReceiver] in their app manifest.
+     *
+     * This registration is unique per subscribing app. Subsequent registrations will replace the
+     * previous registration, if one had been made.
+     */
+    public fun registerDataCallback(
+        dataTypes: Set<@JvmSuppressWildcards DataType>,
+        callbackIntent: PendingIntent
+    ): ListenableFuture<Void>
+
+    /**
+     * Subscribes an intent callback (the same way as [PassiveMonitoringClient.registerDataCallback]
+     * ) and a [PassiveMonitoringCallback] for updates on a set of data types periodically.
+     *
+     * The provided [callback] will take priority in receiving updates as long the app is alive and
+     * the callback can be successfully notified. Otherwise, updates will be delivered to the
+     * [callbackIntent].
+     *
+     * This registration is unique per subscribing app. Subsequent registrations will replace the
+     * previous registration, if one had been made.
+     */
+    public fun registerDataCallback(
+        dataTypes: Set<@JvmSuppressWildcards DataType>,
+        callbackIntent: PendingIntent,
+        callback: PassiveMonitoringCallback
+    ): ListenableFuture<Void>
+
+    /**
+     * Unregisters the subscription made by [PassiveMonitoringClient.registerDataCallback].
+     *
+     * The associated [PendingIntent] will be called one last time with any remaining buffered data.
+     */
+    public fun unregisterDataCallback(): ListenableFuture<Void>
+
+    /**
+     * Registers for notification of the [event] being triggered.
+     *
+     * The provided [PendingIntent] will be sent whenever [event] is triggered.
+     *
+     * Subscribing apps are responsible for ensuring they can receive the [callbackIntent] by e.g.
+     * declaring a suitable [android.content.BroadcastReceiver] in their app manifest.
+     *
+     * Registration of multiple events is possible except where there already exists an event that
+     * is equal, as per the definition of [Event.equals], in which case the existing registration
+     * for that event will be replaced.
+     */
+    public fun registerEventCallback(
+        event: Event,
+        callbackIntent: PendingIntent
+    ): ListenableFuture<Void>
+
+    /** Unregisters the subscription for the given [Event]. */
+    public fun unregisterEventCallback(event: Event): ListenableFuture<Void>
+
+    /** Returns the [PassiveMonitoringCapabilities] of this client for the device. */
+    public val capabilities: ListenableFuture<PassiveMonitoringCapabilities>
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/data/AchievedExerciseGoal.kt b/health/health-services-client/src/main/java/androidx/health/services/client/data/AchievedExerciseGoal.kt
new file mode 100644
index 0000000..6463d58
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/data/AchievedExerciseGoal.kt
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 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.health.services.client.data
+
+import android.os.Parcel
+import android.os.Parcelable
+
+/** Defines an achieved [ExerciseGoal]. */
+public data class AchievedExerciseGoal(
+    /** [ExerciseGoal] that has been achieved. */
+    // TODO(b/181235444): do we need to deliver the DataPoint to the user again here, given
+    // that they will have already gotten it in the ExerciseState? And, what other data do we need
+    // to
+    // tag along an achieved ExerciseGoal?
+    val goal: ExerciseGoal,
+) : Parcelable {
+    override fun describeContents(): Int = 0
+
+    override fun writeToParcel(dest: Parcel, flags: Int) {
+        dest.writeParcelable(goal, flags)
+    }
+
+    public companion object {
+        @JvmField
+        public val CREATOR: Parcelable.Creator<AchievedExerciseGoal> =
+            object : Parcelable.Creator<AchievedExerciseGoal> {
+                override fun createFromParcel(source: Parcel): AchievedExerciseGoal? {
+                    val goal =
+                        source.readParcelable<ExerciseGoal>(ExerciseGoal::class.java.classLoader)
+                            ?: return null
+                    return AchievedExerciseGoal(goal)
+                }
+
+                override fun newArray(size: Int): Array<AchievedExerciseGoal?> {
+                    return arrayOfNulls(size)
+                }
+            }
+    }
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/data/AutoExerciseConfig.kt b/health/health-services-client/src/main/java/androidx/health/services/client/data/AutoExerciseConfig.kt
new file mode 100644
index 0000000..710346f
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/data/AutoExerciseConfig.kt
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 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.health.services.client.data
+
+import android.app.PendingIntent
+import android.os.Bundle
+import android.os.Parcel
+import android.os.Parcelable
+import java.util.Objects
+
+/** Configs for the automatic exercise detection. */
+public data class AutoExerciseConfig
+@JvmOverloads
+constructor(
+    /**
+     * Set of [ExerciseType] for WHS to detect from. If left empty, all possible types are used].
+     */
+    val exercisesToDetect: Set<ExerciseType> = emptySet(),
+    /**
+     * A [PendingIntent] that WHS will use to post state / data changes to the app if the app has no
+     * active [androidx.health.services.client.ExerciseUpdateListener] registered at the moment. The
+     * launchIntent will be fed with the updated exercise states collected by the automatic exercise
+     * detection for the app to consume as soon as it re-starts.
+     */
+    val launchIntent: PendingIntent? = null,
+
+    /** See [ExerciseConfig.exerciseParams]. */
+    val exerciseParams: Bundle = Bundle(),
+) : Parcelable {
+
+    override fun describeContents(): Int = 0
+
+    override fun writeToParcel(dest: Parcel, flags: Int) {
+        dest.writeInt(exercisesToDetect.size)
+        dest.writeIntArray(exercisesToDetect.map { it.id }.toIntArray())
+        dest.writeParcelable(launchIntent, flags)
+        dest.writeBundle(exerciseParams)
+    }
+
+    // TODO(b/180612514): Bundle doesn't have equals, so we need to override the data class default.
+    override fun equals(other: Any?): Boolean {
+        if (other === this) {
+            return true
+        }
+        if (other is AutoExerciseConfig) {
+            return (
+                launchIntent == other.launchIntent &&
+                    BundlesUtil.equals(exerciseParams, other.exerciseParams) &&
+                    exercisesToDetect == other.exercisesToDetect
+                )
+        }
+        return false
+    }
+
+    // TODO(b/180612514): Bundle doesn't have hashCode, so we need to override the data class
+    // default.
+    override fun hashCode(): Int {
+        return Objects.hash(launchIntent, BundlesUtil.hashCode(exerciseParams), exercisesToDetect)
+    }
+
+    public companion object {
+        @JvmField
+        public val CREATOR: Parcelable.Creator<AutoExerciseConfig> =
+            object : Parcelable.Creator<AutoExerciseConfig> {
+                override fun createFromParcel(source: Parcel): AutoExerciseConfig? {
+                    val exercisesIntArray = IntArray(source.readInt())
+                    source.readIntArray(exercisesIntArray)
+                    val launchIntent =
+                        source.readParcelable<PendingIntent>(PendingIntent::class.java.classLoader)
+                    val exerciseParams =
+                        source.readBundle(AutoExerciseConfig::class.java.classLoader) ?: return null
+
+                    return AutoExerciseConfig(
+                        exercisesIntArray.map { ExerciseType.fromId(it) }.toSet(),
+                        launchIntent,
+                        exerciseParams
+                    )
+                }
+
+                override fun newArray(size: Int): Array<AutoExerciseConfig?> {
+                    return arrayOfNulls(size)
+                }
+            }
+    }
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/data/Availability.kt b/health/health-services-client/src/main/java/androidx/health/services/client/data/Availability.kt
new file mode 100644
index 0000000..71a56d8
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/data/Availability.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 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.health.services.client.data
+
+/** Availability of a [DataType]. */
+public enum class Availability(public val id: Int) {
+    UNKNOWN(0),
+    AVAILABLE(1),
+    ACQUIRING(2),
+    UNAVAILABLE(3);
+
+    public companion object {
+        @JvmStatic public fun fromId(id: Int): Availability? = values().firstOrNull { it.id == id }
+    }
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/data/BundlesUtil.kt b/health/health-services-client/src/main/java/androidx/health/services/client/data/BundlesUtil.kt
new file mode 100644
index 0000000..f20e608
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/data/BundlesUtil.kt
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 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.health.services.client.data
+
+import android.os.Bundle
+import java.util.Objects
+
+/** Utility methods for working with Bundles. */
+internal object BundlesUtil {
+
+    /**
+     * Compares two Bundles recursively and returns `true` if they are equal.
+     *
+     * Equality in this case means that both bundles contain the same set of keys and their
+     * corresponding values are all equal (using the [Object.equals] method).
+     */
+    @JvmStatic
+    fun equals(a: Bundle?, b: Bundle?): Boolean {
+        if (a == b) {
+            return true
+        } else if (a == null || b == null) {
+            return false
+        } else if (a.size() != b.size()) {
+            return false
+        }
+        for (key in a.keySet()) {
+            val aValue = a[key]
+            val bValue = b[key]
+            if (aValue is Bundle && bValue is Bundle) {
+                if (!equals(aValue as Bundle?, bValue as Bundle?)) {
+                    return false
+                }
+            } else if (aValue == null) {
+                if (bValue != null || !b.containsKey(key)) {
+                    return false
+                }
+            } else if (!Objects.deepEquals(aValue, bValue)) {
+                return false
+            }
+        }
+        return true
+    }
+
+    /** Calculates a hashCode for a Bundle, examining all keys and values. */
+    @JvmStatic
+    fun hashCode(b: Bundle?): Int {
+        if (b == null) {
+            return 0
+        }
+        val keySet = b.keySet()
+        val hashCodes = IntArray(keySet.size * 2)
+        var i = 0
+        for (key in keySet) {
+            hashCodes[i++] = Objects.hashCode(key)
+            val value = b[key]
+            val valueHashCode: Int =
+                if (value is Bundle) {
+                    hashCode(value as Bundle?)
+                } else {
+                    Objects.hashCode(value)
+                }
+            hashCodes[i++] = valueHashCode
+        }
+        return hashCodes.contentHashCode()
+    }
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/data/Capabilities.kt b/health/health-services-client/src/main/java/androidx/health/services/client/data/Capabilities.kt
new file mode 100644
index 0000000..3c19682
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/data/Capabilities.kt
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 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.health.services.client.data
+
+import android.os.Parcel
+import android.os.Parcelable
+import androidx.health.services.client.data.ExerciseType.Companion.fromId
+
+/** A place holder class that represents the capabilities of WHS client on the device. */
+public data class Capabilities(
+    /** Mapping for each supported [ExerciseType] to its [ExerciseCapabilities] on this device. */
+    val exerciseTypeToExerciseCapabilities: Map<ExerciseType, ExerciseCapabilities>,
+) : Parcelable {
+
+    override fun describeContents(): Int {
+        return 0
+    }
+
+    override fun writeToParcel(dest: Parcel, flags: Int) {
+        writeExerciseTypeToExerciseCapabilities(dest, flags)
+    }
+
+    /** Set of supported [ExerciseType] s on this device. */
+    public val supportedExerciseTypes: Set<ExerciseType>
+        get() = exerciseTypeToExerciseCapabilities.keys
+
+    /**
+     * Returns the supported [ExerciseCapabilities] for a requested [ExerciseType].
+     *
+     * @throws IllegalArgumentException if the supplied [exercise] isn't supported
+     */
+    public fun getExerciseCapabilities(exercise: ExerciseType): ExerciseCapabilities {
+        return exerciseTypeToExerciseCapabilities[exercise]
+            ?: throw IllegalArgumentException(
+                String.format("%s exercise type is not supported", exercise)
+            )
+    }
+
+    /** Returns the set of [ExerciseType] s that support auto pause and resume on this device. */
+    public val autoPauseAndResumeEnabledExercises: Set<ExerciseType>
+        get() {
+            return exerciseTypeToExerciseCapabilities
+                .entries
+                .filter { it.value.supportsAutoPauseAndResume }
+                .map { it.key }
+                .toSet()
+        }
+
+    private fun writeExerciseTypeToExerciseCapabilities(dest: Parcel, flags: Int) {
+        dest.writeInt(exerciseTypeToExerciseCapabilities.size)
+        for ((key1, value) in exerciseTypeToExerciseCapabilities) {
+            val key = key1.id
+            dest.writeInt(key)
+            dest.writeParcelable(value, flags)
+        }
+    }
+
+    public companion object {
+
+        @JvmField
+        public val CREATOR: Parcelable.Creator<Capabilities> =
+            object : Parcelable.Creator<Capabilities> {
+                override fun createFromParcel(parcel: Parcel): Capabilities {
+                    val exerciseTypeToExerciseCapabilitiesFromParcel =
+                        getExerciseToExerciseCapabilityMap(parcel)
+                    return Capabilities(
+                        exerciseTypeToExerciseCapabilitiesFromParcel,
+                    )
+                }
+
+                override fun newArray(size: Int): Array<Capabilities?> = arrayOfNulls(size)
+            }
+
+        private fun readDataTypeSet(parcel: Parcel): Set<DataType> {
+            return parcel.createTypedArray(DataType.CREATOR)!!.toSet()
+        }
+
+        private fun writeDataTypeSet(out: Parcel, flags: Int, dataTypes: Set<DataType>) {
+            out.writeTypedArray(dataTypes.toTypedArray(), flags)
+        }
+
+        private fun getExerciseToExerciseCapabilityMap(
+            parcel: Parcel
+        ): Map<ExerciseType, ExerciseCapabilities> {
+            val map = HashMap<ExerciseType, ExerciseCapabilities>()
+            val mapSize = parcel.readInt()
+            for (i in 0 until mapSize) {
+                val key = fromId(parcel.readInt())
+                val value =
+                    parcel.readParcelable<Parcelable>(
+                        ExerciseCapabilities::class.java.classLoader
+                    ) as
+                        ExerciseCapabilities
+                map[key] = value
+            }
+            return map
+        }
+    }
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/data/ComparisonType.kt b/health/health-services-client/src/main/java/androidx/health/services/client/data/ComparisonType.kt
new file mode 100644
index 0000000..37e2288
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/data/ComparisonType.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 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.health.services.client.data
+
+/** For determining when a threshold has been met or exceeded in a [MetricCondition]. */
+public enum class ComparisonType(public val id: Int) {
+    // TODO(b/175064823): investigate adding EQUAL comparison type
+    GREATER_THAN(1),
+    GREATER_THAN_OR_EQUAL(2),
+    LESS_THAN(3),
+    LESS_THAN_OR_EQUAL(4);
+
+    public companion object {
+        @JvmStatic
+        public fun fromId(id: Int): ComparisonType? = values().firstOrNull { it.id == id }
+    }
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/data/DataPoint.kt b/health/health-services-client/src/main/java/androidx/health/services/client/data/DataPoint.kt
new file mode 100644
index 0000000..5e52737
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/data/DataPoint.kt
@@ -0,0 +1,223 @@
+/*
+ * Copyright (C) 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.health.services.client.data
+
+import android.os.Bundle
+import android.os.Parcel
+import android.os.Parcelable
+import java.time.Duration
+import java.time.Instant
+import java.util.Objects
+
+/**
+ * A data point containing a [value] of type [dataType] from either a single point in time:
+ * [DataType.TimeType.SAMPLE], or a range in time: [DataType.TimeType.INTERVAL].
+ */
+@Suppress("DataClassPrivateConstructor")
+public data class DataPoint
+internal constructor(
+    val dataType: DataType,
+    val value: Value,
+
+    /**
+     * Elapsed start time of this [DataPoint].
+     *
+     * This represents the time at which this [DataPoint] originated, as a [Duration] since boot
+     * time. This is not exposed as a timestamp as the clock may drift between when the data is
+     * generated and when it is read out. Use [getStartInstant] to get the start time of this
+     * [DataPoint] as an [Instant].
+     */
+    val startDurationFromBoot: Duration,
+
+    /**
+     * Elapsed end time of this [DataPoint].
+     *
+     * This represents the time at which this [DataPoint] ends, as a [Duration] since boot time.
+     * This is not exposed as a timestamp as the clock may drift between when the data is generated
+     * and when it is read out. Use [getStartInstant] to get the start time of this [DataPoint] as
+     * an [Instant].
+     *
+     * For instantaneous data points, this is equal to [startDurationFromBoot].
+     */
+    val endDurationFromBoot: Duration = startDurationFromBoot,
+
+    /** Returns any provided metadata of this [DataPoint]. */
+    val metadata: Bundle = Bundle(),
+) : Parcelable {
+
+    init {
+        require(dataType.format == value.format) {
+            "DataType and Value format must match, but got ${dataType.format} and ${value.format}"
+        }
+    }
+
+    /**
+     * Returns the start [Instant] of this [DataPoint], knowing the time at which the system booted.
+     *
+     * @param bootInstant the [Instant] at which the system booted, this can be computed by
+     * `Instant.ofEpochMilli(System.currentTimeMillis() - SystemClock.elapsedRealtime()) `
+     */
+    public fun getStartInstant(bootInstant: Instant): Instant {
+        return bootInstant.plus(startDurationFromBoot)
+    }
+
+    /**
+     * Returns the end [Instant] of this [DataPoint], knowing the time at which the system booted.
+     *
+     * @param bootInstant the [Instant] at which the system booted, this can be computed by
+     * `Instant.ofEpochMilli(System.currentTimeMillis() - SystemClock.elapsedRealtime())`
+     */
+    public fun getEndInstant(bootInstant: Instant): Instant {
+        return bootInstant.plus(endDurationFromBoot)
+    }
+
+    // TODO(b/180612514): Bundle doesn't have equals, so we need to override the data class default.
+    override fun equals(other: Any?): Boolean {
+        if (other === this) {
+            return true
+        }
+        if (other is DataPoint) {
+            return dataType == other.dataType &&
+                value == other.value &&
+                startDurationFromBoot == other.startDurationFromBoot &&
+                endDurationFromBoot == other.endDurationFromBoot &&
+                BundlesUtil.equals(metadata, other.metadata)
+        }
+        return false
+    }
+
+    // TODO(b/180612514): Bundle doesn't have hashCode, so we need to override the data class
+    // default.
+    override fun hashCode(): Int {
+        return Objects.hash(
+            dataType,
+            value,
+            startDurationFromBoot,
+            endDurationFromBoot,
+            BundlesUtil.hashCode(metadata)
+        )
+    }
+
+    override fun describeContents(): Int = 0
+
+    override fun writeToParcel(dest: Parcel, flags: Int) {
+        dest.writeParcelable(dataType, flags)
+        dest.writeParcelable(value, flags)
+        dest.writeLong(startDurationFromBoot.toNanos())
+        dest.writeLong(endDurationFromBoot.toNanos())
+        dest.writeBundle(metadata)
+    }
+
+    public companion object {
+        @JvmField
+        public val CREATOR: Parcelable.Creator<DataPoint> =
+            object : Parcelable.Creator<DataPoint> {
+                override fun createFromParcel(parcel: Parcel): DataPoint? {
+                    val dataType: DataType =
+                        parcel.readParcelable(DataType::class.java.classLoader) ?: return null
+                    val value: Value =
+                        parcel.readParcelable(Value::class.java.classLoader) ?: return null
+                    val startDurationFromBoot = Duration.ofNanos(parcel.readLong())
+                    val endDurationFromBoot = Duration.ofNanos(parcel.readLong())
+                    val metadata: Bundle? = parcel.readBundle(Bundle::class.java.classLoader)
+
+                    return when (dataType.timeType) {
+                        DataType.TimeType.INTERVAL ->
+                            createInterval(
+                                dataType,
+                                value,
+                                startDurationFromBoot,
+                                endDurationFromBoot,
+                                metadata ?: Bundle()
+                            )
+                        DataType.TimeType.SAMPLE -> {
+                            require(endDurationFromBoot.compareTo(startDurationFromBoot) == 0) {
+                                "DataType [$dataType] has SAMPLE type, but" +
+                                    " start[$startDurationFromBoot]/end[$endDurationFromBoot]" +
+                                    " duration from boot are not the same"
+                            }
+                            createSample(
+                                dataType,
+                                value,
+                                startDurationFromBoot,
+                                metadata ?: Bundle()
+                            )
+                        }
+                    }
+                }
+
+                override fun newArray(size: Int): Array<DataPoint?> {
+                    return arrayOfNulls(size)
+                }
+            }
+
+        /**
+         * Returns a [DataPoint] representing the [value] of type [dataType] from
+         * [startDurationFromBoot] to [endDurationFromBoot].
+         *
+         * @throws IllegalArgumentException if the [DataType.TimeType] of the associated [DataType]
+         * is not [DataType.TimeType.INTERVAL], or if data is malformed
+         */
+        @JvmStatic
+        @JvmOverloads
+        public fun createInterval(
+            dataType: DataType,
+            value: Value,
+            startDurationFromBoot: Duration,
+            endDurationFromBoot: Duration,
+            metadata: Bundle = Bundle()
+        ): DataPoint {
+            require(DataType.TimeType.INTERVAL == dataType.timeType) {
+                "DataType $dataType must be of interval type to be created with an interval"
+            }
+
+            require(endDurationFromBoot >= startDurationFromBoot) {
+                "End timestamp mustn't be earlier than start timestamp, but got" +
+                    " $startDurationFromBoot and $endDurationFromBoot"
+            }
+
+            return DataPoint(dataType, value, startDurationFromBoot, endDurationFromBoot, metadata)
+        }
+
+        /**
+         * Returns a [DataPoint] representing the [value] of type [dataType] at [durationFromBoot].
+         *
+         * @throws IllegalArgumentException if the [DataType.TimeType] of the associated [DataType]
+         * is not [DataType.TimeType.SAMPLE], or if data is malformed
+         */
+        @JvmStatic
+        @JvmOverloads
+        public fun createSample(
+            dataType: DataType,
+            value: Value,
+            durationFromBoot: Duration,
+            metadata: Bundle = Bundle()
+        ): DataPoint {
+            require(DataType.TimeType.SAMPLE == dataType.timeType) {
+                "DataType $dataType must be of sample type to be created with a single timestamp"
+            }
+
+            return DataPoint(
+                dataType,
+                value,
+                durationFromBoot,
+                endDurationFromBoot = durationFromBoot,
+                metadata
+            )
+        }
+    }
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/data/DataPoints.kt b/health/health-services-client/src/main/java/androidx/health/services/client/data/DataPoints.kt
new file mode 100644
index 0000000..1168035
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/data/DataPoints.kt
@@ -0,0 +1,320 @@
+/*
+ * Copyright (C) 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.health.services.client.data
+
+import android.content.Intent
+import androidx.annotation.Keep
+import java.time.Duration
+import java.util.ArrayList
+
+/** Helper class to facilitate working with [DataPoint] s. */
+// TODO(b/177504986): Remove all @Keep annotations once we figure out why this class gets stripped
+// away by proguard.
+@Keep
+public object DataPoints {
+    /**
+     * When using [DataType.LOCATION], the value is represented as `double[]`. The `double` value at
+     * this index represents the latitude.
+     */
+    public const val LOCATION_DATA_POINT_LATITUDE_INDEX: Int = 0
+
+    /**
+     * When using [DataType.LOCATION], the value is represented as `double[]`. The `double` value at
+     * this index represents the longitude.
+     */
+    public const val LOCATION_DATA_POINT_LONGITUDE_INDEX: Int = 1
+
+    /**
+     * When using [DataType.LOCATION], the value is represented as `double[]`. The `double` value at
+     * this index represents the altitude. This is an optional index and there is no guarantee that
+     * this index will be present.
+     */
+    public const val LOCATION_DATA_POINT_ALTITUDE_INDEX: Int = 2
+
+    /** Name of intent extra containing the data points set on pending intent. */
+    private const val EXTRA_DATA_POINTS: String = "whs.data_points_list"
+
+    /** Name of intent extra containing whether permissions are granted or not. */
+    private const val EXTRA_PERMISSIONS_GRANTED: String = "whs.data_points_has_permissions"
+
+    /** Retrieves the [DataPoint] s that are contained in the given [Intent], if any. */
+    @JvmStatic
+    @Keep
+    public fun getDataPoints(intent: Intent): List<DataPoint> =
+        intent.getParcelableArrayListExtra(EXTRA_DATA_POINTS) ?: listOf()
+
+    /** Puts the given [DataPoint] s in the given [Intent]. */
+    @JvmStatic
+    public fun putDataPoints(intent: Intent, dataPoints: Collection<DataPoint>) {
+        val copy = ArrayList(dataPoints)
+        intent.putParcelableArrayListExtra(EXTRA_DATA_POINTS, copy)
+    }
+
+    /** Sets whether [DataPoint] permissions are `granted` in the given [Intent]. */
+    @JvmStatic
+    public fun putPermissionsGranted(intent: Intent, granted: Boolean) {
+        intent.putExtra(EXTRA_PERMISSIONS_GRANTED, granted)
+    }
+
+    /** Retrieves whether permissions are granted in this [Intent]. */
+    @JvmStatic
+    public fun getPermissionsGranted(intent: Intent): Boolean =
+        intent.getBooleanExtra(EXTRA_PERMISSIONS_GRANTED, true)
+
+    /** Creates a new [DataPoint] of type [DataType.STEPS] with the given `steps`. */
+    @JvmStatic
+    public fun steps(
+        steps: Long,
+        startDurationFromBoot: Duration,
+        endDurationFromBoot: Duration
+    ): DataPoint =
+        DataPoint.createInterval(
+            DataType.STEPS,
+            Value.ofLong(steps),
+            startDurationFromBoot,
+            endDurationFromBoot
+        )
+
+    /**
+     * Creates a new [DataPoint] of type [DataType.STEPS_PER_MINUTE] with the given
+     * `stepsPerMinute`.
+     */
+    @JvmStatic
+    public fun stepsPerMinute(stepsPerMinute: Long, startDurationFromBoot: Duration): DataPoint =
+        DataPoint.createSample(
+            DataType.STEPS_PER_MINUTE,
+            Value.ofLong(stepsPerMinute),
+            startDurationFromBoot
+        )
+
+    /** Creates a new [DataPoint] of type [DataType.DISTANCE] with the given `meters`. */
+    @JvmStatic
+    public fun distance(
+        meters: Double,
+        startDurationFromBoot: Duration,
+        endDurationFromBoot: Duration
+    ): DataPoint =
+        DataPoint.createInterval(
+            DataType.DISTANCE,
+            Value.ofDouble(meters),
+            startDurationFromBoot,
+            endDurationFromBoot
+        )
+
+    /** Creates a new [DataPoint] of type [DataType.ELEVATION] with the given `meters`. */
+    @JvmStatic
+    public fun elevation(
+        meters: Double,
+        startDurationFromBoot: Duration,
+        endDurationFromBoot: Duration
+    ): DataPoint =
+        DataPoint.createInterval(
+            DataType.ELEVATION,
+            Value.ofDouble(meters),
+            startDurationFromBoot,
+            endDurationFromBoot
+        )
+
+    /** Creates a new [DataPoint] of type [DataType.ALTITUDE] with the given `meters`. */
+    @JvmStatic
+    public fun altitude(meters: Double, durationFromBoot: Duration): DataPoint =
+        DataPoint.createSample(DataType.ALTITUDE, Value.ofDouble(meters), durationFromBoot)
+
+    /** Creates a new [DataPoint] of type [DataType.FLOORS] with the given `floors`. */
+    @JvmStatic
+    public fun floors(
+        floors: Double,
+        startDurationFromBoot: Duration,
+        endDurationFromBoot: Duration
+    ): DataPoint =
+        DataPoint.createInterval(
+            DataType.FLOORS,
+            Value.ofDouble(floors),
+            startDurationFromBoot,
+            endDurationFromBoot
+        )
+
+    /** Creates a new [DataPoint] of type [DataType.TOTAL_CALORIES] with the given `kcalories`. */
+    @JvmStatic
+    public fun calories(
+        kcalories: Double,
+        startDurationFromBoot: Duration,
+        endDurationFromBoot: Duration
+    ): DataPoint =
+        DataPoint.createInterval(
+            DataType.TOTAL_CALORIES,
+            Value.ofDouble(kcalories),
+            startDurationFromBoot,
+            endDurationFromBoot
+        )
+
+    /** Creates a new [DataPoint] of type [DataType.SWIMMING_STROKES] with the given `kcalories`. */
+    @JvmStatic
+    public fun swimmingStrokes(
+        strokes: Long,
+        startDurationFromBoot: Duration,
+        endDurationFromBoot: Duration
+    ): DataPoint =
+        DataPoint.createInterval(
+            DataType.SWIMMING_STROKES,
+            Value.ofLong(strokes),
+            startDurationFromBoot,
+            endDurationFromBoot
+        )
+
+    /**
+     * Creates a new [DataPoint] of type [DataType.LOCATION] with the given `latitude` and
+     * `longitude`.
+     */
+    @JvmStatic
+    public fun location(
+        latitude: Double,
+        longitude: Double,
+        durationFromBoot: Duration
+    ): DataPoint =
+        DataPoint.createSample(
+            DataType.LOCATION,
+            Value.ofDoubleArray(latitude, longitude),
+            durationFromBoot
+        )
+
+    /**
+     * Creates a new [DataPoint] of type [DataType.LOCATION] with the given `latitude`, `longitude`
+     * and `altitude`.
+     */
+    @JvmStatic
+    public fun location(
+        latitude: Double,
+        longitude: Double,
+        altitude: Double,
+        durationFromBoot: Duration
+    ): DataPoint =
+        DataPoint.createSample(
+            DataType.LOCATION,
+            Value.ofDoubleArray(latitude, longitude, altitude),
+            durationFromBoot
+        )
+
+    /** Creates a new [DataPoint] of type [DataType.SPEED] with the given `metersPerSecond`. */
+    @JvmStatic
+    public fun speed(metersPerSecond: Double, durationFromBoot: Duration): DataPoint =
+        DataPoint.createSample(DataType.SPEED, Value.ofDouble(metersPerSecond), durationFromBoot)
+
+    /** Creates a new [DataPoint] of type [DataType.PACE] with the given `millisPerKm`. */
+    @JvmStatic
+    public fun pace(millisPerKm: Double, durationFromBoot: Duration): DataPoint =
+        DataPoint.createSample(DataType.PACE, Value.ofDouble(millisPerKm), durationFromBoot)
+
+    /** Creates a new [DataPoint] of type [DataType.HEART_RATE_BPM] with the given `bpm`. */
+    @JvmStatic
+    public fun heartRate(bpm: Double, durationFromBoot: Duration): DataPoint =
+        DataPoint.createSample(DataType.HEART_RATE_BPM, Value.ofDouble(bpm), durationFromBoot)
+
+    /** Creates a new [DataPoint] of type [DataType.SPO2] with the given `percent`. */
+    @JvmStatic
+    public fun spo2(percent: Double, durationFromBoot: Duration): DataPoint =
+        DataPoint.createSample(DataType.SPO2, Value.ofDouble(percent), durationFromBoot)
+
+    /**
+     * Creates a new [DataPoint] of type [DataType.AGGREGATE_DISTANCE] with the given `distance`.
+     */
+    @JvmStatic
+    public fun aggregateDistance(
+        distance: Double,
+        startDurationFromBoot: Duration,
+        endDurationFromBoot: Duration
+    ): DataPoint =
+        DataPoint.createInterval(
+            DataType.AGGREGATE_DISTANCE,
+            Value.ofDouble(distance),
+            startDurationFromBoot,
+            endDurationFromBoot
+        )
+
+    /** Creates a new [DataPoint] of type [DataType.AGGREGATE_STEP_COUNT] with the given `steps`. */
+    @JvmStatic
+    public fun aggregateSteps(
+        steps: Long,
+        startDurationFromBoot: Duration,
+        endDurationFromBoot: Duration
+    ): DataPoint =
+        DataPoint.createInterval(
+            DataType.AGGREGATE_STEP_COUNT,
+            Value.ofLong(steps),
+            startDurationFromBoot,
+            endDurationFromBoot
+        )
+
+    /**
+     * Creates a new [DataPoint] of type [DataType.AGGREGATE_CALORIES_EXPENDED] with the given
+     * `kcalories`.
+     */
+    @JvmStatic
+    public fun aggregateCalories(
+        kcalories: Double,
+        startDurationFromBoot: Duration,
+        endDurationFromBoot: Duration
+    ): DataPoint =
+        DataPoint.createInterval(
+            DataType.AGGREGATE_CALORIES_EXPENDED,
+            Value.ofDouble(kcalories),
+            startDurationFromBoot,
+            endDurationFromBoot
+        )
+
+    /**
+     * Creates a new [DataPoint] of type [DataType.AGGREGATE_SWIMMING_STROKE_COUNT] with the given
+     * `swimmingStrokes`.
+     */
+    @JvmStatic
+    public fun aggregateSwimmingStrokes(
+        swimmingStrokes: Long,
+        startDurationFromBoot: Duration,
+        endDurationFromBoot: Duration
+    ): DataPoint =
+        DataPoint.createInterval(
+            DataType.AGGREGATE_SWIMMING_STROKE_COUNT,
+            Value.ofLong(swimmingStrokes),
+            startDurationFromBoot,
+            endDurationFromBoot
+        )
+
+    /** Creates a new [DataPoint] of type [DataType.AVERAGE_PACE] with the given `millisPerKm`. */
+    @JvmStatic
+    public fun averagePace(millisPerKm: Double, durationFromBoot: Duration): DataPoint =
+        DataPoint.createSample(DataType.AVERAGE_PACE, Value.ofDouble(millisPerKm), durationFromBoot)
+
+    /**
+     * Creates a new [DataPoint] of type [DataType.AVERAGE_SPEED] with the given `metersPerSecond`.
+     */
+    @JvmStatic
+    public fun averageSpeed(metersPerSecond: Double, durationFromBoot: Duration): DataPoint =
+        DataPoint.createSample(
+            DataType.AVERAGE_SPEED,
+            Value.ofDouble(metersPerSecond),
+            durationFromBoot
+        )
+
+    /** Creates a new [DataPoint] of type [DataType.MAX_SPEED] with the given `metersPerSecond`. */
+    @JvmStatic
+    public fun maxSpeed(metersPerSecond: Double, durationFromBoot: Duration): DataPoint =
+        DataPoint.createSample(
+            DataType.MAX_SPEED,
+            Value.ofDouble(metersPerSecond),
+            durationFromBoot
+        )
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/data/DataType.kt b/health/health-services-client/src/main/java/androidx/health/services/client/data/DataType.kt
new file mode 100644
index 0000000..d3a0ac6
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/data/DataType.kt
@@ -0,0 +1,363 @@
+/*
+ * Copyright (C) 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.health.services.client.data
+
+import android.os.Parcel
+import android.os.Parcelable
+
+/**
+ * A data type is a representation of health data managed by Health Services.
+ *
+ * A [DataType] specifies the format of the values inside a [DataPoint]. WHS defines data types for
+ * instantaneous observations [TimeType.SAMPLE](e.g. heart rate) and data types for change between
+ * readings [TimeType.INTERVAL](e.g. distance).
+ *
+ * Note: the data type defines only the representation and format of the data, and not how it's
+ * being collected, the sensor being used, or the parameters of the collection.
+ */
+public data class DataType(
+    /** Returns the name of this [DataType], e.g. `"Steps"`. */
+    val name: String,
+    /** Returns the [TimeType] of this [DataType]. */
+    val timeType: TimeType,
+    /** Returns the expected format for a [Value] of this [DataType]. */
+    val format: Int,
+) : Parcelable {
+
+    /**
+     * Whether the `DataType` corresponds to a measurement spanning an interval, or a sample at a
+     * single point in time.
+     */
+    public enum class TimeType {
+        INTERVAL,
+        SAMPLE
+    }
+
+    override fun describeContents(): Int = 0
+
+    override fun writeToParcel(dest: Parcel, flags: Int) {
+        with(dest) {
+            writeString(name)
+            writeString(timeType.name)
+            writeInt(format)
+        }
+    }
+
+    public companion object {
+        @JvmField
+        public val CREATOR: Parcelable.Creator<DataType> =
+            object : Parcelable.Creator<DataType> {
+                override fun createFromParcel(parcel: Parcel): DataType? {
+                    return DataType(
+                        parcel.readString() ?: return null,
+                        TimeType.valueOf(parcel.readString() ?: return null),
+                        parcel.readInt()
+                    )
+                }
+
+                override fun newArray(size: Int): Array<DataType?> {
+                    return arrayOfNulls(size)
+                }
+            }
+
+        /** Current altitude expressed in meters in `double` format. */
+        @JvmField
+        public val ALTITUDE: DataType = DataType("Altitude", TimeType.SAMPLE, Value.FORMAT_DOUBLE)
+
+        /** A distance delta between each reading expressed in meters in `double` format. */
+        @JvmField
+        public val DISTANCE: DataType = DataType("Distance", TimeType.INTERVAL, Value.FORMAT_DOUBLE)
+
+        /**
+         * A duration delta during an exercise over which the user was traveling down a decline,
+         * expressed in seconds in `long` format.
+         */
+        @JvmField
+        public val DECLINE_TIME: DataType =
+            DataType("Decline Time", TimeType.INTERVAL, Value.FORMAT_LONG)
+
+        /**
+         * A distance delta traveled over declining ground between each reading expressed in meters
+         * in `double` format.
+         */
+        @JvmField
+        public val DECLINE_DISTANCE: DataType =
+            DataType("Decline Distance", TimeType.INTERVAL, Value.FORMAT_DOUBLE)
+
+        /**
+         * A duration delta during an exercise over which the user was traveling across flat ground,
+         * expressed in seconds in `long` format.
+         */
+        @JvmField
+        public val FLAT_TIME: DataType = DataType("Flat Time", TimeType.INTERVAL, Value.FORMAT_LONG)
+
+        /**
+         * A distance delta traveled over flat ground between each reading expressed in meters in
+         * `double` format.
+         */
+        @JvmField
+        public val FLAT_DISTANCE: DataType =
+            DataType("Flat Distance", TimeType.INTERVAL, Value.FORMAT_DOUBLE)
+
+        /**
+         * A duration delta during an exercise over which the user was traveling up an incline,
+         * expressed in seconds in `long` format.
+         */
+        @JvmField
+        public val INCLINE_TIME: DataType =
+            DataType("Incline Time", TimeType.INTERVAL, Value.FORMAT_LONG)
+
+        /**
+         * A distance delta traveled over inclining ground between each reading expressed in meters
+         * in `double` format.
+         */
+        @JvmField
+        public val INCLINE_DISTANCE: DataType =
+            DataType("Incline Distance", TimeType.INTERVAL, Value.FORMAT_DOUBLE)
+
+        /** An elevation delta between each reading expressed in meters in `double` format. */
+        @JvmField
+        public val ELEVATION: DataType =
+            DataType("Elevation", TimeType.INTERVAL, Value.FORMAT_DOUBLE)
+
+        /** Absolute elevation between each reading expressed in meters in `double` format. */
+        @JvmField
+        public val ABSOLUTE_ELEVATION: DataType =
+            DataType("Absolute Elevation", TimeType.SAMPLE, Value.FORMAT_DOUBLE)
+
+        /** Number of floors climbed between each reading in `double` format */
+        @JvmField
+        public val FLOORS: DataType = DataType("Floors", TimeType.INTERVAL, Value.FORMAT_DOUBLE)
+
+        /** Current heart rate, in beats per minute in `double` format. */
+        @JvmField
+        public val HEART_RATE_BPM: DataType =
+            DataType("HeartRate", TimeType.SAMPLE, Value.FORMAT_DOUBLE)
+
+        /**
+         * Current latitude, longitude and optionally, altitude in `double[]` format. Latitude at
+         * index [DataPoints.LOCATION_DATA_POINT_LATITUDE_INDEX], longitude at index
+         * [DataPoints.LOCATION_DATA_POINT_LONGITUDE_INDEX] and if available, altitude at index
+         * [DataPoints.LOCATION_DATA_POINT_ALTITUDE_INDEX]
+         */
+        @JvmField
+        public val LOCATION: DataType =
+            DataType("Location", TimeType.SAMPLE, Value.FORMAT_DOUBLE_ARRAY)
+
+        /** Current speed over time. In meters/second in `double` format. */
+        @JvmField
+        public val SPEED: DataType = DataType("Speed", TimeType.SAMPLE, Value.FORMAT_DOUBLE)
+
+        /** Percentage of oxygen in the blood in `double` format. Valid range `0f` - `100f`. */
+        @JvmField public val SPO2: DataType = DataType("SpO2", TimeType.SAMPLE, Value.FORMAT_DOUBLE)
+
+        /** Rate of oxygen consumption in `double` format. Valid range `0f` - `100f`. */
+        @JvmField public val VO2: DataType = DataType("VO2", TimeType.SAMPLE, Value.FORMAT_DOUBLE)
+
+        /**
+         * Maximum rate of oxygen consumption measured during incremental exercise in `double`
+         * format. Valid range `0f` - `100f`.
+         */
+        @JvmField
+        public val VO2_MAX: DataType = DataType("VO2 Max", TimeType.SAMPLE, Value.FORMAT_DOUBLE)
+
+        /** Delta of steps between each reading in `long` format. */
+        @JvmField
+        public val STEPS: DataType = DataType("Steps", TimeType.INTERVAL, Value.FORMAT_LONG)
+
+        /** Delta of walking steps between each reading in `long` format. */
+        @JvmField
+        public val WALKING_STEPS: DataType =
+            DataType("Walking Steps", TimeType.INTERVAL, Value.FORMAT_LONG)
+
+        /** Delta of running steps between each reading in `long` format. */
+        @JvmField
+        public val RUNNING_STEPS: DataType =
+            DataType("Running Steps", TimeType.INTERVAL, Value.FORMAT_LONG)
+
+        /** Current step rate in steps/minute in `long` format. */
+        @JvmField
+        public val STEPS_PER_MINUTE: DataType =
+            DataType("Step per minute", TimeType.SAMPLE, Value.FORMAT_LONG)
+
+        /** Delta of strokes between each reading of swimming strokes in `long` format. */
+        @JvmField
+        public val SWIMMING_STROKES: DataType =
+            DataType("Swimming Strokes", TimeType.INTERVAL, Value.FORMAT_LONG)
+
+        /**
+         * Delta of total calories (including basal rate and activity) between each reading in
+         * `double` format.
+         */
+        @JvmField
+        public val TOTAL_CALORIES: DataType =
+            DataType("Calories", TimeType.INTERVAL, Value.FORMAT_DOUBLE)
+
+        /** Current pace. In millisec/km in `double` format. */
+        @JvmField public val PACE: DataType = DataType("Pace", TimeType.SAMPLE, Value.FORMAT_DOUBLE)
+
+        /** The aggregate distance over a period of time expressed in meters in `double` format. */
+        @JvmField
+        public val AGGREGATE_DISTANCE: DataType =
+            DataType("Aggregate Distance", TimeType.INTERVAL, Value.FORMAT_DOUBLE)
+
+        /**
+         * The aggregate flat distance over a period of time expressed in meters in `double` format.
+         */
+        @JvmField
+        public val AGGREGATE_FLAT_DISTANCE: DataType =
+            DataType("Aggregate Flat Distance", TimeType.INTERVAL, Value.FORMAT_DOUBLE)
+
+        /**
+         * The aggregate incline distance over a period of time expressed in meters in `double`
+         * format.
+         */
+        @JvmField
+        public val AGGREGATE_INCLINE_DISTANCE: DataType =
+            DataType("Aggregate Incline Distance", TimeType.INTERVAL, Value.FORMAT_DOUBLE)
+
+        /**
+         * The aggregate incline distance over a period of time expressed in meters in `double`
+         * format.
+         */
+        @JvmField
+        public val AGGREGATE_DECLINE_DISTANCE: DataType =
+            DataType("Aggregate Decline Distance", TimeType.INTERVAL, Value.FORMAT_DOUBLE)
+
+        /**
+         * The aggregate duration for an exercise when the user was traveling on flat ground,
+         * expressed in seconds in `long` format.
+         */
+        @JvmField
+        public val AGGREGATE_FLAT_TIME: DataType =
+            DataType("Aggregate Flat Time", TimeType.INTERVAL, Value.FORMAT_LONG)
+
+        /**
+         * The aggregate duration for an exercise when the user was traveling up an incline,
+         * expressed in seconds in `long` format.
+         */
+        @JvmField
+        public val AGGREGATE_INCLINE_TIME: DataType =
+            DataType("Aggregate Incline Time", TimeType.INTERVAL, Value.FORMAT_LONG)
+
+        /**
+         * The aggregate duration for an exercise when the user was traveling down a decline,
+         * expressed in seconds in `long` format.
+         */
+        @JvmField
+        public val AGGREGATE_DECLINE_TIME: DataType =
+            DataType("Aggregate Decline Time", TimeType.INTERVAL, Value.FORMAT_LONG)
+
+        /**
+         * The aggregate total calories (including basal rate and activity) expended over a period
+         * of time in `double` format.
+         */
+        @JvmField
+        public val AGGREGATE_CALORIES_EXPENDED: DataType =
+            DataType("Aggregate Calories", TimeType.INTERVAL, Value.FORMAT_DOUBLE)
+
+        /** The aggregate step count over a period of time in `long` format. */
+        @JvmField
+        public val AGGREGATE_STEP_COUNT: DataType =
+            DataType("Aggregate Steps", TimeType.INTERVAL, Value.FORMAT_LONG)
+
+        /** The aggregate walking step count over a period of time in `long` format. */
+        @JvmField
+        public val AGGREGATE_WALKING_STEP_COUNT: DataType =
+            DataType("Aggregate Walking Steps", TimeType.INTERVAL, Value.FORMAT_LONG)
+
+        /** The aggregate running step count over a period of time in `long` format. */
+        @JvmField
+        public val AGGREGATE_RUNNING_STEP_COUNT: DataType =
+            DataType("Aggregate Running Steps", TimeType.INTERVAL, Value.FORMAT_LONG)
+
+        /** The aggregate swimming stroke count over a period of time in `long` format. */
+        @JvmField
+        public val AGGREGATE_SWIMMING_STROKE_COUNT: DataType =
+            DataType("Aggregate Swimming Strokes", TimeType.INTERVAL, Value.FORMAT_LONG)
+
+        /** The aggregate elevation over a period of time in meters in `double` format. */
+        @JvmField
+        public val AGGREGATE_ELEVATION: DataType =
+            DataType("Aggregate Elevation", TimeType.INTERVAL, Value.FORMAT_DOUBLE)
+
+        /** The number of floors climbed over a period of time in `double` format */
+        @JvmField
+        public val AGGREGATE_FLOORS: DataType =
+            DataType("Aggregate Floors", TimeType.INTERVAL, Value.FORMAT_DOUBLE)
+
+        /** The average pace over a period of time in millisec/km in `double` format. */
+        @JvmField
+        public val AVERAGE_PACE: DataType =
+            DataType("Average Pace", TimeType.SAMPLE, Value.FORMAT_DOUBLE)
+
+        /** The average speed over a period of time in meters/second in `double` format. */
+        @JvmField
+        public val AVERAGE_SPEED: DataType =
+            DataType("Average Speed", TimeType.SAMPLE, Value.FORMAT_DOUBLE)
+
+        /** The average heart rate over a period of time in beats/minute in `double` format. */
+        @JvmField
+        public val AVERAGE_HEART_RATE_BPM: DataType =
+            DataType("Average Heart Rate BPM", TimeType.SAMPLE, Value.FORMAT_DOUBLE)
+
+        /** The maximum altitude over a period of time in meters in `double` format. */
+        @JvmField
+        public val MAX_ALTITUDE: DataType =
+            DataType("Max Altitude", TimeType.SAMPLE, Value.FORMAT_DOUBLE)
+
+        /** The minimum altitude over a period of time in meters in `double` format. */
+        @JvmField
+        public val MIN_ALTITUDE: DataType =
+            DataType("Min Altitude", TimeType.SAMPLE, Value.FORMAT_DOUBLE)
+
+        /** The maximum pace over a period of time in millisec/km in `double` format. */
+        @JvmField
+        public val MAX_PACE: DataType = DataType("Max Pace", TimeType.SAMPLE, Value.FORMAT_DOUBLE)
+
+        /** The maximum speed over a period of time in meters/second in `double` format. */
+        @JvmField
+        public val MAX_SPEED: DataType = DataType("Max Speed", TimeType.SAMPLE, Value.FORMAT_DOUBLE)
+
+        /** The maximum instantaneous heart rate in beats/minute in `double` format. */
+        @JvmField
+        public val MAX_HEART_RATE_BPM: DataType =
+            DataType("Max Heart Rate", TimeType.SAMPLE, Value.FORMAT_DOUBLE)
+
+        /**
+         * The duration during which the user was resting during an Exercise in seconds in `long`
+         * format.
+         */
+        @JvmField
+        public val RESTING_EXERCISE_DURATION: DataType =
+            DataType("Resting Exercise Duration", TimeType.SAMPLE, Value.FORMAT_LONG)
+
+        /** The duration of the time the Exercise was ACTIVE in seconds in `long` format. */
+        @JvmField
+        public val ACTIVE_EXERCISE_DURATION: DataType =
+            DataType("Active Exercise Duration", TimeType.SAMPLE, Value.FORMAT_LONG)
+
+        /** Count of swimming laps ins `long` format. */
+        @JvmField
+        public val SWIMMING_LAP_COUNT: DataType =
+            DataType("Swim Lap Count", TimeType.INTERVAL, Value.FORMAT_LONG)
+
+        /** The current rep count of the exercise in `long` format. */
+        @JvmField
+        public val REP_COUNT: DataType = DataType("Rep Count", TimeType.INTERVAL, Value.FORMAT_LONG)
+    }
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/data/DataTypeCondition.kt b/health/health-services-client/src/main/java/androidx/health/services/client/data/DataTypeCondition.kt
new file mode 100644
index 0000000..152c9df
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/data/DataTypeCondition.kt
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 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.health.services.client.data
+
+import android.os.Parcel
+import android.os.Parcelable
+
+/** A condition which is considered met when a data type value passes a defined threshold. */
+public data class DataTypeCondition(
+    val dataType: DataType,
+    val threshold: Value,
+    val comparisonType: ComparisonType,
+) : Parcelable {
+    init {
+        require(dataType.format == threshold.format) {
+            "provided data type must have sample time type."
+        }
+    }
+
+    /** Checks whether or not the condition is satisfied by a given [DataPoint]. */
+    public fun isSatisfied(dataPoint: DataPoint): Boolean {
+        require(dataType == dataPoint.dataType) {
+            "attempted to evaluate data type condition with incorrect data type. Expected " +
+                "${dataType.name} but was ${dataPoint.dataType.name}"
+        }
+        return isThresholdSatisfied(dataPoint.value)
+    }
+
+    /** Checks whether or not the value of the condition is satisfied by a given [Value]. */
+    public fun isThresholdSatisfied(value: Value): Boolean {
+        val comparison = Value.compare(value, threshold)
+        return when (comparisonType) {
+            ComparisonType.LESS_THAN -> comparison < 0
+            ComparisonType.GREATER_THAN -> comparison > 0
+            ComparisonType.LESS_THAN_OR_EQUAL -> comparison <= 0
+            ComparisonType.GREATER_THAN_OR_EQUAL -> comparison >= 0
+        }
+    }
+
+    override fun describeContents(): Int = 0
+
+    override fun writeToParcel(dest: Parcel, flags: Int) {
+        dest.writeParcelable(dataType, flags)
+        dest.writeParcelable(threshold, flags)
+        dest.writeInt(comparisonType.id)
+    }
+
+    public companion object {
+        @JvmField
+        public val CREATOR: Parcelable.Creator<DataTypeCondition> =
+            object : Parcelable.Creator<DataTypeCondition> {
+                override fun createFromParcel(source: Parcel): DataTypeCondition? {
+                    val dataType =
+                        source.readParcelable<DataType>(DataType::class.java.classLoader)
+                            ?: return null
+                    val threshold =
+                        source.readParcelable<Value>(Value::class.java.classLoader) ?: return null
+                    val comparisonType = ComparisonType.fromId(source.readInt()) ?: return null
+                    return DataTypeCondition(dataType, threshold, comparisonType)
+                }
+
+                override fun newArray(size: Int): Array<DataTypeCondition?> {
+                    return arrayOfNulls(size)
+                }
+            }
+    }
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/data/DataTypes.kt b/health/health-services-client/src/main/java/androidx/health/services/client/data/DataTypes.kt
new file mode 100644
index 0000000..b821de8
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/data/DataTypes.kt
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 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.health.services.client.data
+
+/** Helper class to facilitate working with [DataTypes] [DataType]. */
+public object DataTypes {
+    private val AGGREGATE_TYPE_TO_RAW_TYPE =
+        DataTypeBiMap(
+            DataType.AGGREGATE_DISTANCE to DataType.DISTANCE,
+            DataType.AGGREGATE_CALORIES_EXPENDED to DataType.TOTAL_CALORIES,
+            DataType.AGGREGATE_STEP_COUNT to DataType.STEPS,
+            DataType.AGGREGATE_ELEVATION to DataType.ELEVATION,
+            DataType.AGGREGATE_FLOORS to DataType.FLOORS,
+            DataType.AGGREGATE_SWIMMING_STROKE_COUNT to DataType.SWIMMING_STROKES
+        )
+
+    private val MAX_TYPE_TO_RAW_TYPE =
+        DataTypeBiMap(
+            DataType.MAX_HEART_RATE_BPM to DataType.HEART_RATE_BPM,
+            DataType.MAX_PACE to DataType.PACE,
+            DataType.MAX_SPEED to DataType.SPEED
+        )
+
+    private val AVERAGE_TYPE_TO_RAW_TYPE =
+        DataTypeBiMap(
+            DataType.AVERAGE_HEART_RATE_BPM to DataType.HEART_RATE_BPM,
+            DataType.AVERAGE_PACE to DataType.PACE,
+            DataType.AVERAGE_SPEED to DataType.SPEED
+        )
+
+    /** Check if a [DataType] represents aggregate value of a collection of non-aggregate data. */
+    @JvmStatic
+    public fun isAggregateDataType(dataType: DataType): Boolean =
+        AGGREGATE_TYPE_TO_RAW_TYPE.map.containsKey(dataType)
+
+    /** Check if a [DataType] represents the maximum value of a collection of non-aggregate data. */
+    @JvmStatic
+    public fun isStatisticalMaxDataType(dataType: DataType): Boolean =
+        MAX_TYPE_TO_RAW_TYPE.map.containsKey(dataType)
+
+    /** Check if a [DataType] represents the average value of a collection of non-aggregate data. */
+    @JvmStatic
+    public fun isStatisticalAverageDataType(dataType: DataType): Boolean =
+        AVERAGE_TYPE_TO_RAW_TYPE.map.containsKey(dataType)
+
+    /**
+     * Check if a [DataType] represents raw data value, i.e., neither aggregate value nor
+     * statistical value.
+     */
+    @JvmStatic
+    public fun isRawType(dataType: DataType): Boolean =
+        !isAggregateDataType(dataType) &&
+            !isStatisticalMaxDataType(dataType) &&
+            !isStatisticalAverageDataType(dataType)
+
+    /** Get the aggregate [DataType] from a raw [DataType], or null if it doesn't exist. */
+    @JvmStatic
+    public fun getAggregateTypeFromRawType(rawType: DataType): DataType? =
+        AGGREGATE_TYPE_TO_RAW_TYPE.inverse[rawType]
+
+    /** Get the raw [DataType] from an aggregate [DataType], or null if it doesn't exist. */
+    @JvmStatic
+    public fun getRawTypeFromAggregateType(aggregateType: DataType): DataType? =
+        AGGREGATE_TYPE_TO_RAW_TYPE.map[aggregateType]
+
+    /** Get the max [DataType] from a raw [DataType], or null if it doesn't exist. */
+    @JvmStatic
+    public fun getMaxTypeFromRawType(rawType: DataType): DataType? =
+        MAX_TYPE_TO_RAW_TYPE.inverse[rawType]
+
+    /** Get the raw [DataType] from a max [DataType], or null if it doesn't exist. */
+    @JvmStatic
+    public fun getRawTypeFromMaxType(maxType: DataType): DataType? =
+        MAX_TYPE_TO_RAW_TYPE.map[maxType]
+
+    /** Get the average [DataType] from a raw [DataType], or null if it doesn't exist. */
+    @JvmStatic
+    public fun getAverageTypeFromRawType(rawType: DataType): DataType? =
+        AVERAGE_TYPE_TO_RAW_TYPE.inverse[rawType]
+
+    /** Get the raw [DataType] from an average [DataType], or null if it doesn't exist. */
+    @JvmStatic
+    public fun getRawTypeFromAverageType(averageType: DataType): DataType? =
+        AVERAGE_TYPE_TO_RAW_TYPE.map[averageType]
+
+    /** Get the aggregate, average, and max [DataType] from a raw [DataType] if they exist. */
+    @JvmStatic
+    public fun getAggregatedDataTypesFromRawType(rawType: DataType): Set<DataType> {
+        val allDataTypes = HashSet<DataType>()
+
+        getAggregateTypeFromRawType(rawType)?.let { allDataTypes.add(it) }
+        getMaxTypeFromRawType(rawType)?.let { allDataTypes.add(it) }
+        getAverageTypeFromRawType(rawType)?.let { allDataTypes.add(it) }
+
+        return allDataTypes
+    }
+
+    private class DataTypeBiMap(vararg pairs: Pair<DataType, DataType>) {
+        val map = mapOf(*pairs)
+        val inverse = pairs.map { it.second to it.first }.toMap()
+    }
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/data/ExerciseCapabilities.kt b/health/health-services-client/src/main/java/androidx/health/services/client/data/ExerciseCapabilities.kt
new file mode 100644
index 0000000..6564f06
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/data/ExerciseCapabilities.kt
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 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.health.services.client.data
+
+import android.os.Parcel
+import android.os.Parcelable
+
+/** Provides exercise specific capabilities data. */
+public data class ExerciseCapabilities(
+    val supportedDataTypes: Set<DataType>,
+    val supportedGoals: Map<DataType, Set<ComparisonType>>,
+    val supportedMilestones: Map<DataType, Set<ComparisonType>>,
+    val supportsAutoPauseAndResume: Boolean,
+    val supportsLaps: Boolean,
+) : Parcelable {
+    override fun describeContents(): Int = 0
+
+    override fun writeToParcel(dest: Parcel, flags: Int) {
+        dest.writeInt(supportedDataTypes.size)
+        dest.writeTypedArray(supportedDataTypes.toTypedArray(), flags)
+
+        writeSupportedDataTypes(supportedGoals, dest, flags)
+        writeSupportedDataTypes(supportedMilestones, dest, flags)
+
+        dest.writeInt(if (supportsAutoPauseAndResume) 1 else 0)
+        dest.writeInt(if (supportsLaps) 1 else 0)
+    }
+
+    public companion object {
+        @JvmField
+        public val CREATOR: Parcelable.Creator<ExerciseCapabilities> =
+            object : Parcelable.Creator<ExerciseCapabilities> {
+                override fun createFromParcel(source: Parcel): ExerciseCapabilities? {
+                    val supportedDataTypesArray = Array<DataType?>(source.readInt()) { null }
+                    source.readTypedArray(supportedDataTypesArray, DataType.CREATOR)
+
+                    val supportedGoals = readSupportedDataTypes(source) ?: return null
+                    val supportedMilestones = readSupportedDataTypes(source) ?: return null
+                    val supportsAutoPauseAndResume = source.readInt() == 1
+                    val supportsLaps = source.readInt() == 1
+
+                    return ExerciseCapabilities(
+                        supportedDataTypesArray.filterNotNull().toSet(),
+                        supportedGoals,
+                        supportedMilestones,
+                        supportsAutoPauseAndResume,
+                        supportsLaps
+                    )
+                }
+
+                override fun newArray(size: Int): Array<ExerciseCapabilities?> {
+                    return arrayOfNulls(size)
+                }
+            }
+
+        private fun writeSupportedDataTypes(
+            supportedDataTypes: Map<DataType, Set<ComparisonType>>,
+            dest: Parcel,
+            flags: Int
+        ) {
+            dest.writeInt(supportedDataTypes.size)
+            for ((dataType, comparisonTypeSet) in supportedDataTypes) {
+                dest.writeParcelable(dataType, flags)
+                dest.writeInt(comparisonTypeSet.size)
+                dest.writeIntArray(comparisonTypeSet.map { it.id }.toIntArray())
+            }
+        }
+
+        private fun readSupportedDataTypes(source: Parcel): Map<DataType, Set<ComparisonType>>? {
+            val supportedDataTypes = HashMap<DataType, Set<ComparisonType>>()
+
+            val numSupportedDataTypes = source.readInt()
+            repeat(numSupportedDataTypes) {
+                val dataType: DataType =
+                    source.readParcelable(DataType::class.java.classLoader) ?: return null
+
+                val comparisonTypeIntArray = IntArray(source.readInt())
+                source.readIntArray(comparisonTypeIntArray)
+                val comparisonTypeSet =
+                    comparisonTypeIntArray.map { ComparisonType.fromId(it) }.filterNotNull().toSet()
+
+                supportedDataTypes[dataType] = comparisonTypeSet
+            }
+
+            return supportedDataTypes
+        }
+    }
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/data/ExerciseConfig.kt b/health/health-services-client/src/main/java/androidx/health/services/client/data/ExerciseConfig.kt
new file mode 100644
index 0000000..9531eea
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/data/ExerciseConfig.kt
@@ -0,0 +1,188 @@
+/*
+ * Copyright (C) 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.health.services.client.data
+
+import android.os.Bundle
+import android.os.Parcel
+import android.os.Parcelable
+import java.util.Objects
+
+/** Defines configuration for an exercise tracked using WHS. */
+@Suppress("DataClassPrivateConstructor")
+public data class ExerciseConfig
+protected constructor(
+    /**
+     * [ExerciseType] the user is performing for this exercise.
+     *
+     * This information can be used to tune sensors, e.g. the calories estimate can take the MET
+     * value into account.
+     */
+    val exerciseType: ExerciseType,
+    val dataTypes: Set<DataType>,
+    val autoPauseAndResume: Boolean,
+    val exerciseGoals: List<ExerciseGoal>,
+    val exerciseParams: Bundle,
+) : Parcelable {
+    init {
+        require(dataTypes.isNotEmpty()) { "Must specify the desired data types." }
+        require(exerciseType != ExerciseType.UNKNOWN) { "Must specify a valid exercise type." }
+    }
+
+    override fun describeContents(): Int = 0
+
+    override fun writeToParcel(dest: Parcel, flags: Int) {
+        dest.writeInt(exerciseType.id)
+        dest.writeInt(dataTypes.size)
+        dest.writeTypedArray(dataTypes.toTypedArray(), flags)
+        dest.writeInt(if (autoPauseAndResume) 1 else 0)
+        dest.writeInt(exerciseGoals.size)
+        dest.writeTypedArray(exerciseGoals.toTypedArray(), flags)
+        dest.writeBundle(exerciseParams)
+    }
+
+    /** Builder for [ExerciseConfig] instances. */
+    public class Builder {
+        private var exerciseType: ExerciseType? = null
+        private var dataTypes: Set<DataType>? = null
+        private var autoPauseAndResume: Boolean = false
+        private var exerciseGoals: List<ExerciseGoal> = emptyList()
+        private var exerciseParams: Bundle = Bundle.EMPTY
+
+        /**
+         * Sets the active [ExerciseType] the user is performing for this exercise.
+         *
+         * Provide this parameter when tracking a workout to provide more accurate data. This
+         * information can be used to tune sensors, e.g. the calories estimate can take the MET
+         * value into account.
+         */
+        public fun setExerciseType(exerciseType: ExerciseType): Builder {
+            this.exerciseType = exerciseType
+            return this
+        }
+
+        /**
+         * Sets the requested [DataType] s that should be tracked during this exercise. If not
+         * explicitly called, a default set of [DataType] will be chosen based on the [ ].
+         */
+        public fun setDataTypes(dataTypes: Set<DataType>): Builder {
+            this.dataTypes = dataTypes.toSet()
+            return this
+        }
+
+        /**
+         * Sets whether auto pause and auto resume are enabled for this exercise. If not set,
+         * they're disabled by default.
+         */
+        public fun setAutoPauseAndResume(autoPauseAndResume: Boolean): Builder {
+            this.autoPauseAndResume = autoPauseAndResume
+            return this
+        }
+
+        /**
+         * Sets [ExerciseGoal] s specified for this exercise.
+         *
+         * This is useful to have goals specified before the start of an exercise.
+         */
+        public fun setExerciseGoals(exerciseGoals: List<ExerciseGoal>): Builder {
+            this.exerciseGoals = exerciseGoals.toList()
+            return this
+        }
+
+        /**
+         * Sets additional parameters for current exercise. Supported keys can be found in
+         * [ExerciseConfig].
+         */
+        // TODO(b/180612514) expose keys on a per-OEM basis.
+        public fun setExerciseParams(exerciseParams: Bundle): Builder {
+            this.exerciseParams = exerciseParams
+            return this
+        }
+
+        /** Returns the built `ExerciseConfig`. */
+        public fun build(): ExerciseConfig {
+            return ExerciseConfig(
+                checkNotNull(exerciseType) { "No exercise type specified" },
+                checkNotNull(dataTypes) { "No data types specified" },
+                autoPauseAndResume,
+                exerciseGoals,
+                exerciseParams
+            )
+        }
+    }
+
+    // TODO(b/180612514): Bundle doesn't have equals, so we need to override the data class default.
+    override fun equals(other: Any?): Boolean {
+        if (other === this) {
+            return true
+        }
+        if (other is ExerciseConfig) {
+            return exerciseType == other.exerciseType &&
+                dataTypes == other.dataTypes &&
+                autoPauseAndResume == other.autoPauseAndResume &&
+                exerciseGoals == other.exerciseGoals &&
+                BundlesUtil.equals(exerciseParams, other.exerciseParams)
+        }
+        return false
+    }
+
+    // TODO(b/180612514): Bundle doesn't have hashCode, so we need to override the data class
+    // default.
+    override fun hashCode(): Int {
+        return Objects.hash(
+            exerciseType,
+            dataTypes,
+            autoPauseAndResume,
+            exerciseGoals,
+            BundlesUtil.hashCode(exerciseParams)
+        )
+    }
+
+    public companion object {
+        @JvmStatic public fun builder(): Builder = Builder()
+
+        @JvmField
+        public val CREATOR: Parcelable.Creator<ExerciseConfig> =
+            object : Parcelable.Creator<ExerciseConfig> {
+                override fun createFromParcel(source: Parcel): ExerciseConfig? {
+                    val exerciseType = ExerciseType.fromId(source.readInt())
+
+                    val dataTypesArray = Array<DataType?>(source.readInt()) { null }
+                    source.readTypedArray(dataTypesArray, DataType.CREATOR)
+
+                    val autoPauseAndResume = source.readInt() == 1
+
+                    val exerciseGoals = Array<ExerciseGoal?>(source.readInt()) { null }
+                    source.readTypedArray(exerciseGoals, ExerciseGoal.CREATOR)
+
+                    val exerciseParams =
+                        source.readBundle(ExerciseConfig::class.java.classLoader) ?: Bundle()
+
+                    return ExerciseConfig(
+                        exerciseType,
+                        dataTypesArray.filterNotNull().toSet(),
+                        autoPauseAndResume,
+                        exerciseGoals.filterNotNull().toList(),
+                        exerciseParams
+                    )
+                }
+
+                override fun newArray(size: Int): Array<ExerciseConfig?> {
+                    return arrayOfNulls(size)
+                }
+            }
+    }
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/data/ExerciseGoal.kt b/health/health-services-client/src/main/java/androidx/health/services/client/data/ExerciseGoal.kt
new file mode 100644
index 0000000..39c2ab1
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/data/ExerciseGoal.kt
@@ -0,0 +1,143 @@
+/*
+ * Copyright (C) 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.health.services.client.data
+
+import android.os.Parcel
+import android.os.Parcelable
+import java.util.Objects
+
+// TODO(yeabkal): as we support more types of goals, we may want to rename the class.
+/** Defines a goal for an exercise. */
+@Suppress("DataClassPrivateConstructor")
+public data class ExerciseGoal
+protected constructor(
+    val exerciseGoalType: ExerciseGoalType,
+    val dataTypeCondition: DataTypeCondition,
+    // TODO(yeabkal): shall we rename to "getMilestonePeriod"? Currently "getPeriod" is used to be
+    // flexible in case we support other kinds of goals. Recheck when design is fully locked.
+    val period: Value? = null,
+) : Parcelable {
+
+    override fun describeContents(): Int = 0
+
+    override fun writeToParcel(dest: Parcel, flags: Int) {
+        dest.writeInt(exerciseGoalType.id)
+        dest.writeParcelable(dataTypeCondition, flags)
+        dest.writeParcelable(period, flags)
+    }
+
+    // TODO(yeabkal): try to unify equality logic across goal types.
+    // TODO(b/186899729): We need a better way to match on achieved goals.
+    override fun equals(other: Any?): Boolean {
+        if (other === this) {
+            return true
+        }
+        if (other !is ExerciseGoal) {
+            return false
+        }
+
+        if (this.exerciseGoalType != other.exerciseGoalType) {
+            return false
+        }
+
+        return when (exerciseGoalType) {
+            ExerciseGoalType.ONE_TIME_GOAL -> dataTypeCondition == other.dataTypeCondition
+            // The threshold of a milestone is not included in the equality calculation to let apps
+            // easily map back an achieved milestone to the milestone they requested for tracking.
+            ExerciseGoalType.MILESTONE ->
+                dataTypeCondition.dataType == other.dataTypeCondition.dataType &&
+                    dataTypeCondition.comparisonType == other.dataTypeCondition.comparisonType &&
+                    period == other.period
+        }
+    }
+
+    override fun hashCode(): Int {
+        return if (exerciseGoalType == ExerciseGoalType.ONE_TIME_GOAL) {
+            Objects.hash(exerciseGoalType, dataTypeCondition)
+        } else {
+            Objects.hash(
+                exerciseGoalType,
+                dataTypeCondition.dataType,
+                dataTypeCondition.comparisonType,
+                period
+            )
+        }
+    }
+
+    public companion object {
+        @JvmField
+        public val CREATOR: Parcelable.Creator<ExerciseGoal> =
+            object : Parcelable.Creator<ExerciseGoal> {
+                override fun createFromParcel(source: Parcel): ExerciseGoal? {
+                    val exerciseGoalType = ExerciseGoalType.fromId(source.readInt()) ?: return null
+                    val dataTypeCondition: DataTypeCondition =
+                        source.readParcelable(DataTypeCondition::class.java.classLoader)
+                            ?: return null
+                    val period: Value? = source.readParcelable(Value::class.java.classLoader)
+
+                    return ExerciseGoal(exerciseGoalType, dataTypeCondition, period)
+                }
+
+                override fun newArray(size: Int): Array<ExerciseGoal?> {
+                    return arrayOfNulls(size)
+                }
+            }
+
+        /**
+         * Creates an [ExerciseGoal] that is achieved once when the given [DataTypeCondition] is
+         * satisfied.
+         */
+        @JvmStatic
+        public fun createOneTimeGoal(condition: DataTypeCondition): ExerciseGoal {
+            return ExerciseGoal(ExerciseGoalType.ONE_TIME_GOAL, condition)
+        }
+
+        /**
+         * Creates an [ExerciseGoal] that is achieved multiple times with its threshold being
+         * updated by a `period` value each time it is achieved. For instance, a milestone could be
+         * one for every 2km. This goal will there be triggered at distances = 2km, 4km, 6km, ...
+         */
+        @JvmStatic
+        public fun createMilestone(condition: DataTypeCondition, period: Value): ExerciseGoal {
+            require(period.format == condition.threshold.format) {
+                "The condition's threshold and the period should have the same types of values."
+            }
+            return ExerciseGoal(ExerciseGoalType.MILESTONE, condition, period)
+        }
+
+        /** Creates a new goal that is the same as a given goal but with a new threshold value. */
+        @JvmStatic
+        public fun createMilestoneGoalWithUpdatedThreshold(
+            goal: ExerciseGoal,
+            newThreshold: Value
+        ): ExerciseGoal {
+            require(ExerciseGoalType.MILESTONE == goal.exerciseGoalType) {
+                "The goal to update should be of MILESTONE type."
+            }
+            require(goal.period != null) { "The milestone goal's period should not be null." }
+            val (dataType, oldThreshold, comparisonType) = goal.dataTypeCondition
+            require(oldThreshold.format == newThreshold.format) {
+                "The old and new thresholds should have the same types of values."
+            }
+            return ExerciseGoal(
+                ExerciseGoalType.MILESTONE,
+                DataTypeCondition(dataType, newThreshold, comparisonType),
+                goal.period
+            )
+        }
+    }
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/data/ExerciseGoalType.kt b/health/health-services-client/src/main/java/androidx/health/services/client/data/ExerciseGoalType.kt
new file mode 100644
index 0000000..0790057
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/data/ExerciseGoalType.kt
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 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.health.services.client.data
+
+/** Exercise goal types. */
+public enum class ExerciseGoalType(public val id: Int) {
+    ONE_TIME_GOAL(1),
+    MILESTONE(2);
+
+    public companion object {
+        @JvmStatic
+        public fun fromId(id: Int): ExerciseGoalType? = values().firstOrNull { it.id == id }
+    }
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/data/ExerciseInfo.kt b/health/health-services-client/src/main/java/androidx/health/services/client/data/ExerciseInfo.kt
new file mode 100644
index 0000000..7c43643
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/data/ExerciseInfo.kt
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 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.health.services.client.data
+
+import android.os.Parcel
+import android.os.Parcelable
+
+/** High-level info about the exercise. */
+public data class ExerciseInfo(
+    /** Returns the [ExerciseTrackedStatus]. */
+    val exerciseTrackedStatus: ExerciseTrackedStatus,
+
+    /**
+     * Returns the [ExerciseType] of the active exercise, or [ExerciseType.UNKNOWN] if there is no
+     * active exercise.
+     */
+    val exerciseType: ExerciseType,
+) : Parcelable {
+    override fun describeContents(): Int = 0
+
+    override fun writeToParcel(dest: Parcel, flags: Int) {
+        dest.writeInt(exerciseTrackedStatus.id)
+        dest.writeInt(exerciseType.id)
+    }
+
+    public companion object {
+        @JvmField
+        public val CREATOR: Parcelable.Creator<ExerciseInfo> =
+            object : Parcelable.Creator<ExerciseInfo> {
+                override fun createFromParcel(source: Parcel): ExerciseInfo? {
+                    return ExerciseInfo(
+                        ExerciseTrackedStatus.fromId(source.readInt()) ?: return null,
+                        ExerciseType.fromId(source.readInt())
+                    )
+                }
+
+                override fun newArray(size: Int): Array<ExerciseInfo?> {
+                    return arrayOfNulls(size)
+                }
+            }
+    }
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/data/ExerciseLapSummary.kt b/health/health-services-client/src/main/java/androidx/health/services/client/data/ExerciseLapSummary.kt
new file mode 100644
index 0000000..cd17ab1
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/data/ExerciseLapSummary.kt
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 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.health.services.client.data
+
+import android.os.Parcel
+import android.os.Parcelable
+import java.time.Duration
+import java.time.Instant
+
+/** Describes a completed exercise lap. */
+public data class ExerciseLapSummary(
+    /** Returns the lap count of this summary. Lap count starts at 1 for the first lap. */
+    val lapCount: Int,
+
+    /** Returns the time at which the lap has started. */
+    val startTime: Instant,
+
+    /** Returns the time at which the lap has ended. */
+    val endTime: Instant,
+
+    /**
+     * Returns the total elapsed time for which the exercise has been active during this lap, i.e.
+     * started but not paused.
+     */
+    val activeDuration: Duration,
+
+    /**
+     * Returns the [DataPoint] s for each metric keyed by [DataType] tracked between [startTime] and
+     * [endTime] i.e. during the duration of this lap. This will only contain aggregated [DataType]
+     * s calculated over the duration of the lap.
+     */
+    val lapMetrics: Map<DataType, DataPoint>,
+) : Parcelable {
+    override fun describeContents(): Int = 0
+
+    override fun writeToParcel(dest: Parcel, flags: Int) {
+        dest.writeInt(lapCount)
+        dest.writeLong(startTime.toEpochMilli())
+        dest.writeLong(endTime.toEpochMilli())
+        dest.writeLong(activeDuration.toMillis())
+
+        dest.writeInt(lapMetrics.size)
+        for ((dataType, dataPoint) in lapMetrics) {
+            dest.writeParcelable(dataType, flags)
+            dest.writeParcelable(dataPoint, flags)
+        }
+    }
+
+    public companion object {
+        @JvmField
+        public val CREATOR: Parcelable.Creator<ExerciseLapSummary> =
+            object : Parcelable.Creator<ExerciseLapSummary> {
+                override fun createFromParcel(source: Parcel): ExerciseLapSummary? {
+                    val lapCount = source.readInt()
+                    val startTime = Instant.ofEpochMilli(source.readLong())
+                    val endTime = Instant.ofEpochMilli(source.readLong())
+                    val activeDuration = Duration.ofMillis(source.readLong())
+
+                    val lapMetrics = HashMap<DataType, DataPoint>()
+                    val numMetrics = source.readInt()
+                    repeat(numMetrics) {
+                        val dataType: DataType =
+                            source.readParcelable(DataType::class.java.classLoader) ?: return null
+                        lapMetrics[dataType] =
+                            source.readParcelable(DataPoint::class.java.classLoader) ?: return null
+                    }
+
+                    return ExerciseLapSummary(
+                        lapCount,
+                        startTime,
+                        endTime,
+                        activeDuration,
+                        lapMetrics
+                    )
+                }
+
+                override fun newArray(size: Int): Array<ExerciseLapSummary?> {
+                    return arrayOfNulls(size)
+                }
+            }
+    }
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/data/ExerciseState.kt b/health/health-services-client/src/main/java/androidx/health/services/client/data/ExerciseState.kt
new file mode 100644
index 0000000..964e271
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/data/ExerciseState.kt
@@ -0,0 +1,159 @@
+/*
+ * Copyright (C) 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.health.services.client.data
+
+/** Enumerates the state of an exercise. */
+public enum class ExerciseState(public val id: Int) {
+    /**
+     * The exercise is actively being started, but we don't yet have sensor stability or GPS fix.
+     *
+     * Used only in the manually started exercise.
+     */
+    USER_STARTING(1),
+
+    /**
+     * The exercise is actively in-progress.
+     *
+     * Used in both of the manually started exercise and the automatic exercise detection. It's also
+     * the state when the automatic exercise detection has detected an exercise and the exercise is
+     * actively in-progress.
+     */
+    ACTIVE(2),
+
+    /**
+     * The session is being paused by the user. Sensors are actively being flushed.
+     *
+     * Used only in the manually started exercise.
+     */
+    USER_PAUSING(3),
+
+    /**
+     * The session has been paused by the user. Sensors have completed flushing.
+     *
+     * Used only in the manually started exercise.
+     */
+    USER_PAUSED(4),
+
+    /**
+     * The session is being paused by auto-pause. Sensors are actively being flushed.
+     *
+     * Used only in the manually started exercise.
+     */
+    AUTO_PAUSING(5),
+
+    /**
+     * The session has been automatically paused. Sensors have completed flushing.
+     *
+     * Used only in the manually started exercise.
+     */
+    AUTO_PAUSED(6),
+
+    /**
+     * The session is being resumed by the user.
+     *
+     * Used only in the manually started exercise.
+     */
+    USER_RESUMING(7),
+
+    /**
+     * The session is being automatically resumed.
+     *
+     * Used only in the manually started exercise.
+     */
+    AUTO_RESUMING(8),
+
+    /**
+     * The exercise is being ended by the user. Sensors are actively being flushed.
+     *
+     * Used only in the manually started exercise.
+     */
+    USER_ENDING(9),
+
+    /**
+     * The exercise has been ended by the user. No new metrics will be exported and a final summary
+     * should be provided to the client.
+     *
+     * Used only in the manually started exercise.
+     */
+    USER_ENDED(10),
+
+    /**
+     * The exercise is being automatically ended due to a lack of exercise updates being received by
+     * the user. Sensors are actively being flushed.
+     *
+     * Used only in the manually started exercise.
+     */
+    AUTO_ENDING(11),
+
+    /**
+     * The exercise has been automatically ended due to a lack of exercise updates being received by
+     * the user. No new metrics will be exported and a final summary should be provided to the
+     * client.
+     *
+     * Used only in the manually started exercise.
+     */
+    AUTO_ENDED(12),
+
+    /**
+     * The exercise is being ended because it has been superseded by a new exercise being started by
+     * another client. Sensors are actively being flushed.
+     *
+     * Used in both of the manually started exercise and the automatic exercise detection.
+     */
+    TERMINATING(13),
+
+    /**
+     * The exercise has been ended because it was superseded by a new exercise being started by
+     * another client. No new metrics will be exported and a final summary should be provided to the
+     * client.
+     *
+     * Used in both of the manually started exercise and the automatic exercise detection.
+     */
+    TERMINATED(14);
+
+    /**
+     * Returns true if this [ExerciseState] corresponds to one of the paused states and false
+     * otherwise. This method returns false if the exercise has ended, to check whether it has ended
+     * call [isEnded].
+     */
+    public val isPaused: Boolean
+        get() = PAUSED_STATES.contains(this)
+
+    /**
+     * Returns true if this [ExerciseState] corresponds to one of the resuming states and false
+     * otherwise. This method returns false if the exercise has ended, to check whether it has ended
+     * call [isEnded].
+     */
+    public val isResuming: Boolean
+        get() = RESUMING_STATES.contains(this)
+
+    /**
+     * Returns true if this [ExerciseState] corresponds to one of the ended states and false
+     * otherwise. This method returns false if the exercise has been paused, to check whether it is
+     * currently paused call [isPaused].
+     */
+    public val isEnded: Boolean
+        get() = ENDED_STATES.contains(this)
+
+    public companion object {
+        private val RESUMING_STATES = setOf(USER_RESUMING, AUTO_RESUMING)
+        private val PAUSED_STATES = setOf(USER_PAUSED, AUTO_PAUSED)
+        private val ENDED_STATES = setOf(USER_ENDED, AUTO_ENDED, TERMINATED)
+
+        @JvmStatic public fun fromId(id: Int): ExerciseState? = values().firstOrNull { it.id == id }
+    }
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/data/ExerciseTrackedStatus.kt b/health/health-services-client/src/main/java/androidx/health/services/client/data/ExerciseTrackedStatus.kt
new file mode 100644
index 0000000..2a1279b
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/data/ExerciseTrackedStatus.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 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.health.services.client.data
+
+/** Status representing if an exercise is being tracked and which app owns the exercise. */
+public enum class ExerciseTrackedStatus(public val id: Int) {
+    OTHER_APP_IN_PROGRESS(1),
+    OWNED_EXERCISE_IN_PROGRESS(2),
+    NO_EXERCISE_IN_PROGRESS(3);
+
+    public companion object {
+        @JvmStatic
+        public fun fromId(id: Int): ExerciseTrackedStatus? = values().firstOrNull { it.id == id }
+    }
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/data/ExerciseType.kt b/health/health-services-client/src/main/java/androidx/health/services/client/data/ExerciseType.kt
new file mode 100644
index 0000000..d385c67
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/data/ExerciseType.kt
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 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.health.services.client.data
+
+// TODO(b/185276729): Keep track of values separately to maintain alphabetical order
+// once values are locked in
+/** Exercise type used to configure sensors and algorithms. */
+public enum class ExerciseType(
+    /** Returns a unique identifier of for the [ExerciseType], as an `int`. */
+    public val id: Int
+) {
+    /** The current exercise type of the user is unknown or not set. */
+    UNKNOWN(0),
+    BACK_EXTENSION(1),
+    BADMINTON(2),
+    BARBELL_SHOULDER_PRESS(3),
+    BASEBALL(4),
+    BASKETBALL(5),
+    BENCH_PRESS(6),
+    BENCH_SIT_UP(7),
+    BIKING(8),
+    BIKING_STATIONARY(9),
+    BOOT_CAMP(10),
+    BOXING(11),
+    BURPEE(12),
+
+    /** (E.g., push ups, sit ups, pull-ups, jumping jacks). */
+    CALISTHENICS(13),
+    CRICKET(14),
+    CRUNCH(15),
+    DANCING(16),
+    DEADLIFT(17),
+    DUMBBELL_CURL_RIGHT_ARM(18),
+    DUMBBELL_CURL_LEFT_ARM(19),
+    DUMBBELL_FRONT_RAISE(20),
+    DUMBBELL_LATERAL_RAISE(21),
+    DUMBBELL_TRICEPS_EXTENSION_LEFT_ARM(22),
+    DUMBBELL_TRICEPS_EXTENSION_RIGHT_ARM(23),
+    DUMBBELL_TRICEPS_EXTENSION_TWO_ARM(24),
+    ELLIPTICAL(25),
+    EXERCISE_CLASS(26),
+    FENCING(27),
+    FRISBEE_DISC(28),
+    FOOTBALL_AMERICAN(29),
+    FOOTBALL_AUSTRALIAN(30),
+    GOLF(31),
+    GUIDED_BREATHING(32),
+    GYNMASTICS(33),
+    HANDBALL(34),
+    HIGH_INTENSITY_INTERVAL_TRAINING(35),
+    HIKING(36),
+    ICE_HOCKEY(37),
+    ICE_SKATING(38),
+    JUMP_ROPE(39),
+    JUMPING_JACK(40),
+    LAT_PULL_DOWN(41),
+    LUNGE(42),
+    MARTIAL_ARTS(43),
+    MEDITATION(44),
+    PADDLING(45),
+    PARA_GLIDING(46),
+    PILATES(47),
+    PLANK(48),
+    RACQUETBALL(49),
+    ROCK_CLIMBING(50),
+    ROLLER_HOCKEY(51),
+    ROWING(52),
+    ROWING_MACHINE(53),
+    RUNNING(54),
+    RUNNING_TREADMILL(55),
+    RUGBY(56),
+    SAILING(57),
+    SCUBA_DIVING(58),
+    SKATING(59),
+    SKIING(60),
+    SNOWBOARDING(61),
+    SNOWSHOEING(62),
+    SOCCER(63),
+    SOFTBALL(64),
+    SQUASH(65),
+    SQUAT(66),
+    STAIR_CLIMBING(67),
+    STAIR_CLIMBING_MACHINE(68),
+    STRENGTH_TRAINING(69),
+    STRETCHING(70),
+    SURFING(71),
+    SWIMMING_OPEN_WATER(72),
+    SWIMMING_POOL(73),
+    TABLE_TENNIS(74),
+    TENNIS(75),
+    VOLLEYBALL(76),
+    WALKING(77),
+    WATER_POLO(78),
+    WEIGHTLIFTING(79),
+    WORKOUT_INDOOR(80),
+    WORKOUT_OUTDOOR(81),
+    YOGA(82);
+
+    public companion object {
+        private val IDS = initialize()
+        private fun initialize(): Map<Int, ExerciseType> {
+            val map = mutableMapOf<Int, ExerciseType>()
+            for (exerciseType in values()) {
+                map.put(exerciseType.id, exerciseType)
+            }
+            return map
+        }
+
+        /**
+         * Returns the [ExerciseType] based on its unique `id`.
+         *
+         * If the `id` doesn't map to an particular [ExerciseType], then [ExerciseType.UNKNOWN] is
+         * returned by default.
+         */
+        @JvmStatic
+        public fun fromId(id: Int): ExerciseType {
+            val exerciseType = IDS[id]
+            return exerciseType ?: UNKNOWN
+        }
+    }
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/data/ExerciseUpdate.kt b/health/health-services-client/src/main/java/androidx/health/services/client/data/ExerciseUpdate.kt
new file mode 100644
index 0000000..915d490a
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/data/ExerciseUpdate.kt
@@ -0,0 +1,183 @@
+/*
+ * Copyright (C) 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.health.services.client.data
+
+import android.os.Parcel
+import android.os.Parcelable
+import java.time.Duration
+import java.time.Instant
+
+// TODO(b/179756269): add aggregated metrics.
+/** Contains the latest updated state and metrics for the current exercise. */
+public data class ExerciseUpdate(
+    /** Returns the current status of the exercise. */
+    val state: ExerciseState,
+
+    /** Returns the time at which the exercise was started. */
+    val startTime: Instant,
+
+    /**
+     * Returns the total elapsed time for which the exercise has been active, i.e. started but not
+     * paused.
+     */
+    val activeDuration: Duration,
+
+    /**
+     * Returns the list of latest [DataPoint] for each metric keyed by data type name. This allows a
+     * client to easily query for the "current" values of each metric since last call. There will
+     * only be one value for an Aggregated DataType.
+     */
+    val latestMetrics: Map<DataType, List<DataPoint>>,
+
+    /**
+     * Returns the latest `#ONE_TIME_GOAL` [ExerciseGoal] s that have been achieved. `#MILESTONE`
+     * [ExerciseGoal] s will be returned via `#getLatestMilestoneMarkerSummaries` below.
+     */
+    val latestAchievedGoals: Set<AchievedExerciseGoal>,
+
+    /** Returns the latest [MilestoneMarkerSummary] s. */
+    val latestMilestoneMarkerSummaries: Set<MilestoneMarkerSummary>,
+
+    /**
+     * Returns the [ExerciseConfig] used by the exercise when the [ExerciseUpdate] was dispatched,
+     * or `null` if there isn't any manually started exercise.
+     */
+    val exerciseConfig: ExerciseConfig?,
+
+    /**
+     * Returns the [AutoExerciseConfig] associated for the automatic exercise detection or `null` if
+     * the automatic exercise detection is stopped.
+     */
+    val autoExerciseConfig: AutoExerciseConfig?,
+
+    /**
+     * Returns the [ExerciseType] instance detected by the automatic exercise detection, otherwise
+     * [ExerciseType.UNKNOWN].
+     *
+     * It's only relevant when the automatic exercise detection is on.
+     */
+    val autoExerciseDetected: ExerciseType?,
+) : Parcelable {
+    override fun describeContents(): Int = 0
+
+    override fun writeToParcel(dest: Parcel, flags: Int) {
+        dest.writeInt(state.id)
+        dest.writeLong(startTime.toEpochMilli())
+        dest.writeLong(activeDuration.toMillis())
+
+        dest.writeInt(latestMetrics.size)
+        for ((dataType, dataPoints) in latestMetrics) {
+            dest.writeParcelable(dataType, flags)
+            dest.writeInt(dataPoints.size)
+            dest.writeTypedArray(dataPoints.toTypedArray(), flags)
+        }
+
+        dest.writeInt(latestAchievedGoals.size)
+        dest.writeTypedArray(latestAchievedGoals.toTypedArray(), flags)
+
+        dest.writeInt(latestMilestoneMarkerSummaries.size)
+        dest.writeTypedArray(latestMilestoneMarkerSummaries.toTypedArray(), flags)
+
+        dest.writeParcelable(exerciseConfig, flags)
+        dest.writeParcelable(autoExerciseConfig, flags)
+        dest.writeInt(autoExerciseDetected?.id ?: -1)
+    }
+
+    public companion object {
+        @JvmField
+        public val CREATOR: Parcelable.Creator<ExerciseUpdate> =
+            object : Parcelable.Creator<ExerciseUpdate> {
+                override fun createFromParcel(source: Parcel): ExerciseUpdate? {
+                    val exerciseState = ExerciseState.fromId(source.readInt()) ?: return null
+                    val startTime = Instant.ofEpochMilli(source.readLong())
+                    val activeDuration = Duration.ofMillis(source.readLong())
+
+                    val numMetrics = source.readInt()
+                    val latestMetrics = HashMap<DataType, List<DataPoint>>()
+                    repeat(numMetrics) {
+                        val dataType: DataType =
+                            source.readParcelable(DataType::class.java.classLoader) ?: return null
+                        val dataPointsArray = Array<DataPoint?>(source.readInt()) { null }
+                        source.readTypedArray(dataPointsArray, DataPoint.CREATOR)
+                        latestMetrics[dataType] = dataPointsArray.filterNotNull().toList()
+                    }
+
+                    val latestAchievedGoalsArray =
+                        Array<AchievedExerciseGoal?>(source.readInt()) { null }
+                    source.readTypedArray(latestAchievedGoalsArray, AchievedExerciseGoal.CREATOR)
+
+                    val latestMilestoneMarkerSummariesArray =
+                        Array<MilestoneMarkerSummary?>(source.readInt()) { null }
+                    source.readTypedArray(
+                        latestMilestoneMarkerSummariesArray,
+                        MilestoneMarkerSummary.CREATOR
+                    )
+
+                    val exerciseConfig: ExerciseConfig? =
+                        source.readParcelable(ExerciseConfig::class.java.classLoader)
+                    val autoExerciseConfig: AutoExerciseConfig? =
+                        source.readParcelable(AutoExerciseConfig::class.java.classLoader)
+                    val autoExerciseDetectedId = source.readInt()
+                    val autoExerciseDetected =
+                        if (autoExerciseDetectedId == -1) null
+                        else ExerciseType.fromId(autoExerciseDetectedId)
+
+                    return ExerciseUpdate(
+                        exerciseState,
+                        startTime,
+                        activeDuration,
+                        latestMetrics,
+                        latestAchievedGoalsArray.filterNotNull().toSet(),
+                        latestMilestoneMarkerSummariesArray.filterNotNull().toSet(),
+                        exerciseConfig,
+                        autoExerciseConfig,
+                        autoExerciseDetected
+                    )
+                }
+
+                override fun newArray(size: Int): Array<ExerciseUpdate?> {
+                    return arrayOfNulls(size)
+                }
+            }
+    }
+
+    /** Supported Exercise session types. */
+    public enum class ExerciseSessionType {
+        /**
+         * The exercise is a manually started exercise session.
+         *
+         * [ExerciseUpdate.getExerciseConfig] returns non-null config with this type.
+         */
+        MANUALLY_STARTED_EXERCISE,
+
+        /**
+         * The exercise is an automatic exercise detection session.
+         *
+         * [ExerciseUpdate.getAutoExerciseConfig] returns non-null config with this type.
+         */
+        AUTO_EXERCISE_DETECTION,
+    }
+
+    /** Returns the current [ExerciseSessionType] WHS is carrying out. */
+    public fun getExerciseSessionType(): ExerciseSessionType {
+        return if (autoExerciseConfig != null) {
+            ExerciseSessionType.AUTO_EXERCISE_DETECTION
+        } else {
+            ExerciseSessionType.MANUALLY_STARTED_EXERCISE
+        }
+    }
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/data/MeasureCapabilities.kt b/health/health-services-client/src/main/java/androidx/health/services/client/data/MeasureCapabilities.kt
new file mode 100644
index 0000000..bffc1ec
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/data/MeasureCapabilities.kt
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 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.health.services.client.data
+
+import android.os.Parcel
+import android.os.Parcelable
+
+/**
+ * A place holder class that represents the capabilities of the WHS measuring client on the device.
+ */
+public data class MeasureCapabilities(
+    /**
+     * Set of supported [DataType] s for measure capture on this device.
+     *
+     * Some data types are not available for measurement; this is typically used to measure health
+     * data (e.g. HR).
+     */
+    val supportedDataTypesMeasure: Set<DataType>,
+) : Parcelable {
+    override fun describeContents(): Int = 0
+
+    override fun writeToParcel(dest: Parcel, flags: Int) {
+        dest.writeTypedList(supportedDataTypesMeasure.toList())
+    }
+
+    public companion object {
+        @JvmField
+        public val CREATOR: Parcelable.Creator<MeasureCapabilities> =
+            object : Parcelable.Creator<MeasureCapabilities> {
+                override fun createFromParcel(source: Parcel): MeasureCapabilities? {
+                    val measureDataTypes = ArrayList<DataType>()
+                    source.readTypedList(measureDataTypes, DataType.CREATOR)
+                    return MeasureCapabilities(
+                        measureDataTypes.toSet(),
+                    )
+                }
+
+                override fun newArray(size: Int): Array<MeasureCapabilities?> {
+                    return arrayOfNulls(size)
+                }
+            }
+    }
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/data/MilestoneMarkerSummary.kt b/health/health-services-client/src/main/java/androidx/health/services/client/data/MilestoneMarkerSummary.kt
new file mode 100644
index 0000000..98a340c
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/data/MilestoneMarkerSummary.kt
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 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.health.services.client.data
+
+import android.os.Parcel
+import android.os.Parcelable
+import java.time.Duration
+import java.time.Instant
+
+/**
+ * The summary of metrics and state from the previously achieved milestone marker [ExerciseGoal].
+ */
+public data class MilestoneMarkerSummary(
+    /** Returns the time at which this milestone marker started being tracked. */
+    val startTime: Instant,
+
+    /** Returns the time at which this milestone marker was reached. */
+    val endTime: Instant,
+
+    /**
+     * Returns the total elapsed time for which the exercise was active during this milestone, i.e.
+     * started but not paused.
+     */
+    val activeDuration: Duration,
+
+    /** The [AchievedExerciseGoal] that triggered this milestone summary. */
+    val achievedGoal: AchievedExerciseGoal,
+
+    /**
+     * Returns the [DataPoint] for each aggregated metric keyed by [DataType] tracked between
+     * [startTime] and [endTime] i.e. during the duration of this milestone.
+     */
+    val summaryMetrics: Map<DataType, DataPoint>,
+) : Parcelable {
+    override fun describeContents(): Int = 0
+
+    override fun writeToParcel(dest: Parcel, flags: Int) {
+        dest.writeLong(startTime.toEpochMilli())
+        dest.writeLong(endTime.toEpochMilli())
+        dest.writeLong(activeDuration.toMillis())
+        dest.writeParcelable(achievedGoal, flags)
+
+        dest.writeInt(summaryMetrics.size)
+        for ((dataType, dataPoint) in summaryMetrics) {
+            dest.writeParcelable(dataType, flags)
+            dest.writeParcelable(dataPoint, flags)
+        }
+    }
+
+    public companion object {
+        @JvmField
+        public val CREATOR: Parcelable.Creator<MilestoneMarkerSummary> =
+            object : Parcelable.Creator<MilestoneMarkerSummary> {
+                override fun createFromParcel(source: Parcel): MilestoneMarkerSummary? {
+                    val startTime = Instant.ofEpochMilli(source.readLong())
+                    val endTime = Instant.ofEpochMilli(source.readLong())
+                    val activeDuration = Duration.ofMillis(source.readLong())
+                    val achievedGoal: AchievedExerciseGoal =
+                        source.readParcelable(AchievedExerciseGoal::class.java.classLoader)
+                            ?: return null
+
+                    val summaryMetrics = HashMap<DataType, DataPoint>()
+                    repeat(source.readInt()) {
+                        val dataType: DataType =
+                            source.readParcelable(DataType::class.java.classLoader) ?: return null
+                        val dataPoint: DataPoint =
+                            source.readParcelable(DataPoint::class.java.classLoader) ?: return null
+                        summaryMetrics[dataType] = dataPoint
+                    }
+
+                    return MilestoneMarkerSummary(
+                        startTime = startTime,
+                        endTime = endTime,
+                        activeDuration = activeDuration,
+                        achievedGoal = achievedGoal,
+                        summaryMetrics = summaryMetrics
+                    )
+                }
+
+                override fun newArray(size: Int): Array<MilestoneMarkerSummary?> {
+                    return arrayOfNulls(size)
+                }
+            }
+    }
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/data/PassiveActivityState.kt b/health/health-services-client/src/main/java/androidx/health/services/client/data/PassiveActivityState.kt
new file mode 100644
index 0000000..0013973
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/data/PassiveActivityState.kt
@@ -0,0 +1,155 @@
+/*
+ * Copyright (C) 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.health.services.client.data
+
+import android.content.Intent
+import android.os.Parcel
+import android.os.Parcelable
+import androidx.health.services.client.data.UserActivityState.USER_ACTIVITY_EXERCISE
+import androidx.health.services.client.data.UserActivityState.USER_ACTIVITY_INACTIVE
+import androidx.health.services.client.data.UserActivityState.USER_ACTIVITY_PASSIVE
+import androidx.health.services.client.data.UserActivityState.USER_ACTIVITY_UNKNOWN
+import java.time.Instant
+
+/**
+ * Represents state from Passive tracking.
+ *
+ * Provides [DataPoint] s associated with the Passive tracking, in addition to data related to the
+ * user's [UserActivityState].
+ */
+public data class PassiveActivityState(
+    /** List of [DataPoint] s from Passive tracking. */
+    val dataPoints: List<DataPoint>,
+
+    /** The [UserActivityState] of the user from Passive tracking. */
+    val userActivityState: UserActivityState,
+
+    /**
+     * The [ExerciseType] of the user for a [UserActivityState.USER_ACTIVITY_EXERCISE] state, and
+     * `null` for other [UserActivityState] s.
+     */
+    val exerciseType: ExerciseType?,
+
+    /** The time at which the current state took effect. */
+    val stateChangeTime: Instant,
+) : Parcelable {
+
+    /**
+     * Puts the state as an extra into a given [Intent]. The state can then be obtained from the
+     * intent via [PassiveActivityState.fromIntent].
+     */
+    public fun putToIntent(intent: Intent) {
+        intent.putExtra(EXTRA_KEY, this)
+    }
+
+    override fun describeContents(): Int = 0
+
+    override fun writeToParcel(dest: Parcel, flags: Int) {
+        dest.writeInt(dataPoints.size)
+        dest.writeTypedArray(dataPoints.toTypedArray(), flags)
+
+        dest.writeInt(userActivityState.id)
+        dest.writeInt(exerciseType?.id ?: -1)
+        dest.writeLong(stateChangeTime.toEpochMilli())
+    }
+
+    public companion object {
+        private const val EXTRA_KEY = "whs.passive_activity_state"
+
+        @JvmField
+        public val CREATOR: Parcelable.Creator<PassiveActivityState> =
+            object : Parcelable.Creator<PassiveActivityState> {
+                override fun createFromParcel(source: Parcel): PassiveActivityState? {
+                    val dataPointsArray: Array<DataPoint?> = arrayOfNulls(source.readInt())
+                    source.readTypedArray(dataPointsArray, DataPoint.CREATOR)
+
+                    val activityState = UserActivityState.fromId(source.readInt()) ?: return null
+                    val exerciseTypeId = source.readInt()
+                    val exerciseType =
+                        if (exerciseTypeId == -1) null else ExerciseType.fromId(exerciseTypeId)
+                    val time = Instant.ofEpochMilli(source.readLong())
+
+                    return PassiveActivityState(
+                        dataPointsArray.filterNotNull().toList(),
+                        activityState,
+                        exerciseType,
+                        time
+                    )
+                }
+
+                override fun newArray(size: Int): Array<PassiveActivityState?> {
+                    return arrayOfNulls(size)
+                }
+            }
+
+        /** Creates a [PassiveActivityState] for [USER_ACTIVITY_UNKNOWN]. */
+        @JvmStatic
+        public fun createUnknownTypeState(
+            dataPoints: List<DataPoint>,
+            stateChangeTime: Instant
+        ): PassiveActivityState =
+            PassiveActivityState(
+                dataPoints,
+                USER_ACTIVITY_UNKNOWN,
+                exerciseType = null,
+                stateChangeTime
+            )
+
+        /** Creates a [PassiveActivityState] for [USER_ACTIVITY_EXERCISE]. */
+        @JvmStatic
+        public fun createActiveExerciseState(
+            dataPoints: List<DataPoint>,
+            exerciseType: ExerciseType,
+            stateChangeTime: Instant
+        ): PassiveActivityState =
+            PassiveActivityState(dataPoints, USER_ACTIVITY_EXERCISE, exerciseType, stateChangeTime)
+
+        /** Creates a [PassiveActivityState] for [USER_ACTIVITY_PASSIVE]. */
+        @JvmStatic
+        public fun createPassiveActivityState(
+            dataPoints: List<DataPoint>,
+            stateChangeTime: Instant
+        ): PassiveActivityState =
+            PassiveActivityState(
+                dataPoints,
+                USER_ACTIVITY_PASSIVE,
+                exerciseType = null,
+                stateChangeTime
+            )
+
+        /**
+         * Creates a [PassiveActivityState] from an [Intent]. Returns null if no
+         * [PassiveActivityState] is stored in the given intent.
+         */
+        @JvmStatic
+        public fun fromIntent(intent: Intent): PassiveActivityState? =
+            intent.getParcelableExtra(EXTRA_KEY)
+
+        /** Creates a [PassiveActivityState] for [USER_ACTIVITY_INACTIVE]. */
+        @JvmStatic
+        public fun createInactiveState(
+            dataPoints: List<DataPoint>,
+            stateChangeTime: Instant
+        ): PassiveActivityState =
+            PassiveActivityState(
+                dataPoints,
+                USER_ACTIVITY_INACTIVE,
+                exerciseType = null,
+                stateChangeTime
+            )
+    }
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/data/PassiveMonitoringCapabilities.kt b/health/health-services-client/src/main/java/androidx/health/services/client/data/PassiveMonitoringCapabilities.kt
new file mode 100644
index 0000000..b715fda
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/data/PassiveMonitoringCapabilities.kt
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 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.health.services.client.data
+
+import android.os.Parcel
+import android.os.Parcelable
+
+/**
+ * A place holder class that represents the capabilities of the WHS passive monitoring client on the
+ * device.
+ */
+public data class PassiveMonitoringCapabilities(
+
+    /**
+     * Set of supported [DataType] s for background capture on this device.
+     *
+     * Some data types are only available during exercise (e.g. location) or for measurements.
+     */
+    val supportedDataTypesPassiveMonitoring: Set<DataType>,
+
+    /** Set of supported [DataType] s for event callbacks on this device. */
+    val supportedDataTypesEvents: Set<DataType>,
+) : Parcelable {
+    override fun describeContents(): Int = 0
+
+    override fun writeToParcel(dest: Parcel, flags: Int) {
+        dest.writeTypedList(supportedDataTypesPassiveMonitoring.toList())
+        dest.writeTypedList(supportedDataTypesEvents.toList())
+    }
+
+    public companion object {
+        @JvmField
+        public val CREATOR: Parcelable.Creator<PassiveMonitoringCapabilities> =
+            object : Parcelable.Creator<PassiveMonitoringCapabilities> {
+                override fun createFromParcel(source: Parcel): PassiveMonitoringCapabilities? {
+                    val passiveMonitoringDataTypes = ArrayList<DataType>()
+                    source.readTypedList(passiveMonitoringDataTypes, DataType.CREATOR)
+                    val eventDataTypes = ArrayList<DataType>()
+                    source.readTypedList(eventDataTypes, DataType.CREATOR)
+                    return PassiveMonitoringCapabilities(
+                        passiveMonitoringDataTypes.toSet(),
+                        eventDataTypes.toSet()
+                    )
+                }
+
+                override fun newArray(size: Int): Array<PassiveMonitoringCapabilities?> {
+                    return arrayOfNulls(size)
+                }
+            }
+    }
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/data/UserActivityState.kt b/health/health-services-client/src/main/java/androidx/health/services/client/data/UserActivityState.kt
new file mode 100644
index 0000000..5ba779e
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/data/UserActivityState.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 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.health.services.client.data
+
+/** Types of user activity states. */
+public enum class UserActivityState(public val id: Int) {
+    USER_ACTIVITY_UNKNOWN(0),
+    USER_ACTIVITY_EXERCISE(1),
+    USER_ACTIVITY_PASSIVE(2),
+    USER_ACTIVITY_INACTIVE(3);
+
+    public companion object {
+        @JvmStatic
+        public fun fromId(id: Int): UserActivityState? = values().firstOrNull { it.id == id }
+    }
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/data/Value.kt b/health/health-services-client/src/main/java/androidx/health/services/client/data/Value.kt
new file mode 100644
index 0000000..5ae780d
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/data/Value.kt
@@ -0,0 +1,234 @@
+/*
+ * Copyright (C) 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.health.services.client.data
+
+import android.os.Parcel
+import android.os.Parcelable
+
+/** A [Parcelable] wrapper that can hold a value of a specified type. */
+@Suppress("DataClassPrivateConstructor")
+public data class Value
+private constructor(
+    // TODO(b/175054913): Investigate using a AutoOneOf instead.
+    val format: Int,
+    val doubleList: List<Double>,
+    val longValue: Long,
+) : Parcelable {
+
+    /**
+     * Returns this [Value] represented as an `long`.
+     *
+     * @throws IllegalStateException if [isLong] is `false`
+     */
+    public fun asLong(): Long {
+        check(isLong) { "Attempted to read value as long, but value is not of type long" }
+        return longValue
+    }
+
+    /**
+     * Returns this [Value] represented as an `boolean`.
+     *
+     * @throws IllegalStateException if [isBoolean] is `false`
+     */
+    public fun asBoolean(): Boolean {
+        check(isBoolean) { "Attempted to read value as boolean, but value is not of type boolean" }
+        return longValue != 0L
+    }
+
+    /**
+     * Returns this [Value] represented as a `double`.
+     *
+     * @throws IllegalStateException if [isDouble] is `false`
+     */
+    public fun asDouble(): Double {
+        check(isDouble) { "Attempted to read value as double, but value is not of type double" }
+        return doubleList[0]
+    }
+
+    /**
+     * Returns this [Value] represented as a `double[]`.
+     *
+     * @throws IllegalStateException if [isDoubleArray] is `false`
+     */
+    public fun asDoubleArray(): DoubleArray {
+        check(isDoubleArray) {
+            "Attempted to read value as double array, but value is not correct type"
+        }
+        return doubleList.toDoubleArray()
+    }
+
+    /** Whether or not this [Value] can be represented as an `long`. */
+    public val isLong: Boolean
+        get() = format == FORMAT_LONG
+
+    /** Whether or not this [Value] can be represented as an `boolean`. */
+    public val isBoolean: Boolean
+        get() = format == FORMAT_BOOLEAN
+
+    /** Whether or not this [Value] can be represented as a `double`. */
+    public val isDouble: Boolean
+        get() = format == FORMAT_DOUBLE
+
+    /** Whether or not this [Value] can be represented as a `double[]`. */
+    public val isDoubleArray: Boolean
+        get() = format == FORMAT_DOUBLE_ARRAY
+
+    override fun describeContents(): Int {
+        return 0
+    }
+
+    /**
+     * Writes the value of this object to [dest].
+     *
+     * @throws IllegalStateException if [format] is invalid
+     */
+    override fun writeToParcel(dest: Parcel, flags: Int) {
+        val format = format
+        dest.writeInt(format)
+        when (format) {
+            FORMAT_BOOLEAN, FORMAT_LONG -> {
+                dest.writeLong(longValue)
+                return
+            }
+            FORMAT_DOUBLE -> {
+                dest.writeDouble(asDouble())
+                return
+            }
+            FORMAT_DOUBLE_ARRAY -> {
+                val doubleArray = asDoubleArray()
+                dest.writeInt(doubleArray.size)
+                dest.writeDoubleArray(doubleArray)
+                return
+            }
+            else -> {}
+        }
+        throw IllegalStateException(String.format("Unexpected format: %s", format))
+    }
+
+    public companion object {
+        /** The format used for a [Value] represented as a `double`. */
+        public const val FORMAT_DOUBLE: Int = 1
+
+        /** The format used for a [Value] represented as an `long`. */
+        public const val FORMAT_LONG: Int = 2
+
+        /** The format used for a [Value] represented as an `boolean`. */
+        public const val FORMAT_BOOLEAN: Int = 4
+
+        /** The format used for a [Value] represented as a `double[]`. */
+        public const val FORMAT_DOUBLE_ARRAY: Int = 3
+
+        @JvmField
+        public val CREATOR: Parcelable.Creator<Value> =
+            object : Parcelable.Creator<Value> {
+                override fun createFromParcel(parcel: Parcel): Value {
+                    val format = parcel.readInt()
+                    when (format) {
+                        FORMAT_BOOLEAN, FORMAT_LONG ->
+                            return Value(format, listOf(), parcel.readLong())
+                        FORMAT_DOUBLE ->
+                            return Value(format, listOf(parcel.readDouble()), /* longValue= */ 0)
+                        FORMAT_DOUBLE_ARRAY -> {
+                            val doubleArray = DoubleArray(parcel.readInt())
+                            parcel.readDoubleArray(doubleArray)
+                            return Value(format, doubleArray.toList(), /* longValue= */ 0)
+                        }
+                        else -> {}
+                    }
+                    throw IllegalStateException(String.format("Unexpected format: %s", format))
+                }
+
+                override fun newArray(size: Int): Array<Value?> {
+                    return arrayOfNulls(size)
+                }
+            }
+
+        /** Creates a [Value] that represents a `long`. */
+        @JvmStatic
+        public fun ofLong(value: Long): Value {
+            return Value(FORMAT_LONG, listOf(), value)
+        }
+
+        /** Creates a [Value] that represents an `boolean`. */
+        @JvmStatic
+        public fun ofBoolean(value: Boolean): Value {
+            return Value(FORMAT_BOOLEAN, listOf(), if (value) 1 else 0)
+        }
+
+        /** Creates a [Value] that represents a `double`. */
+        @JvmStatic
+        public fun ofDouble(value: Double): Value {
+            return Value(FORMAT_DOUBLE, listOf(value), longValue = 0)
+        }
+
+        /** Creates a [Value] that represents a `double[]`. */
+        @JvmStatic
+        public fun ofDoubleArray(vararg doubleArray: Double): Value {
+            return Value(FORMAT_DOUBLE_ARRAY, doubleArray.toList(), longValue = 0)
+        }
+
+        /**
+         * Compares two [Value] s based on their representation.
+         *
+         * @throws IllegalStateException if `first` and `second` do not share the same format or are
+         * represented as a `double[]`
+         */
+        internal fun compare(first: Value, second: Value): Int {
+            check(first.format == second.format) {
+                "Attempted to compare Values with different formats"
+            }
+            when (first.format) {
+                FORMAT_LONG -> return first.longValue.compareTo(second.longValue)
+                FORMAT_BOOLEAN -> return first.asBoolean().compareTo(second.asBoolean())
+                FORMAT_DOUBLE -> return first.doubleList[0].compareTo(second.doubleList[0])
+                FORMAT_DOUBLE_ARRAY ->
+                    throw IllegalStateException(
+                        "Attempted to compare Values with invalid format (double array)"
+                    )
+                else -> {}
+            }
+            throw IllegalStateException(String.format("Unexpected format: %s", first.format))
+        }
+
+        /**
+         * Adds two [Value] s based on their representation.
+         *
+         * @throws IllegalStateException if `first` and `second` do not share the same format or are
+         * represented as a `double[]` or `boolean`
+         */
+        @JvmStatic
+        public fun sum(first: Value, second: Value): Value {
+            require(first.format == second.format) {
+                "Attempted to add Values with different formats"
+            }
+            when (first.format) {
+                FORMAT_LONG -> return ofLong(first.asLong() + second.asLong())
+                FORMAT_DOUBLE -> return ofDouble(first.asDouble() + second.asDouble())
+                FORMAT_BOOLEAN ->
+                    throw IllegalStateException(
+                        "Attempted to add Values with invalid format (boolean)"
+                    )
+                FORMAT_DOUBLE_ARRAY ->
+                    throw IllegalStateException(
+                        "Attempted to add Values with invalid format (double array)"
+                    )
+                else -> {}
+            }
+            throw IllegalStateException(String.format("Unexpected format: %s", first.format))
+        }
+    }
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/data/event/Event.kt b/health/health-services-client/src/main/java/androidx/health/services/client/data/event/Event.kt
new file mode 100644
index 0000000..5e28beb
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/data/event/Event.kt
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 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.health.services.client.data.event
+
+import android.os.Parcel
+import android.os.Parcelable
+import androidx.health.services.client.data.DataPoint
+import androidx.health.services.client.data.DataTypeCondition
+
+/** Defines an event that will be triggered when the specified condition is met. */
+public data class Event(
+    /** [DataTypeCondition] which must be met for the event to be triggered. */
+    val dataTypeCondition: DataTypeCondition,
+    val triggerType: TriggerType,
+) : Parcelable {
+
+    /** Whether or not repeated events should be triggered. */
+    public enum class TriggerType(public val id: Int) {
+        /** The event will trigger the first time the specified conditions are met. */
+        ONCE(1),
+
+        /** The event will trigger each time the specified conditions *become* met. */
+        REPEATED(2);
+
+        public companion object {
+            @JvmStatic
+            public fun fromId(id: Int): TriggerType? = values().firstOrNull { it.id == id }
+        }
+    }
+
+    /**
+     * Does the provided [DataPoint] satisfy the event condition.
+     *
+     * @throws IllegalArgumentException if the provided data point is not of the same data type as
+     * the condition itself.
+     */
+    public fun isTriggered(dataPoint: DataPoint): Boolean {
+        return dataTypeCondition.isSatisfied(dataPoint)
+    }
+
+    override fun describeContents(): Int = 0
+
+    override fun writeToParcel(dest: Parcel, flags: Int) {
+        dest.writeParcelable(dataTypeCondition, flags)
+        dest.writeInt(triggerType.id)
+    }
+
+    public companion object {
+        @JvmField
+        public val CREATOR: Parcelable.Creator<Event> =
+            object : Parcelable.Creator<Event> {
+                override fun createFromParcel(source: Parcel): Event? {
+                    return Event(
+                        source.readParcelable(DataTypeCondition::class.java.classLoader)
+                            ?: return null,
+                        TriggerType.fromId(source.readInt()) ?: return null
+                    )
+                }
+
+                override fun newArray(size: Int): Array<Event?> {
+                    return arrayOfNulls(size)
+                }
+            }
+    }
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/impl/ExerciseIpcClient.kt b/health/health-services-client/src/main/java/androidx/health/services/client/impl/ExerciseIpcClient.kt
new file mode 100644
index 0000000..7ab0e8a
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/impl/ExerciseIpcClient.kt
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 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.health.services.client.impl
+
+import android.os.IBinder
+import androidx.health.services.client.impl.ipc.Client
+import androidx.health.services.client.impl.ipc.Client.VersionGetter
+import androidx.health.services.client.impl.ipc.ClientConfiguration
+import androidx.health.services.client.impl.ipc.ServiceOperation
+import androidx.health.services.client.impl.ipc.internal.ConnectionManager
+import androidx.health.services.client.impl.ipc.internal.ListenerKey
+import com.google.common.util.concurrent.ListenableFuture
+
+/**
+ * An IPC Client that connects to and communicates with the WHS Service to make Exercise API calls.
+ *
+ * @hide
+ */
+public class ExerciseIpcClient internal constructor(connectionManager: ConnectionManager) :
+    Client(
+        CLIENT_CONFIGURATION,
+        connectionManager,
+        VersionGetter { binder: IBinder -> getServiceInterface(binder).apiVersion }
+    ) {
+
+    public override fun <T> execute(operation: ServiceOperation<T>): ListenableFuture<T> {
+        return super.execute(operation)
+    }
+
+    public override fun <T> registerListener(
+        listenerKey: ListenerKey,
+        registerListenerOperation: ServiceOperation<T>
+    ): ListenableFuture<T> {
+        return super.registerListener(listenerKey, registerListenerOperation)
+    }
+
+    public override fun <T> unregisterListener(
+        listenerKey: ListenerKey,
+        unregisterListenerOperation: ServiceOperation<T>
+    ): ListenableFuture<T> {
+        return super.unregisterListener(listenerKey, unregisterListenerOperation)
+    }
+
+    public companion object {
+        public const val SERVICE_BIND_ACTION: String =
+            "com.google.android.wearable.healthservices.ExerciseClient"
+        private const val CLIENT = "HealthServicesExerciseClient"
+        private const val SERVICE_PACKAGE_NAME = "com.google.android.wearable.healthservices"
+        private val CLIENT_CONFIGURATION =
+            ClientConfiguration(CLIENT, SERVICE_PACKAGE_NAME, SERVICE_BIND_ACTION)
+
+        @JvmStatic
+        internal fun getServiceInterface(binder: IBinder): IExerciseApiService {
+            return IExerciseApiService.Stub.asInterface(binder)
+        }
+    }
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/impl/ExerciseUpdateListenerStub.kt b/health/health-services-client/src/main/java/androidx/health/services/client/impl/ExerciseUpdateListenerStub.kt
new file mode 100644
index 0000000..d48bee2
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/impl/ExerciseUpdateListenerStub.kt
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 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.health.services.client.impl
+
+import androidx.annotation.GuardedBy
+import androidx.health.services.client.ExerciseUpdateListener
+import androidx.health.services.client.impl.ipc.internal.ListenerKey
+import androidx.health.services.client.impl.response.ExerciseLapSummaryResponse
+import androidx.health.services.client.impl.response.ExerciseUpdateResponse
+import java.util.HashMap
+import java.util.concurrent.Executor
+
+/**
+ * A stub implementation for IExerciseUpdateListener.
+ *
+ * @hide
+ */
+public class ExerciseUpdateListenerStub
+private constructor(private val listener: ExerciseUpdateListener, private val executor: Executor) :
+    IExerciseUpdateListener.Stub() {
+
+    public val listenerKey: ListenerKey = ListenerKey(listener)
+
+    override fun onExerciseUpdate(response: ExerciseUpdateResponse) {
+        executor.execute { listener.onExerciseUpdate(response.exerciseUpdate) }
+    }
+
+    override fun onLapSummary(response: ExerciseLapSummaryResponse) {
+        executor.execute { listener.onLapSummary(response.exerciseLapSummary) }
+    }
+
+    /**
+     * A class that stores unique active instances of [ExerciseUpdateListener] to ensure same binder
+     * object is passed by framework to service side of the IPC.
+     */
+    public class ExerciseUpdateListenerCache private constructor() {
+        @GuardedBy("this")
+        private val listeners: MutableMap<ExerciseUpdateListener, ExerciseUpdateListenerStub> =
+            HashMap()
+
+        @Synchronized
+        public fun getOrCreate(
+            listener: ExerciseUpdateListener,
+            executor: Executor
+        ): ExerciseUpdateListenerStub {
+            return listeners.getOrPut(listener) { ExerciseUpdateListenerStub(listener, executor) }
+        }
+
+        @Synchronized
+        public fun remove(
+            exerciseUpdateListener: ExerciseUpdateListener
+        ): ExerciseUpdateListenerStub? {
+            return listeners.remove(exerciseUpdateListener)
+        }
+
+        public companion object {
+            @JvmField
+            public val INSTANCE: ExerciseUpdateListenerCache = ExerciseUpdateListenerCache()
+        }
+    }
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/impl/HealthServicesIpcClient.kt b/health/health-services-client/src/main/java/androidx/health/services/client/impl/HealthServicesIpcClient.kt
new file mode 100644
index 0000000..f46fda7
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/impl/HealthServicesIpcClient.kt
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 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.health.services.client.impl
+
+import android.os.IBinder
+import androidx.health.services.client.impl.ipc.Client
+import androidx.health.services.client.impl.ipc.Client.VersionGetter
+import androidx.health.services.client.impl.ipc.ClientConfiguration
+import androidx.health.services.client.impl.ipc.ServiceOperation
+import androidx.health.services.client.impl.ipc.internal.ConnectionManager
+import androidx.health.services.client.impl.ipc.internal.ListenerKey
+import com.google.common.util.concurrent.ListenableFuture
+
+/**
+ * An IPC Client that connects to and communicates with the WHS Service.
+ *
+ * @hide
+ */
+public class HealthServicesIpcClient internal constructor(connectionManager: ConnectionManager) :
+    Client(
+        CLIENT_CONFIGURATION,
+        connectionManager,
+        VersionGetter { binder -> getServiceInterface(binder).apiVersion }
+    ) {
+
+    public override fun <T> execute(operation: ServiceOperation<T>): ListenableFuture<T> {
+        return super.execute(operation)
+    }
+
+    public override fun <T> registerListener(
+        listenerKey: ListenerKey,
+        registerListenerOperation: ServiceOperation<T>
+    ): ListenableFuture<T> {
+        return super.registerListener(listenerKey, registerListenerOperation)
+    }
+
+    public override fun <T> unregisterListener(
+        listenerKey: ListenerKey,
+        unregisterListenerOperation: ServiceOperation<T>
+    ): ListenableFuture<T> {
+        return super.unregisterListener(listenerKey, unregisterListenerOperation)
+    }
+
+    public companion object {
+        private const val CLIENT = "HealthServicesClient"
+        private const val SERVICE_PACKAGE_NAME = "com.google.android.wearable.healthservices"
+        public const val SERVICE_BIND_ACTION: String =
+            "com.google.android.wearable.healthservices.HealthServicesClient"
+        private val CLIENT_CONFIGURATION =
+            ClientConfiguration(CLIENT, SERVICE_PACKAGE_NAME, SERVICE_BIND_ACTION)
+
+        @JvmStatic
+        internal fun getServiceInterface(binder: IBinder): IHealthServicesApiService {
+            return IHealthServicesApiService.Stub.asInterface(binder)
+        }
+    }
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/impl/MeasureCallbackStub.kt b/health/health-services-client/src/main/java/androidx/health/services/client/impl/MeasureCallbackStub.kt
new file mode 100644
index 0000000..476c6f6
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/impl/MeasureCallbackStub.kt
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 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.health.services.client.impl
+
+import androidx.annotation.GuardedBy
+import androidx.annotation.VisibleForTesting
+import androidx.health.services.client.MeasureCallback
+import androidx.health.services.client.data.DataType
+import androidx.health.services.client.impl.ipc.internal.ListenerKey
+import androidx.health.services.client.impl.response.AvailabilityResponse
+import androidx.health.services.client.impl.response.DataPointsResponse
+import com.google.common.util.concurrent.MoreExecutors
+import java.util.HashMap
+import java.util.concurrent.Executor
+
+/**
+ * A stub implementation for IMeasureCallback.
+ *
+ * @hide
+ */
+public class MeasureCallbackStub
+private constructor(callbackKey: MeasureCallbackKey, private val callback: MeasureCallback) :
+    IMeasureCallback.Stub() {
+
+    public val listenerKey: ListenerKey = ListenerKey(callbackKey)
+
+    @get:VisibleForTesting
+    public var executor: Executor = MoreExecutors.directExecutor()
+        private set
+
+    override fun onAvailabilityChanged(response: AvailabilityResponse) {
+        executor.execute {
+            callback.onAvailabilityChanged(response.dataType, response.availability)
+        }
+    }
+
+    override fun onData(response: DataPointsResponse) {
+        executor.execute { callback.onData(response.dataPoints) }
+    }
+
+    /**
+     * Its important to use the same stub for registration and un-registration, to ensure same
+     * binder object is passed by framework to service side of the IPC.
+     */
+    public class MeasureCallbackCache private constructor() {
+        @GuardedBy("this")
+        private val listeners: MutableMap<MeasureCallbackKey, MeasureCallbackStub> = HashMap()
+
+        @Synchronized
+        public fun getOrCreate(
+            dataType: DataType,
+            measureCallback: MeasureCallback,
+            executor: Executor
+        ): MeasureCallbackStub {
+            val callbackKey = MeasureCallbackKey(dataType, measureCallback)
+
+            // If a measure callback happens for the same datatype with same callback, pass the same
+            // stub instance, but update the executor, as executor might have changed. Its ok to
+            // register
+            // the callback once again, as on our service implementation re-registering for same
+            // datatype
+            // with  same callback is a no-op.
+            var measureCallbackStub = listeners[callbackKey]
+            if (measureCallbackStub == null) {
+                measureCallbackStub = MeasureCallbackStub(callbackKey, measureCallback)
+                listeners[callbackKey] = measureCallbackStub
+            }
+            measureCallbackStub.executor = executor
+            return measureCallbackStub
+        }
+
+        @Synchronized
+        public fun remove(
+            dataType: DataType,
+            measureCallback: MeasureCallback
+        ): MeasureCallbackStub? {
+            val callbackKey = MeasureCallbackKey(dataType, measureCallback)
+            return listeners.remove(callbackKey)
+        }
+
+        public companion object {
+            @JvmField public val INSTANCE: MeasureCallbackCache = MeasureCallbackCache()
+        }
+    }
+
+    private data class MeasureCallbackKey(
+        private val dataType: DataType,
+        private val measureCallback: MeasureCallback
+    )
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/impl/MeasureIpcClient.kt b/health/health-services-client/src/main/java/androidx/health/services/client/impl/MeasureIpcClient.kt
new file mode 100644
index 0000000..e816a0a
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/impl/MeasureIpcClient.kt
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 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.health.services.client.impl
+
+import android.os.IBinder
+import androidx.health.services.client.impl.ipc.Client
+import androidx.health.services.client.impl.ipc.Client.VersionGetter
+import androidx.health.services.client.impl.ipc.ClientConfiguration
+import androidx.health.services.client.impl.ipc.ServiceOperation
+import androidx.health.services.client.impl.ipc.internal.ConnectionManager
+import androidx.health.services.client.impl.ipc.internal.ListenerKey
+import com.google.common.util.concurrent.ListenableFuture
+
+/**
+ * An IPC Client that connects to and communicates with the WHS Service to make Measure API calls.
+ *
+ * @hide
+ */
+public class MeasureIpcClient internal constructor(connectionManager: ConnectionManager) :
+    Client(
+        CLIENT_CONFIGURATION,
+        connectionManager,
+        VersionGetter { binder -> getServiceInterface(binder).apiVersion }
+    ) {
+
+    public override fun <T> execute(operation: ServiceOperation<T>): ListenableFuture<T> {
+        return super.execute(operation)
+    }
+
+    public override fun <T> registerListener(
+        listenerKey: ListenerKey,
+        registerListenerOperation: ServiceOperation<T>
+    ): ListenableFuture<T> {
+        return super.registerListener(listenerKey, registerListenerOperation)
+    }
+
+    public override fun <T> unregisterListener(
+        listenerKey: ListenerKey,
+        unregisterListenerOperation: ServiceOperation<T>
+    ): ListenableFuture<T> {
+        return super.unregisterListener(listenerKey, unregisterListenerOperation)
+    }
+
+    public companion object {
+        public const val SERVICE_BIND_ACTION: String =
+            "com.google.android.wearable.healthservices.MeasureClient"
+        public const val CLIENT: String = "HealthServicesMeasureClient"
+        public const val SERVICE_PACKAGE_NAME: String = "com.google.android.wearable.healthservices"
+        private val CLIENT_CONFIGURATION =
+            ClientConfiguration(CLIENT, SERVICE_PACKAGE_NAME, SERVICE_BIND_ACTION)
+
+        @JvmStatic
+        internal fun getServiceInterface(binder: IBinder): IMeasureApiService {
+            return IMeasureApiService.Stub.asInterface(binder)
+        }
+    }
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/impl/PassiveMonitoringCallbackStub.kt b/health/health-services-client/src/main/java/androidx/health/services/client/impl/PassiveMonitoringCallbackStub.kt
new file mode 100644
index 0000000..700f1af
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/impl/PassiveMonitoringCallbackStub.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 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.health.services.client.impl
+
+import androidx.health.services.client.PassiveMonitoringCallback
+import androidx.health.services.client.impl.response.PassiveActivityStateResponse
+
+/**
+ * A stub implementation for IPassiveMonitoringCallback.
+ *
+ * @hide
+ */
+internal class PassiveMonitoringCallbackStub
+internal constructor(private val callback: PassiveMonitoringCallback) :
+    IPassiveMonitoringCallback.Stub() {
+
+    override fun onPassiveActivityState(response: PassiveActivityStateResponse) {
+        callback.onPassiveActivityState(response.passiveActivityState)
+    }
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/impl/PassiveMonitoringIpcClient.kt b/health/health-services-client/src/main/java/androidx/health/services/client/impl/PassiveMonitoringIpcClient.kt
new file mode 100644
index 0000000..acbe468
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/impl/PassiveMonitoringIpcClient.kt
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 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.health.services.client.impl
+
+import android.os.IBinder
+import androidx.health.services.client.impl.ipc.Client
+import androidx.health.services.client.impl.ipc.Client.VersionGetter
+import androidx.health.services.client.impl.ipc.ClientConfiguration
+import androidx.health.services.client.impl.ipc.ServiceOperation
+import androidx.health.services.client.impl.ipc.internal.ConnectionManager
+import com.google.common.util.concurrent.ListenableFuture
+
+/**
+ * An IPC Client that connects to and communicates with the WHS Service to make passive monitoring
+ * API calls.
+ *
+ * @hide
+ */
+public class PassiveMonitoringIpcClient(connectionManager: ConnectionManager) :
+    Client(
+        CLIENT_CONFIGURATION,
+        connectionManager,
+        VersionGetter { binder -> getServiceInterface(binder).apiVersion }
+    ) {
+
+    public override fun <T> execute(operation: ServiceOperation<T>): ListenableFuture<T> {
+        return super.execute(operation)
+    }
+
+    public companion object {
+        public const val SERVICE_BIND_ACTION: String =
+            "com.google.android.wearable.healthservices.PassiveMonitoringClient"
+        private const val CLIENT = "HealthServicesPassiveMonitoringClient"
+        private const val SERVICE_PACKAGE_NAME = "com.google.android.wearable.healthservices"
+        private val CLIENT_CONFIGURATION =
+            ClientConfiguration(CLIENT, SERVICE_PACKAGE_NAME, SERVICE_BIND_ACTION)
+
+        @JvmStatic
+        internal fun getServiceInterface(binder: IBinder): IPassiveMonitoringApiService {
+            return IPassiveMonitoringApiService.Stub.asInterface(binder)
+        }
+    }
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/impl/ServiceBackedExerciseClient.kt b/health/health-services-client/src/main/java/androidx/health/services/client/impl/ServiceBackedExerciseClient.kt
new file mode 100644
index 0000000..adff9c2
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/impl/ServiceBackedExerciseClient.kt
@@ -0,0 +1,203 @@
+/*
+ * Copyright (C) 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.health.services.client.impl
+
+import android.content.Context
+import androidx.core.content.ContextCompat
+import androidx.health.services.client.ExerciseClient
+import androidx.health.services.client.ExerciseUpdateListener
+import androidx.health.services.client.data.Capabilities
+import androidx.health.services.client.data.ExerciseConfig
+import androidx.health.services.client.data.ExerciseGoal
+import androidx.health.services.client.data.ExerciseInfo
+import androidx.health.services.client.impl.ExerciseIpcClient.Companion.getServiceInterface
+import androidx.health.services.client.impl.internal.ExerciseInfoCallback
+import androidx.health.services.client.impl.internal.StatusCallback
+import androidx.health.services.client.impl.internal.WhsConnectionManager
+import androidx.health.services.client.impl.ipc.ServiceOperation
+import androidx.health.services.client.impl.ipc.internal.ConnectionManager
+import androidx.health.services.client.impl.request.AutoPauseAndResumeConfigRequest
+import androidx.health.services.client.impl.request.CapabilitiesRequest
+import androidx.health.services.client.impl.request.ExerciseGoalRequest
+import androidx.health.services.client.impl.request.StartExerciseRequest
+import androidx.health.services.client.impl.response.CapabilitiesResponse
+import com.google.common.util.concurrent.Futures
+import com.google.common.util.concurrent.ListenableFuture
+import java.util.concurrent.Executor
+
+/**
+ * [ExerciseClient] API implementation that receives data from the WHS Service.
+ *
+ * @hide
+ */
+internal class ServiceBackedExerciseClient
+private constructor(private val context: Context, connectionManager: ConnectionManager) :
+    ExerciseClient {
+
+    private val ipcClient: ExerciseIpcClient = ExerciseIpcClient(connectionManager)
+
+    override fun startExercise(configuration: ExerciseConfig): ListenableFuture<Void> {
+        val serviceOperation =
+            ServiceOperation<Void> { binder, resultFuture ->
+                getServiceInterface(binder)
+                    .startExercise(
+                        StartExerciseRequest(context.packageName, configuration),
+                        StatusCallback(resultFuture)
+                    )
+            }
+        return ipcClient.execute(serviceOperation)
+    }
+
+    override fun pauseExercise(): ListenableFuture<Void> {
+        val serviceOperation =
+            ServiceOperation<Void> { binder, resultFuture ->
+                getServiceInterface(binder)
+                    .pauseExercise(context.packageName, StatusCallback(resultFuture))
+            }
+        return ipcClient.execute(serviceOperation)
+    }
+
+    override fun resumeExercise(): ListenableFuture<Void> {
+        val serviceOperation =
+            ServiceOperation<Void> { binder, resultFuture ->
+                getServiceInterface(binder)
+                    .resumeExercise(context.packageName, StatusCallback(resultFuture))
+            }
+        return ipcClient.execute(serviceOperation)
+    }
+
+    override fun endExercise(): ListenableFuture<Void> {
+        val serviceOperation =
+            ServiceOperation<Void> { binder, resultFuture ->
+                getServiceInterface(binder)
+                    .endExercise(context.packageName, StatusCallback(resultFuture))
+            }
+        return ipcClient.execute(serviceOperation)
+    }
+
+    override fun markLap(): ListenableFuture<Void> {
+        val serviceOperation =
+            ServiceOperation<Void> { binder, resultFuture ->
+                getServiceInterface(binder)
+                    .markLap(context.packageName, StatusCallback(resultFuture))
+            }
+        return ipcClient.execute(serviceOperation)
+    }
+
+    override val currentExerciseInfo: ListenableFuture<ExerciseInfo>
+        get() {
+            val serviceOperation =
+                ServiceOperation<ExerciseInfo> { binder, resultFuture ->
+                    getServiceInterface(binder)
+                        .getCurrentExerciseInfo(
+                            context.packageName,
+                            ExerciseInfoCallback(resultFuture)
+                        )
+                }
+            return ipcClient.execute(serviceOperation)
+        }
+
+    override fun setUpdateListener(listener: ExerciseUpdateListener): ListenableFuture<Void> {
+        return setUpdateListener(listener, ContextCompat.getMainExecutor(context))
+    }
+
+    override fun setUpdateListener(
+        listener: ExerciseUpdateListener,
+        executor: Executor
+    ): ListenableFuture<Void> {
+        val listenerStub =
+            ExerciseUpdateListenerStub.ExerciseUpdateListenerCache.INSTANCE.getOrCreate(
+                listener,
+                executor
+            )
+        val serviceOperation =
+            ServiceOperation<Void> { binder, resultFuture ->
+                getServiceInterface(binder)
+                    .setUpdateListener(
+                        context.packageName,
+                        listenerStub,
+                        StatusCallback(resultFuture)
+                    )
+            }
+        return ipcClient.registerListener(listenerStub.listenerKey, serviceOperation)
+    }
+
+    override fun clearUpdateListener(listener: ExerciseUpdateListener): ListenableFuture<Void> {
+        val listenerStub =
+            ExerciseUpdateListenerStub.ExerciseUpdateListenerCache.INSTANCE.remove(listener)
+                ?: return Futures.immediateFailedFuture(
+                    IllegalArgumentException("Given listener was not added.")
+                )
+        val serviceOperation =
+            ServiceOperation<Void> { binder, resultFuture ->
+                getServiceInterface(binder)
+                    .clearUpdateListener(
+                        context.packageName,
+                        listenerStub,
+                        StatusCallback(resultFuture)
+                    )
+            }
+        return ipcClient.unregisterListener(listenerStub.listenerKey, serviceOperation)
+    }
+
+    override fun addGoalToActiveExercise(exerciseGoal: ExerciseGoal): ListenableFuture<Void> {
+        val serviceOperation =
+            ServiceOperation<Void> { binder, resultFuture ->
+                getServiceInterface(binder)
+                    .addGoalToActiveExercise(
+                        ExerciseGoalRequest(context.packageName, exerciseGoal),
+                        StatusCallback(resultFuture)
+                    )
+            }
+        return ipcClient.execute(serviceOperation)
+    }
+
+    override fun overrideAutoPauseAndResumeForActiveExercise(
+        enabled: Boolean
+    ): ListenableFuture<Void> {
+        val serviceOperation =
+            ServiceOperation<Void> { binder, resultFuture ->
+                getServiceInterface(binder)
+                    .overrideAutoPauseAndResumeForActiveExercise(
+                        AutoPauseAndResumeConfigRequest(context.packageName, enabled),
+                        StatusCallback(resultFuture)
+                    )
+            }
+        return ipcClient.execute(serviceOperation)
+    }
+
+    override val capabilities: ListenableFuture<Capabilities>
+        get() {
+            val request = CapabilitiesRequest(context.packageName)
+            val serviceOperation =
+                ServiceOperation<CapabilitiesResponse> { binder, resultFuture ->
+                    resultFuture.set(getServiceInterface(binder).getCapabilities(request))
+                }
+            return Futures.transform(
+                ipcClient.execute(serviceOperation),
+                { response -> response?.capabilities },
+                ContextCompat.getMainExecutor(context)
+            )
+        }
+
+    internal companion object {
+        @JvmStatic
+        fun getClient(context: Context): ServiceBackedExerciseClient {
+            return ServiceBackedExerciseClient(context, WhsConnectionManager.getInstance(context))
+        }
+    }
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/impl/ServiceBackedHealthServicesClient.kt b/health/health-services-client/src/main/java/androidx/health/services/client/impl/ServiceBackedHealthServicesClient.kt
new file mode 100644
index 0000000..ef052fb
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/impl/ServiceBackedHealthServicesClient.kt
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 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.health.services.client.impl
+
+import android.content.Context
+import androidx.health.services.client.ExerciseClient
+import androidx.health.services.client.HealthServicesClient
+import androidx.health.services.client.MeasureClient
+import androidx.health.services.client.PassiveMonitoringClient
+import androidx.health.services.client.impl.internal.WhsConnectionManager
+import androidx.health.services.client.impl.ipc.internal.ConnectionManager
+
+/**
+ * A [HealthServicesClient] implementation.
+ *
+ * @hide
+ */
+public class ServiceBackedHealthServicesClient
+internal constructor(context: Context, connectionManager: ConnectionManager) :
+    HealthServicesClient {
+
+    private val applicationContext: Context = context.applicationContext
+    private val ipcClient: HealthServicesIpcClient = HealthServicesIpcClient(connectionManager)
+
+    public constructor(context: Context) : this(context, WhsConnectionManager.getInstance(context))
+
+    override val exerciseClient: ExerciseClient
+        get() = ServiceBackedExerciseClient.getClient(applicationContext)
+    override val passiveMonitoringClient: PassiveMonitoringClient
+        get() = ServiceBackedPassiveMonitoringClient(applicationContext)
+    override val measureClient: MeasureClient
+        get() = ServiceBackedMeasureClient.getClient(applicationContext)
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/impl/ServiceBackedMeasureClient.kt b/health/health-services-client/src/main/java/androidx/health/services/client/impl/ServiceBackedMeasureClient.kt
new file mode 100644
index 0000000..51e21ed
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/impl/ServiceBackedMeasureClient.kt
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 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.health.services.client.impl
+
+import android.content.Context
+import androidx.annotation.VisibleForTesting
+import androidx.core.content.ContextCompat
+import androidx.health.services.client.MeasureCallback
+import androidx.health.services.client.MeasureClient
+import androidx.health.services.client.data.DataType
+import androidx.health.services.client.data.MeasureCapabilities
+import androidx.health.services.client.impl.MeasureCallbackStub.MeasureCallbackCache
+import androidx.health.services.client.impl.MeasureIpcClient.Companion.getServiceInterface
+import androidx.health.services.client.impl.internal.StatusCallback
+import androidx.health.services.client.impl.internal.WhsConnectionManager
+import androidx.health.services.client.impl.ipc.ServiceOperation
+import androidx.health.services.client.impl.ipc.internal.ConnectionManager
+import androidx.health.services.client.impl.request.CapabilitiesRequest
+import androidx.health.services.client.impl.request.MeasureRegistrationRequest
+import androidx.health.services.client.impl.request.MeasureUnregistrationRequest
+import androidx.health.services.client.impl.response.MeasureCapabilitiesResponse
+import com.google.common.util.concurrent.Futures
+import com.google.common.util.concurrent.ListenableFuture
+import java.util.concurrent.Executor
+
+/**
+ * [MeasureClient] that interacts with WHS via IPC.
+ *
+ * @hide
+ */
+@VisibleForTesting
+public class ServiceBackedMeasureClient(
+    private val context: Context,
+    connectionManager: ConnectionManager
+) : MeasureClient {
+
+    private val ipcClient: MeasureIpcClient = MeasureIpcClient(connectionManager)
+
+    override fun registerCallback(
+        dataType: DataType,
+        callback: MeasureCallback
+    ): ListenableFuture<Void> {
+        return registerCallback(dataType, callback, ContextCompat.getMainExecutor(context))
+    }
+
+    override fun registerCallback(
+        dataType: DataType,
+        callback: MeasureCallback,
+        executor: Executor
+    ): ListenableFuture<Void> {
+        val request = MeasureRegistrationRequest(context.packageName, dataType)
+        val callbackStub = MeasureCallbackCache.INSTANCE.getOrCreate(dataType, callback, executor)
+        val serviceOperation =
+            ServiceOperation<Void> { binder, resultFuture ->
+                getServiceInterface(binder)
+                    .registerCallback(request, callbackStub, StatusCallback(resultFuture))
+            }
+        return ipcClient.registerListener(callbackStub.listenerKey, serviceOperation)
+    }
+
+    override fun unregisterCallback(
+        dataType: DataType,
+        callback: MeasureCallback
+    ): ListenableFuture<Void> {
+        val callbackStub =
+            MeasureCallbackCache.INSTANCE.remove(dataType, callback)
+                ?: return Futures.immediateFailedFuture(
+                    IllegalArgumentException("Given callback was not registered.")
+                )
+        val request = MeasureUnregistrationRequest(context.packageName, dataType)
+        val serviceOperation =
+            ServiceOperation<Void> { binder, resultFuture ->
+                getServiceInterface(binder)
+                    .unregisterCallback(request, callbackStub, StatusCallback(resultFuture))
+            }
+        return ipcClient.unregisterListener(callbackStub.listenerKey, serviceOperation)
+    }
+
+    override val capabilities: ListenableFuture<MeasureCapabilities>
+        get() {
+            val request = CapabilitiesRequest(context.packageName)
+            val serviceOperation =
+                ServiceOperation<MeasureCapabilitiesResponse> { binder, resultFuture ->
+                    resultFuture.set(getServiceInterface(binder).getCapabilities(request))
+                }
+            return Futures.transform(
+                ipcClient.execute(serviceOperation),
+                { response -> response?.measureCapabilities },
+                ContextCompat.getMainExecutor(context)
+            )
+        }
+
+    internal companion object {
+        @JvmStatic
+        fun getClient(context: Context): ServiceBackedMeasureClient {
+            return ServiceBackedMeasureClient(context, WhsConnectionManager.getInstance(context))
+        }
+    }
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/impl/ServiceBackedPassiveMonitoringClient.kt b/health/health-services-client/src/main/java/androidx/health/services/client/impl/ServiceBackedPassiveMonitoringClient.kt
new file mode 100644
index 0000000..f156c38
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/impl/ServiceBackedPassiveMonitoringClient.kt
@@ -0,0 +1,131 @@
+/*
+ * Copyright (C) 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.health.services.client.impl
+
+import android.app.PendingIntent
+import android.content.Context
+import androidx.core.content.ContextCompat
+import androidx.health.services.client.PassiveMonitoringCallback
+import androidx.health.services.client.PassiveMonitoringClient
+import androidx.health.services.client.data.DataType
+import androidx.health.services.client.data.PassiveMonitoringCapabilities
+import androidx.health.services.client.data.event.Event
+import androidx.health.services.client.impl.PassiveMonitoringIpcClient.Companion.getServiceInterface
+import androidx.health.services.client.impl.internal.StatusCallback
+import androidx.health.services.client.impl.internal.WhsConnectionManager
+import androidx.health.services.client.impl.ipc.ServiceOperation
+import androidx.health.services.client.impl.request.BackgroundRegistrationRequest
+import androidx.health.services.client.impl.request.CapabilitiesRequest
+import androidx.health.services.client.impl.request.EventRequest
+import androidx.health.services.client.impl.response.PassiveMonitoringCapabilitiesResponse
+import com.google.common.util.concurrent.Futures
+import com.google.common.util.concurrent.ListenableFuture
+
+/**
+ * Passive monitoring client that interacts with WHS via IPC.
+ *
+ * @hide
+ */
+internal class ServiceBackedPassiveMonitoringClient(private val applicationContext: Context) :
+    PassiveMonitoringClient {
+
+    private val ipcClient: PassiveMonitoringIpcClient =
+        PassiveMonitoringIpcClient(WhsConnectionManager.getInstance(applicationContext))
+
+    override fun registerDataCallback(
+        dataTypes: Set<DataType>,
+        callbackIntent: PendingIntent
+    ): ListenableFuture<Void> {
+        return registerDataCallbackInternal(dataTypes, callbackIntent, callback = null)
+    }
+
+    override fun registerDataCallback(
+        dataTypes: Set<DataType>,
+        callbackIntent: PendingIntent,
+        callback: PassiveMonitoringCallback
+    ): ListenableFuture<Void> {
+        return registerDataCallbackInternal(dataTypes, callbackIntent, callback)
+    }
+
+    override fun unregisterDataCallback(): ListenableFuture<Void> {
+        val serviceOperation =
+            ServiceOperation<Void> { binder, resultFuture ->
+                getServiceInterface(binder)
+                    .unregisterDataCallback(
+                        applicationContext.packageName,
+                        StatusCallback(resultFuture)
+                    )
+            }
+        return ipcClient.execute(serviceOperation)
+    }
+
+    override fun registerEventCallback(
+        event: Event,
+        callbackIntent: PendingIntent
+    ): ListenableFuture<Void> {
+        val request = EventRequest(applicationContext.packageName, event)
+        val serviceOperation =
+            ServiceOperation<Void> { binder, resultFuture ->
+                getServiceInterface(binder)
+                    .registerEventCallback(request, callbackIntent, StatusCallback(resultFuture))
+            }
+        return ipcClient.execute(serviceOperation)
+    }
+
+    override fun unregisterEventCallback(event: Event): ListenableFuture<Void> {
+        val request = EventRequest(applicationContext.packageName, event)
+        val serviceOperation =
+            ServiceOperation<Void> { binder, resultFuture ->
+                getServiceInterface(binder)
+                    .unregisterEventCallback(request, StatusCallback(resultFuture))
+            }
+        return ipcClient.execute(serviceOperation)
+    }
+
+    override val capabilities: ListenableFuture<PassiveMonitoringCapabilities>
+        get() {
+            val request = CapabilitiesRequest(applicationContext.packageName)
+            val serviceOperation =
+                ServiceOperation<PassiveMonitoringCapabilitiesResponse> { binder, resultFuture ->
+                    resultFuture.set(getServiceInterface(binder).getCapabilities(request))
+                }
+            return Futures.transform(
+                ipcClient.execute(serviceOperation),
+                { response -> response?.passiveMonitoringCapabilities },
+                ContextCompat.getMainExecutor(applicationContext)
+            )
+        }
+
+    private fun registerDataCallbackInternal(
+        dataTypes: Set<DataType>,
+        callbackIntent: PendingIntent,
+        callback: PassiveMonitoringCallback?
+    ): ListenableFuture<Void> {
+        val request = BackgroundRegistrationRequest(applicationContext.packageName, dataTypes)
+        val serviceOperation =
+            ServiceOperation<Void> { binder, resultFuture ->
+                getServiceInterface(binder)
+                    .registerDataCallback(
+                        request,
+                        callbackIntent,
+                        callback?.let { PassiveMonitoringCallbackStub(it) },
+                        StatusCallback(resultFuture)
+                    )
+            }
+        return ipcClient.execute(serviceOperation)
+    }
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/impl/internal/ExerciseInfoCallback.kt b/health/health-services-client/src/main/java/androidx/health/services/client/impl/internal/ExerciseInfoCallback.kt
new file mode 100644
index 0000000..078cc154
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/impl/internal/ExerciseInfoCallback.kt
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 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.health.services.client.impl.internal
+
+import android.os.RemoteException
+import androidx.health.services.client.data.ExerciseInfo
+import androidx.health.services.client.impl.response.ExerciseInfoResponse
+import com.google.common.util.concurrent.SettableFuture
+
+/**
+ * A callback for ipc invocations dealing with [ExerciseInfo].
+ *
+ * @hide
+ */
+public class ExerciseInfoCallback(private val resultFuture: SettableFuture<ExerciseInfo>) :
+    IExerciseInfoCallback.Stub() {
+
+    @Throws(RemoteException::class)
+    override fun onExerciseInfo(response: ExerciseInfoResponse) {
+        resultFuture.set(response.exerciseInfo)
+    }
+
+    @Throws(RemoteException::class)
+    override fun onFailure(message: String) {
+        resultFuture.setException(Exception(message))
+    }
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/impl/internal/StatusCallback.kt b/health/health-services-client/src/main/java/androidx/health/services/client/impl/internal/StatusCallback.kt
new file mode 100644
index 0000000..89789e6
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/impl/internal/StatusCallback.kt
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 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.health.services.client.impl.internal
+
+import android.os.RemoteException
+import com.google.common.util.concurrent.SettableFuture
+
+/**
+ * A generic callback for ipc invocations.
+ *
+ * @hide
+ */
+public class StatusCallback(private val resultFuture: SettableFuture<Void>) :
+    IStatusCallback.Stub() {
+
+    @Throws(RemoteException::class)
+    override fun onSuccess() {
+        resultFuture.set(null)
+    }
+
+    @Throws(RemoteException::class)
+    override fun onFailure(msg: String) {
+        resultFuture.setException(Exception(msg))
+    }
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/impl/internal/WhsConnectionManager.kt b/health/health-services-client/src/main/java/androidx/health/services/client/impl/internal/WhsConnectionManager.kt
new file mode 100644
index 0000000..eb701b6
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/impl/internal/WhsConnectionManager.kt
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 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.health.services.client.impl.internal
+
+import android.annotation.SuppressLint
+import android.content.Context
+import android.os.HandlerThread
+import android.os.Looper
+import android.os.Process
+import androidx.annotation.GuardedBy
+import androidx.health.services.client.impl.ipc.internal.ConnectionManager
+
+/**
+ * Utility to return an instance of connection manager.
+ *
+ * @hide
+ */
+public object WhsConnectionManager {
+
+    private val lock = Any()
+
+    // Suppress StaticFieldLeak; we're only storing application Context.
+    @SuppressLint("StaticFieldLeak")
+    @GuardedBy("lock")
+    private lateinit var instance: ConnectionManager
+
+    @JvmStatic
+    public fun getInstance(context: Context): ConnectionManager {
+        synchronized(lock) {
+            if (!::instance.isInitialized) {
+                val looper = startHandlerThread()
+                instance = ConnectionManager(context.applicationContext, looper)
+            }
+
+            return instance
+        }
+    }
+
+    private fun startHandlerThread(): Looper {
+        val handlerThread =
+            HandlerThread(
+                "WhsConnectionManager",
+                Process.THREAD_PRIORITY_BACKGROUND + Process.THREAD_PRIORITY_MORE_FAVORABLE
+            )
+        handlerThread.start()
+        return handlerThread.looper
+    }
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/impl/ipc/ApiVersionException.java b/health/health-services-client/src/main/java/androidx/health/services/client/impl/ipc/ApiVersionException.java
new file mode 100644
index 0000000..134ffa3
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/impl/ipc/ApiVersionException.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 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.health.services.client.impl.ipc;
+
+import androidx.annotation.RestrictTo;
+import androidx.annotation.RestrictTo.Scope;
+
+import java.util.concurrent.ExecutionException;
+
+/**
+ * Exception that is thrown when API version requirements are not met.
+ *
+ * @hide
+ */
+@RestrictTo(Scope.LIBRARY)
+public class ApiVersionException extends ExecutionException {
+
+    private final int mRemoteVersion;
+    private final int mMinVersion;
+
+    public ApiVersionException(int remoteVersion, int minVersion) {
+        super(
+                "Version requirements for calling the method was not met, remoteVersion: "
+                        + remoteVersion
+                        + ", minVersion: "
+                        + minVersion);
+        this.mRemoteVersion = remoteVersion;
+        this.mMinVersion = minVersion;
+    }
+
+    public int getRemoteVersion() {
+        return mRemoteVersion;
+    }
+
+    public int getMinVersion() {
+        return mMinVersion;
+    }
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/impl/ipc/Client.java b/health/health-services-client/src/main/java/androidx/health/services/client/impl/ipc/Client.java
new file mode 100644
index 0000000..b417620
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/impl/ipc/Client.java
@@ -0,0 +1,237 @@
+/*
+ * Copyright (C) 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.health.services.client.impl.ipc;
+
+import android.os.IBinder;
+import android.os.RemoteException;
+
+import androidx.annotation.Nullable;
+import androidx.annotation.RestrictTo;
+import androidx.annotation.RestrictTo.Scope;
+import androidx.annotation.VisibleForTesting;
+import androidx.health.services.client.impl.ipc.internal.BaseQueueOperation;
+import androidx.health.services.client.impl.ipc.internal.ConnectionConfiguration;
+import androidx.health.services.client.impl.ipc.internal.ConnectionManager;
+import androidx.health.services.client.impl.ipc.internal.ExecutionTracker;
+import androidx.health.services.client.impl.ipc.internal.ListenerKey;
+import androidx.health.services.client.impl.ipc.internal.QueueOperation;
+
+import com.google.common.util.concurrent.FutureCallback;
+import com.google.common.util.concurrent.Futures;
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.MoreExecutors;
+import com.google.common.util.concurrent.SettableFuture;
+
+/**
+ * SDK client for establishing connection to a cross process service.
+ *
+ * <p>Extend this class to create a new client. Each client should represent one connection to AIDL
+ * interface. For user instruction see: go/wear-dd-wcs-sdk
+ *
+ * @hide
+ */
+@RestrictTo(Scope.LIBRARY)
+public abstract class Client {
+
+    /** Interface abstracting extraction of the API version from the binder. */
+    public interface VersionGetter {
+        /** Returns the API version. */
+        Integer readVersion(IBinder binder) throws RemoteException;
+    }
+
+    private static final int UNKNOWN_VERSION = -1;
+
+    private final ConnectionConfiguration mConnectionConfiguration;
+    private final ConnectionManager mConnectionManager;
+    private final ServiceOperation<Integer> mApiVersionOperation;
+
+    @VisibleForTesting volatile int mCurrentVersion = UNKNOWN_VERSION;
+
+    public Client(
+            ClientConfiguration clientConfiguration,
+            ConnectionManager connectionManager,
+            VersionGetter versionGetter) {
+        QueueOperation versionOperation =
+                new QueueOperation() {
+                    @Override
+                    public void execute(IBinder binder) throws RemoteException {
+                        mCurrentVersion = versionGetter.readVersion(binder);
+                    }
+
+                    @Override
+                    public void setException(Throwable exception) {}
+
+                    @Override
+                    public QueueOperation trackExecution(ExecutionTracker tracker) {
+                        return this;
+                    }
+
+                    @Override
+                    public ConnectionConfiguration getConnectionConfiguration() {
+                        return Client.this.getConnectionConfiguration();
+                    }
+                };
+        this.mConnectionConfiguration =
+                new ConnectionConfiguration(
+                        clientConfiguration.getServicePackageName(),
+                        clientConfiguration.getApiClientName(),
+                        clientConfiguration.getBindAction(),
+                        versionOperation);
+        this.mConnectionManager = connectionManager;
+        this.mApiVersionOperation =
+                (binder, resultFuture) -> resultFuture.set(versionGetter.readVersion(binder));
+    }
+
+    /**
+     * Executes given operation against a IPC service defined by {@code clientConfiguration}.
+     *
+     * @param operation Operation that will be executed against the service
+     * @param <R> Type of returned variable
+     * @return {@link ListenableFuture<R>} with the result of the operation or an exception if the
+     *     execution fails.
+     */
+    protected <R> ListenableFuture<R> execute(ServiceOperation<R> operation) {
+        SettableFuture<R> settableFuture = SettableFuture.create();
+        mConnectionManager.scheduleForExecution(
+                createQueueOperation(operation, mConnectionConfiguration, settableFuture));
+        return settableFuture;
+    }
+
+    protected <R> ListenableFuture<R> executeWithVersionCheck(
+            ServiceOperation<R> operation, int minApiVersion) {
+        if (mCurrentVersion == UNKNOWN_VERSION) {
+            SettableFuture<R> settableFuture = SettableFuture.create();
+            ListenableFuture<Integer> versionFuture = execute(mApiVersionOperation);
+            Futures.addCallback(
+                    versionFuture,
+                    new FutureCallback<Integer>() {
+                        @Override
+                        public void onSuccess(@Nullable Integer remoteVersion) {
+                            mCurrentVersion =
+                                    remoteVersion == null ? UNKNOWN_VERSION : remoteVersion;
+                            if (mCurrentVersion < minApiVersion) {
+                                settableFuture.setException(
+                                        getApiVersionCheckFailureException(
+                                                mCurrentVersion, minApiVersion));
+                            } else {
+                                getConnectionManager()
+                                        .scheduleForExecution(
+                                                createQueueOperation(
+                                                        operation,
+                                                        getConnectionConfiguration(),
+                                                        settableFuture));
+                            }
+                        }
+
+                        @Override
+                        public void onFailure(Throwable throwable) {
+                            settableFuture.setException(throwable);
+                        }
+                    },
+                    MoreExecutors.directExecutor());
+            return settableFuture;
+        } else if (mCurrentVersion >= minApiVersion) {
+            return execute(operation);
+        } else {
+            // This empty operation is executed just to connect to the service. If we didn't connect
+            // it
+            // could happen that we won't detect change in the API version.
+            mConnectionManager.scheduleForExecution(
+                    new BaseQueueOperation(mConnectionConfiguration));
+            return Futures.immediateFailedFuture(
+                    getApiVersionCheckFailureException(mCurrentVersion, minApiVersion));
+        }
+    }
+
+    /**
+     * Registers a listener by executing the provided {@link ServiceOperation}.
+     *
+     * <p>The provided {@code registerListenerOperation} will be stored for every unique {@code
+     * listenerKey} and re-executed when connection is lost.
+     *
+     * @param listenerKey Key based on which listeners will be distinguished.
+     * @param registerListenerOperation Method that registers the listener, can by any {@link
+     *     ServiceOperation}.
+     * @param <R> Type of return value returned in the future.
+     * @return {@link ListenableFuture<R>} with the result of the operation or an exception if the
+     *     execution fails.
+     */
+    protected <R> ListenableFuture<R> registerListener(
+            ListenerKey listenerKey, ServiceOperation<R> registerListenerOperation) {
+        SettableFuture<R> settableFuture = SettableFuture.create();
+        mConnectionManager.registerListener(
+                listenerKey,
+                createQueueOperation(
+                        registerListenerOperation, mConnectionConfiguration, settableFuture));
+        return settableFuture;
+    }
+
+    /**
+     * Unregisters a listener by executing the provided {@link ServiceOperation}.
+     *
+     * @param listenerKey Key based on which listeners will be distinguished.
+     * @param unregisterListenerOperation Method that unregisters the listener, can by any {@link
+     *     ServiceOperation}.
+     * @param <R> Type of return value returned in the future.
+     * @return {@link ListenableFuture<R>} with the result of the operation or an exception if the
+     *     execution fails.
+     */
+    protected <R> ListenableFuture<R> unregisterListener(
+            ListenerKey listenerKey, ServiceOperation<R> unregisterListenerOperation) {
+        SettableFuture<R> settableFuture = SettableFuture.create();
+        mConnectionManager.unregisterListener(
+                listenerKey,
+                createQueueOperation(
+                        unregisterListenerOperation, getConnectionConfiguration(), settableFuture));
+        return settableFuture;
+    }
+
+    protected Exception getApiVersionCheckFailureException(int currentVersion, int minApiVersion) {
+        return new ApiVersionException(currentVersion, minApiVersion);
+    }
+
+    ConnectionConfiguration getConnectionConfiguration() {
+        return mConnectionConfiguration;
+    }
+
+    ConnectionManager getConnectionManager() {
+        return mConnectionManager;
+    }
+
+    private static <R> QueueOperation createQueueOperation(
+            ServiceOperation<R> operation,
+            ConnectionConfiguration connectionConfiguration,
+            SettableFuture<R> settableFuture) {
+        return new BaseQueueOperation(connectionConfiguration) {
+            @Override
+            public void execute(IBinder binder) throws RemoteException {
+                operation.execute(binder, settableFuture);
+            }
+
+            @Override
+            public void setException(Throwable exception) {
+                settableFuture.setException(exception);
+            }
+
+            @Override
+            public QueueOperation trackExecution(ExecutionTracker tracker) {
+                tracker.track(settableFuture);
+                return this;
+            }
+        };
+    }
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/impl/ipc/ClientConfiguration.java b/health/health-services-client/src/main/java/androidx/health/services/client/impl/ipc/ClientConfiguration.java
new file mode 100644
index 0000000..cf824d1
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/impl/ipc/ClientConfiguration.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 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.health.services.client.impl.ipc;
+
+import androidx.annotation.RestrictTo;
+import androidx.annotation.RestrictTo.Scope;
+
+/**
+ * An interface that provides basic information about the IPC service. This is required for building
+ * the service in {@link Client}.
+ *
+ * @hide
+ */
+@RestrictTo(Scope.LIBRARY)
+public class ClientConfiguration {
+    private final String mServicePackageName;
+    private final String mBindAction;
+    private final String mApiClientName;
+
+    public ClientConfiguration(String apiClientName, String servicePackageName, String bindAction) {
+        this.mServicePackageName = servicePackageName;
+        this.mBindAction = bindAction;
+        this.mApiClientName = apiClientName;
+    }
+
+    /** Returns the application package of the remote service. */
+    public String getServicePackageName() {
+        return mServicePackageName;
+    }
+
+    /** Returns the action used to bind to the remote service. */
+    public String getBindAction() {
+        return mBindAction;
+    }
+
+    /** Returns name of the service, use for logging and debugging only. */
+    public String getApiClientName() {
+        return mApiClientName;
+    }
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/impl/ipc/ServiceOperation.java b/health/health-services-client/src/main/java/androidx/health/services/client/impl/ipc/ServiceOperation.java
new file mode 100644
index 0000000..c15503e
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/impl/ipc/ServiceOperation.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 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.health.services.client.impl.ipc;
+
+import android.os.IBinder;
+import android.os.RemoteException;
+
+import androidx.annotation.RestrictTo;
+import androidx.annotation.RestrictTo.Scope;
+
+import com.google.common.util.concurrent.SettableFuture;
+
+/**
+ * General operation that will be executed against given service. A new operation can be created by
+ * implementing this interface. User is then responsible for setting the result Future with the
+ * result value.
+ *
+ * @param <R> Type of the returned value.
+ * @hide
+ */
+@RestrictTo(Scope.LIBRARY)
+public interface ServiceOperation<R> {
+
+    /**
+     * Method executed against the service.
+     *
+     * @param binder Already connected binder to the target service.
+     * @param resultFuture A {@link SettableFuture} that should be set with the result.
+     */
+    void execute(IBinder binder, SettableFuture<R> resultFuture) throws RemoteException;
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/impl/ipc/internal/BaseQueueOperation.java b/health/health-services-client/src/main/java/androidx/health/services/client/impl/ipc/internal/BaseQueueOperation.java
new file mode 100644
index 0000000..079d509
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/impl/ipc/internal/BaseQueueOperation.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 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.health.services.client.impl.ipc.internal;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import android.os.IBinder;
+import android.os.RemoteException;
+
+import androidx.annotation.RestrictTo;
+import androidx.annotation.RestrictTo.Scope;
+
+/**
+ * Abstract implementation of QueueOperation that accepts {@link ConnectionConfiguration} describing
+ * the service where it will be executed.
+ *
+ * @hide
+ */
+@RestrictTo(Scope.LIBRARY)
+public class BaseQueueOperation implements QueueOperation {
+    private final ConnectionConfiguration mConnectionConfiguration;
+
+    public BaseQueueOperation(ConnectionConfiguration connectionConfiguration) {
+        this.mConnectionConfiguration = checkNotNull(connectionConfiguration);
+    }
+
+    @Override
+    public void execute(IBinder binder) throws RemoteException {}
+
+    @Override
+    public void setException(Throwable exception) {}
+
+    @Override
+    public QueueOperation trackExecution(ExecutionTracker tracker) {
+        return this;
+    }
+
+    /** Configuration of the service connection on which the operation will be executed. */
+    @Override
+    public ConnectionConfiguration getConnectionConfiguration() {
+        return mConnectionConfiguration;
+    }
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/impl/ipc/internal/ConnectionConfiguration.java b/health/health-services-client/src/main/java/androidx/health/services/client/impl/ipc/internal/ConnectionConfiguration.java
new file mode 100644
index 0000000..9b6c42b
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/impl/ipc/internal/ConnectionConfiguration.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 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.health.services.client.impl.ipc.internal;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import androidx.annotation.RestrictTo;
+import androidx.annotation.RestrictTo.Scope;
+import androidx.health.services.client.impl.ipc.ClientConfiguration;
+
+/**
+ * Internal representation of configuration of IPC service connection.
+ *
+ * @hide
+ */
+@RestrictTo(Scope.LIBRARY)
+public final class ConnectionConfiguration {
+    private final String mPackageName;
+    private final String mClientName;
+    private final String mBindAction;
+    private final QueueOperation mRefreshVersionOperation;
+
+    public ConnectionConfiguration(
+            String packageName,
+            String clientName,
+            String bindAction,
+            QueueOperation refreshVersionOperation) {
+        this.mPackageName = checkNotNull(packageName);
+        this.mClientName = checkNotNull(clientName);
+        this.mBindAction = checkNotNull(bindAction);
+        this.mRefreshVersionOperation = checkNotNull(refreshVersionOperation);
+    }
+
+    /** A key that defines the connection among other IPC connections. It should be unique. */
+    String getKey() {
+        return String.format("%s#%s#%s", mClientName, mPackageName, mBindAction);
+    }
+
+    /** API Client name defined in the {@link ClientConfiguration#getApiClientName()}. */
+    String getClientName() {
+        return mClientName;
+    }
+
+    /**
+     * An action used to bind to the remote service. Taken from {@link
+     * ClientConfiguration#getBindAction()}.
+     */
+    String getBindAction() {
+        return mBindAction;
+    }
+
+    /**
+     * Package name of remote service. Taken from {@link
+     * ClientConfiguration#getServicePackageName()}.
+     */
+    String getPackageName() {
+        return mPackageName;
+    }
+
+    QueueOperation getRefreshVersionOperation() {
+        return mRefreshVersionOperation;
+    }
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/impl/ipc/internal/ConnectionManager.java b/health/health-services-client/src/main/java/androidx/health/services/client/impl/ipc/internal/ConnectionManager.java
new file mode 100644
index 0000000..739a531
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/impl/ipc/internal/ConnectionManager.java
@@ -0,0 +1,184 @@
+/*
+ * Copyright (C) 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.health.services.client.impl.ipc.internal;
+
+import android.content.Context;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.util.Log;
+
+import androidx.annotation.RestrictTo;
+import androidx.annotation.RestrictTo.Scope;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Manages connections to a service in a different process.
+ *
+ * @hide
+ */
+@RestrictTo(Scope.LIBRARY)
+public final class ConnectionManager implements Handler.Callback, ServiceConnection.Callback {
+    private static final String TAG = "ConnectionManager";
+
+    private static final int MSG_CONNECTED = 1;
+    private static final int MSG_DISCONNECTED = 2;
+    private static final int MSG_EXECUTE = 3;
+    private static final int MSG_REGISTER_LISTENER = 4;
+    private static final int MSG_UNREGISTER_LISTENER = 5;
+
+    private final Context mContext;
+    private final Handler mHandler;
+    private final Map<String, ServiceConnection> mServiceConnectionMap = new HashMap<>();
+
+    private boolean mBindToSelfEnabled;
+
+    public ConnectionManager(Context context, Looper looper) {
+        this.mContext = context;
+        this.mHandler = new Handler(looper, this);
+    }
+
+    /**
+     * Schedules operation for execution
+     *
+     * @param operation Operation prepared for scheduling on the connection queue.
+     */
+    public void scheduleForExecution(QueueOperation operation) {
+        mHandler.sendMessage(mHandler.obtainMessage(MSG_EXECUTE, operation));
+    }
+
+    /**
+     * Registers a listener by executing an operation represented by the {@link QueueOperation}.
+     *
+     * @param listenerKey Key based on which listeners will be distinguished.
+     * @param registerOperation Queue operation executed against the corresponding connection to
+     *     register the listener. Will be used to re-register when connection is lost.
+     */
+    public void registerListener(ListenerKey listenerKey, QueueOperation registerOperation) {
+        mHandler.sendMessage(
+                mHandler.obtainMessage(
+                        MSG_REGISTER_LISTENER, new ListenerHolder(listenerKey, registerOperation)));
+    }
+
+    /**
+     * Unregisters a listener by executing an operation represented by the {@link QueueOperation}.
+     *
+     * @param listenerKey Key based on which listeners will be distinguished.
+     * @param unregisterOperation Queue operation executed against the corresponding connection to
+     *     unregister the listener.
+     */
+    public void unregisterListener(ListenerKey listenerKey, QueueOperation unregisterOperation) {
+        mHandler.sendMessage(
+                mHandler.obtainMessage(
+                        MSG_UNREGISTER_LISTENER,
+                        new ListenerHolder(listenerKey, unregisterOperation)));
+    }
+
+    @Override
+    public void onConnected(ServiceConnection connection) {
+        mHandler.sendMessage(mHandler.obtainMessage(MSG_CONNECTED, connection));
+    }
+
+    @Override
+    public void onDisconnected(ServiceConnection connection, long reconnectDelayMs) {
+        mHandler.sendMessageDelayed(
+                mHandler.obtainMessage(MSG_DISCONNECTED, connection), reconnectDelayMs);
+    }
+
+    @Override
+    public boolean isBindToSelfEnabled() {
+        return mBindToSelfEnabled;
+    }
+
+    @Override
+    public boolean handleMessage(Message msg) {
+        switch (msg.what) {
+            case MSG_CONNECTED:
+                ServiceConnection serviceConnection = ((ServiceConnection) msg.obj);
+                serviceConnection.reRegisterAllListeners();
+                serviceConnection.refreshServiceVersion();
+                serviceConnection.flushQueue();
+                return true;
+            case MSG_DISCONNECTED:
+                ((ServiceConnection) msg.obj).maybeReconnect();
+                return true;
+            case MSG_EXECUTE:
+                QueueOperation queueOperation = (QueueOperation) msg.obj;
+                getConnection(queueOperation.getConnectionConfiguration()).enqueue(queueOperation);
+                return true;
+            case MSG_REGISTER_LISTENER:
+                ListenerHolder registerListenerHolder = (ListenerHolder) msg.obj;
+                getConnection(
+                                registerListenerHolder
+                                        .getListenerOperation()
+                                        .getConnectionConfiguration())
+                        .registerListener(
+                                registerListenerHolder.getListenerKey(),
+                                registerListenerHolder.getListenerOperation());
+                return true;
+            case MSG_UNREGISTER_LISTENER:
+                ListenerHolder unregisterListenerHolder = (ListenerHolder) msg.obj;
+                getConnection(
+                                unregisterListenerHolder
+                                        .getListenerOperation()
+                                        .getConnectionConfiguration())
+                        .unregisterListener(
+                                unregisterListenerHolder.getListenerKey(),
+                                unregisterListenerHolder.getListenerOperation());
+                return true;
+            default:
+                Log.e(TAG, "Received unknown message: " + msg.what);
+                return false;
+        }
+    }
+
+    public void setBindToSelf(boolean bindToSelfEnabled) {
+        this.mBindToSelfEnabled = bindToSelfEnabled;
+    }
+
+    private ServiceConnection getConnection(ConnectionConfiguration connectionConfiguration) {
+        String connectionKey = connectionConfiguration.getKey();
+        ServiceConnection serviceConnection = mServiceConnectionMap.get(connectionKey);
+        if (serviceConnection == null) {
+            serviceConnection =
+                    new ServiceConnection(
+                            mContext, connectionConfiguration, new DefaultExecutionTracker(), this);
+            mServiceConnectionMap.put(connectionKey, serviceConnection);
+        }
+        return serviceConnection;
+    }
+
+    private static class ListenerHolder {
+        private final ListenerKey mListenerKey;
+        private final QueueOperation mListenerOperation;
+
+        ListenerHolder(ListenerKey listenerKey, QueueOperation listenerOperation) {
+            this.mListenerKey = listenerKey;
+            this.mListenerOperation = listenerOperation;
+        }
+
+        ListenerKey getListenerKey() {
+            return mListenerKey;
+        }
+
+        QueueOperation getListenerOperation() {
+            return mListenerOperation;
+        }
+    }
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/impl/ipc/internal/DefaultExecutionTracker.java b/health/health-services-client/src/main/java/androidx/health/services/client/impl/ipc/internal/DefaultExecutionTracker.java
new file mode 100644
index 0000000..c8b527e
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/impl/ipc/internal/DefaultExecutionTracker.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 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.health.services.client.impl.ipc.internal;
+
+import androidx.annotation.RestrictTo;
+import androidx.annotation.RestrictTo.Scope;
+
+import com.google.common.util.concurrent.MoreExecutors;
+import com.google.common.util.concurrent.SettableFuture;
+
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * Default implementation of {@link ExecutionTracker}.
+ *
+ * @hide
+ */
+@SuppressWarnings("ExecutorTaskName")
+@RestrictTo(Scope.LIBRARY)
+public class DefaultExecutionTracker implements ExecutionTracker {
+    private final Set<SettableFuture<?>> mFuturesInProgress = new HashSet<>();
+
+    @Override
+    public void track(SettableFuture<?> future) {
+        mFuturesInProgress.add(future);
+        future.addListener(() -> mFuturesInProgress.remove(future), MoreExecutors.directExecutor());
+    }
+
+    @Override
+    public void cancelPendingFutures(Throwable throwable) {
+        for (SettableFuture<?> future : mFuturesInProgress) {
+            future.setException(throwable);
+        }
+        mFuturesInProgress.clear();
+    }
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/impl/ipc/internal/ExecutionTracker.java b/health/health-services-client/src/main/java/androidx/health/services/client/impl/ipc/internal/ExecutionTracker.java
new file mode 100644
index 0000000..eb9e81d
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/impl/ipc/internal/ExecutionTracker.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 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.health.services.client.impl.ipc.internal;
+
+import androidx.annotation.RestrictTo;
+import androidx.annotation.RestrictTo.Scope;
+
+import com.google.common.util.concurrent.SettableFuture;
+
+/**
+ * Tracker for tracking operations that are currently in progress.
+ *
+ * @hide
+ */
+@RestrictTo(Scope.LIBRARY)
+public interface ExecutionTracker {
+
+    /** Track given future as in progress. */
+    void track(SettableFuture<?> future);
+
+    /** Cancel all tracked futures with given exception. */
+    void cancelPendingFutures(Throwable throwable);
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/impl/ipc/internal/ListenerKey.java b/health/health-services-client/src/main/java/androidx/health/services/client/impl/ipc/internal/ListenerKey.java
new file mode 100644
index 0000000..f2960e3
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/impl/ipc/internal/ListenerKey.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 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.health.services.client.impl.ipc.internal;
+
+import androidx.annotation.Nullable;
+import androidx.annotation.RestrictTo;
+import androidx.annotation.RestrictTo.Scope;
+
+/**
+ * Unique key to hold listener reference.
+ *
+ * @hide
+ */
+@RestrictTo(Scope.LIBRARY)
+public final class ListenerKey {
+    private final Object mListenerKey;
+
+    public ListenerKey(Object listenerKey) {
+        this.mListenerKey = listenerKey;
+    }
+
+    @Override
+    public boolean equals(@Nullable Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (!(o instanceof ListenerKey)) {
+            return false;
+        }
+
+        ListenerKey that = (ListenerKey) o;
+        return mListenerKey.equals(that);
+    }
+
+    @Override
+    public int hashCode() {
+        return System.identityHashCode(mListenerKey);
+    }
+
+    @Override
+    public String toString() {
+        return String.valueOf(mListenerKey);
+    }
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/impl/ipc/internal/QueueOperation.java b/health/health-services-client/src/main/java/androidx/health/services/client/impl/ipc/internal/QueueOperation.java
new file mode 100644
index 0000000..6021bdf
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/impl/ipc/internal/QueueOperation.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 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.health.services.client.impl.ipc.internal;
+
+import android.os.IBinder;
+import android.os.RemoteException;
+
+import androidx.annotation.RestrictTo;
+import androidx.annotation.RestrictTo.Scope;
+
+/**
+ * A wrapper for SDK operation that will be executed on a connected binder. It is intended for
+ * scheduling in execution queue.
+ *
+ * @hide
+ */
+@RestrictTo(Scope.LIBRARY)
+public interface QueueOperation {
+    /**
+     * Method executed against the service.
+     *
+     * @param binder already connected to the target service.
+     */
+    void execute(IBinder binder) throws RemoteException;
+
+    /** Sets exception as the result of the operation. */
+    void setException(Throwable exception);
+
+    /**
+     * Tracks the operation execution with an {@link ExecutionTracker}.
+     *
+     * @param tracker To track the execution as in progress.
+     */
+    QueueOperation trackExecution(ExecutionTracker tracker);
+
+    /** Returns configuration of the service connection on which the operation will be executed. */
+    ConnectionConfiguration getConnectionConfiguration();
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/impl/ipc/internal/ServiceConnection.java b/health/health-services-client/src/main/java/androidx/health/services/client/impl/ipc/internal/ServiceConnection.java
new file mode 100644
index 0000000..fa432bc
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/impl/ipc/internal/ServiceConnection.java
@@ -0,0 +1,341 @@
+/*
+ * Copyright (C) 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.health.services.client.impl.ipc.internal;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.os.DeadObjectException;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Log;
+
+import androidx.annotation.Nullable;
+import androidx.annotation.RestrictTo;
+import androidx.annotation.RestrictTo.Scope;
+import androidx.annotation.VisibleForTesting;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Queue;
+import java.util.concurrent.CancellationException;
+import java.util.concurrent.ConcurrentLinkedQueue;
+
+import javax.annotation.concurrent.NotThreadSafe;
+
+/**
+ * A class that maintains a connection to IPC backend service. If connection is not available it
+ * uses a queue to store service requests until connection is renewed. One {@link ServiceConnection}
+ * is associated with one AIDL file .
+ *
+ * <p>Note: this class is not thread safe and should be called always from the same thread.
+ *
+ * @hide
+ */
+@NotThreadSafe
+@RestrictTo(Scope.LIBRARY)
+public class ServiceConnection implements android.content.ServiceConnection {
+    private static final String TAG = "ServiceConnection";
+
+    /** Callback for reporting back to the manager. */
+    public interface Callback {
+
+        /** Called when the connection to the server was successfully established. */
+        void onConnected(ServiceConnection connection);
+
+        /**
+         * Called when the connection to the server was lost.
+         *
+         * @param connection Represents this connection to a service.
+         * @param reconnectDelayMs Delay before the caller should try to reconnect this connection.
+         */
+        void onDisconnected(ServiceConnection connection, long reconnectDelayMs);
+
+        /**
+         * Return true if the {@link ServiceConnection} should bind to the service in the same
+         * application for testing reason.
+         */
+        boolean isBindToSelfEnabled();
+    }
+
+    private static final int MAX_RETRIES = 10;
+
+    private final Context mContext;
+    private final Queue<QueueOperation> mOperationQueue = new ConcurrentLinkedQueue<>();
+    private final ConnectionConfiguration mConnectionConfiguration;
+    private final ExecutionTracker mExecutionTracker;
+    private final Map<ListenerKey, QueueOperation> mRegisteredListeners = new HashMap<>();
+    private final Callback mCallback;
+
+    @VisibleForTesting @Nullable IBinder mBinder;
+    private volatile boolean mIsServiceBound;
+    /** Denotes how many times connection to the service failed and we retried. */
+    private int mServiceConnectionRetry;
+
+    ServiceConnection(
+            Context context,
+            ConnectionConfiguration connectionConfiguration,
+            ExecutionTracker executionTracker,
+            Callback callback) {
+        this.mContext = checkNotNull(context);
+        this.mConnectionConfiguration = checkNotNull(connectionConfiguration);
+        this.mExecutionTracker = checkNotNull(executionTracker);
+        this.mCallback = checkNotNull(callback);
+    }
+
+    private String getBindPackageName() {
+        if (mCallback.isBindToSelfEnabled()) {
+            return mContext.getPackageName();
+        } else {
+            return mConnectionConfiguration.getPackageName();
+        }
+    }
+
+    /** Connects to the service. */
+    public void connect() {
+        if (mIsServiceBound) {
+            return;
+        }
+        try {
+            mIsServiceBound =
+                    mContext.bindService(
+                            new Intent()
+                                    .setPackage(getBindPackageName())
+                                    .setAction(mConnectionConfiguration.getBindAction()),
+                            this,
+                            Context.BIND_AUTO_CREATE | Context.BIND_ADJUST_WITH_ACTIVITY);
+        } catch (SecurityException exception) {
+            Log.w(
+                    TAG,
+                    "Failed to bind connection '"
+                            + mConnectionConfiguration.getKey()
+                            + "', no permission or service not found.",
+                    exception);
+            mIsServiceBound = false;
+            mBinder = null;
+            throw exception;
+        }
+
+        if (!mIsServiceBound) {
+            // Service not found or we don't have permission to call it.
+            Log.e(
+                    TAG,
+                    "Connection to service is not available for package '"
+                            + mConnectionConfiguration.getPackageName()
+                            + "' and action '"
+                            + mConnectionConfiguration.getBindAction()
+                            + "'.");
+            handleNonRetriableDisconnection(new CancellationException("Service not available"));
+        }
+    }
+
+    private void handleNonRetriableDisconnection(Throwable throwable) {
+        // Set retry count to maximum to prevent retries
+        mServiceConnectionRetry = MAX_RETRIES;
+        handleRetriableDisconnection(throwable);
+    }
+
+    private synchronized void handleRetriableDisconnection(Throwable throwable) {
+        if (isConnected()) {
+            // Connection is already re-established. So just return.
+            Log.w(TAG, "Connection is already re-established. No need to reconnect again");
+            return;
+        }
+
+        clearConnection(throwable);
+
+        if (mServiceConnectionRetry < MAX_RETRIES) {
+            Log.w(
+                    TAG,
+                    "WCS SDK Client '"
+                            + mConnectionConfiguration.getClientName()
+                            + "' disconnected, retrying connection. Retry attempt: "
+                            + mServiceConnectionRetry,
+                    throwable);
+            mCallback.onDisconnected(this, getRetryDelayMs(mServiceConnectionRetry));
+        } else {
+            Log.e(TAG, "Connection disconnected and maximum number of retries reached.", throwable);
+        }
+    }
+
+    private static int getRetryDelayMs(int retryNumber) {
+        // Exponential retry delay starting on 200ms.
+        return (200 << retryNumber);
+    }
+
+    private void clearConnection(Throwable throwable) {
+        if (mIsServiceBound) {
+            try {
+                mContext.unbindService(this);
+                mIsServiceBound = false;
+            } catch (IllegalArgumentException e) {
+                Log.e(TAG, "Failed to unbind the service. Ignoring and continuing", e);
+            }
+        }
+
+        mBinder = null;
+        mExecutionTracker.cancelPendingFutures(throwable);
+        cancelAllOperationsInQueue(throwable);
+    }
+
+    void enqueue(QueueOperation operation) {
+        if (isConnected()) {
+            execute(operation);
+        } else {
+            mOperationQueue.add(operation);
+            connect();
+        }
+    }
+
+    void registerListener(ListenerKey listenerKey, QueueOperation registerListenerOperation) {
+        mRegisteredListeners.put(listenerKey, registerListenerOperation);
+        if (isConnected()) {
+            enqueue(registerListenerOperation);
+        } else {
+            connect();
+        }
+    }
+
+    void unregisterListener(ListenerKey listenerKey, QueueOperation unregisterListenerOperation) {
+        mRegisteredListeners.remove(listenerKey);
+        enqueue(unregisterListenerOperation);
+    }
+
+    void maybeReconnect() {
+        if (mRegisteredListeners.isEmpty()) {
+            Log.d(
+                    TAG,
+                    "No listeners registered, service "
+                            + mConnectionConfiguration.getClientName()
+                            + " is not automatically reconnected.");
+        } else {
+            mServiceConnectionRetry++;
+            Log.d(
+                    TAG,
+                    "Listeners for service "
+                            + mConnectionConfiguration.getClientName()
+                            + " are registered, reconnecting.");
+            connect();
+        }
+    }
+
+    @VisibleForTesting
+    void execute(QueueOperation operation) {
+        try {
+            operation.trackExecution(mExecutionTracker);
+            operation.execute(checkNotNull(mBinder));
+        } catch (DeadObjectException exception) {
+            handleRetriableDisconnection(exception);
+            // TODO(b/152024821): Consider possible TransactionTooLargeException failure.
+        } catch (RemoteException | RuntimeException exception) {
+            operation.setException(exception);
+        }
+    }
+
+    void reRegisterAllListeners() {
+        for (Map.Entry<ListenerKey, QueueOperation> entry : mRegisteredListeners.entrySet()) {
+            Log.d(TAG, "Re-registering listener: " + entry.getKey());
+            execute(entry.getValue());
+        }
+    }
+
+    void refreshServiceVersion() {
+        mOperationQueue.add(mConnectionConfiguration.getRefreshVersionOperation());
+    }
+
+    void flushQueue() {
+        for (QueueOperation operation : new ArrayList<>(mOperationQueue)) {
+            boolean removed = mOperationQueue.remove(operation);
+            if (removed) {
+                execute(operation);
+            }
+        }
+    }
+
+    private void cancelAllOperationsInQueue(Throwable throwable) {
+        for (QueueOperation operation : new ArrayList<>(mOperationQueue)) {
+            boolean removed = mOperationQueue.remove(operation);
+            if (removed) {
+                operation.setException(throwable);
+                execute(operation);
+            }
+        }
+    }
+
+    private boolean isConnected() {
+        return mBinder != null && mBinder.isBinderAlive();
+    }
+
+    @Override
+    public void onServiceConnected(ComponentName componentName, IBinder binder) {
+        Log.d(TAG, "onServiceConnected(), componentName = " + componentName);
+        if (binder == null) {
+            Log.e(TAG, "Service connected but binder is null.");
+            return;
+        }
+        mServiceConnectionRetry = 0;
+        cleanOnDeath(binder);
+        this.mBinder = binder;
+        mCallback.onConnected(this);
+    }
+
+    private void cleanOnDeath(IBinder binder) {
+        try {
+            binder.linkToDeath(
+                    () -> {
+                        Log.w(
+                                TAG,
+                                "Binder died for client:"
+                                        + mConnectionConfiguration.getClientName());
+                        handleRetriableDisconnection(new CancellationException());
+                    },
+                    /* flags= */ 0);
+        } catch (RemoteException exception) {
+            Log.w(
+                    TAG,
+                    "Cannot link to death, binder already died. Cleaning operations.",
+                    exception);
+            handleRetriableDisconnection(exception);
+        }
+    }
+
+    @Override
+    public void onServiceDisconnected(ComponentName componentName) {
+        Log.d(TAG, "onServiceDisconnected(), componentName = " + componentName);
+        // Service disconnected but binding still exists so it should reconnect automatically.
+    }
+
+    @Override
+    public void onBindingDied(ComponentName name) {
+        Log.e(TAG, "Binding died for client '" + mConnectionConfiguration.getClientName() + "'.");
+        handleRetriableDisconnection(new CancellationException());
+    }
+
+    @Override
+    public void onNullBinding(ComponentName name) {
+        Log.e(
+                TAG,
+                "Cannot bind client '"
+                        + mConnectionConfiguration.getClientName()
+                        + "', binder is null");
+        // This connection will never be usable, don't bother with retries.
+        handleNonRetriableDisconnection(new CancellationException("Null binding"));
+    }
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/impl/request/AutoPauseAndResumeConfigRequest.kt b/health/health-services-client/src/main/java/androidx/health/services/client/impl/request/AutoPauseAndResumeConfigRequest.kt
new file mode 100644
index 0000000..6fc9161
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/impl/request/AutoPauseAndResumeConfigRequest.kt
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 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.health.services.client.impl.request
+
+import android.os.Parcel
+import android.os.Parcelable
+
+/**
+ * Request for enabling/disabling auto pause/resume.
+ *
+ * @hide
+ */
+public data class AutoPauseAndResumeConfigRequest(
+    val packageName: String,
+    val shouldEnable: Boolean,
+) : Parcelable {
+    override fun describeContents(): Int = 0
+
+    override fun writeToParcel(dest: Parcel, flags: Int) {
+        dest.writeString(packageName)
+        dest.writeInt(if (shouldEnable) 1 else 0)
+    }
+
+    public companion object {
+        @JvmField
+        public val CREATOR: Parcelable.Creator<AutoPauseAndResumeConfigRequest> =
+            object : Parcelable.Creator<AutoPauseAndResumeConfigRequest> {
+                override fun createFromParcel(source: Parcel): AutoPauseAndResumeConfigRequest? {
+                    return AutoPauseAndResumeConfigRequest(
+                        source.readString() ?: return null,
+                        source.readInt() == 1,
+                    )
+                }
+
+                override fun newArray(size: Int): Array<AutoPauseAndResumeConfigRequest?> {
+                    return arrayOfNulls(size)
+                }
+            }
+    }
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/impl/request/BackgroundRegistrationRequest.kt b/health/health-services-client/src/main/java/androidx/health/services/client/impl/request/BackgroundRegistrationRequest.kt
new file mode 100644
index 0000000..2c0296a
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/impl/request/BackgroundRegistrationRequest.kt
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 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.health.services.client.impl.request
+
+import android.os.Parcel
+import android.os.Parcelable
+import androidx.health.services.client.data.DataType
+
+/**
+ * Request for background registration.
+ *
+ * @hide
+ */
+public data class BackgroundRegistrationRequest(
+    val packageName: String,
+    val dataTypes: Set<DataType>,
+) : Parcelable {
+    override fun describeContents(): Int = 0
+
+    override fun writeToParcel(dest: Parcel, flags: Int) {
+        dest.writeString(packageName)
+        dest.writeTypedList(dataTypes.toList())
+    }
+
+    public companion object {
+        @JvmField
+        public val CREATOR: Parcelable.Creator<BackgroundRegistrationRequest> =
+            object : Parcelable.Creator<BackgroundRegistrationRequest> {
+                override fun createFromParcel(source: Parcel): BackgroundRegistrationRequest? {
+                    val packageName = source.readString() ?: return null
+                    val list = ArrayList<DataType>()
+                    source.readTypedList(list, DataType.CREATOR)
+                    return BackgroundRegistrationRequest(packageName, list.toSet())
+                }
+
+                override fun newArray(size: Int): Array<BackgroundRegistrationRequest?> {
+                    return arrayOfNulls(size)
+                }
+            }
+    }
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/impl/request/BackgroundUnregistrationRequest.kt b/health/health-services-client/src/main/java/androidx/health/services/client/impl/request/BackgroundUnregistrationRequest.kt
new file mode 100644
index 0000000..67fcaa8
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/impl/request/BackgroundUnregistrationRequest.kt
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 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.health.services.client.impl.request
+
+import android.os.Parcel
+import android.os.Parcelable
+
+/**
+ * Request for background unregistration.
+ *
+ * @hide
+ */
+public data class BackgroundUnregistrationRequest(val packageName: String) : Parcelable {
+
+    override fun describeContents(): Int = 0
+
+    override fun writeToParcel(dest: Parcel, flags: Int) {
+        dest.writeString(packageName)
+    }
+
+    public companion object {
+        @JvmField
+        public val CREATOR: Parcelable.Creator<BackgroundUnregistrationRequest> =
+            object : Parcelable.Creator<BackgroundUnregistrationRequest> {
+                override fun createFromParcel(source: Parcel): BackgroundUnregistrationRequest? {
+                    val packageName = source.readString() ?: return null
+                    return BackgroundUnregistrationRequest(packageName)
+                }
+
+                override fun newArray(size: Int): Array<BackgroundUnregistrationRequest?> {
+                    return arrayOfNulls(size)
+                }
+            }
+    }
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/impl/request/CapabilitiesRequest.kt b/health/health-services-client/src/main/java/androidx/health/services/client/impl/request/CapabilitiesRequest.kt
new file mode 100644
index 0000000..ba837e4
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/impl/request/CapabilitiesRequest.kt
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 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.health.services.client.impl.request
+
+import android.os.Parcel
+import android.os.Parcelable
+
+/**
+ * Request for capabilities.
+ *
+ * @hide
+ */
+public data class CapabilitiesRequest(val packageName: String) : Parcelable {
+
+    override fun describeContents(): Int = 0
+
+    override fun writeToParcel(dest: Parcel, flags: Int) {
+        dest.writeString(packageName)
+    }
+
+    public companion object {
+        @JvmField
+        public val CREATOR: Parcelable.Creator<CapabilitiesRequest> =
+            object : Parcelable.Creator<CapabilitiesRequest> {
+                override fun createFromParcel(source: Parcel): CapabilitiesRequest? {
+                    val packageName = source.readString() ?: return null
+                    return CapabilitiesRequest(packageName)
+                }
+
+                override fun newArray(size: Int): Array<CapabilitiesRequest?> {
+                    return arrayOfNulls(size)
+                }
+            }
+    }
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/impl/request/EventRequest.kt b/health/health-services-client/src/main/java/androidx/health/services/client/impl/request/EventRequest.kt
new file mode 100644
index 0000000..ddfce39
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/impl/request/EventRequest.kt
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 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.health.services.client.impl.request
+
+import android.os.Parcel
+import android.os.Parcelable
+import androidx.health.services.client.data.event.Event
+
+/**
+ * Request for event registration.
+ *
+ * @hide
+ */
+public data class EventRequest(val packageName: String, val event: Event) : Parcelable {
+    override fun describeContents(): Int = 0
+
+    override fun writeToParcel(dest: Parcel, flags: Int) {
+        dest.writeString(packageName)
+        dest.writeParcelable(event, flags)
+    }
+
+    public companion object {
+        @JvmField
+        public val CREATOR: Parcelable.Creator<EventRequest> =
+            object : Parcelable.Creator<EventRequest> {
+                override fun createFromParcel(source: Parcel): EventRequest? {
+                    return EventRequest(
+                        source.readString() ?: return null,
+                        source.readParcelable(Event::class.java.classLoader) ?: return null,
+                    )
+                }
+
+                override fun newArray(size: Int): Array<EventRequest?> {
+                    return arrayOfNulls(size)
+                }
+            }
+    }
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/impl/request/ExerciseGoalRequest.kt b/health/health-services-client/src/main/java/androidx/health/services/client/impl/request/ExerciseGoalRequest.kt
new file mode 100644
index 0000000..a427c67
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/impl/request/ExerciseGoalRequest.kt
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 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.health.services.client.impl.request
+
+import android.os.Parcel
+import android.os.Parcelable
+import androidx.health.services.client.data.ExerciseGoal
+
+/**
+ * Request for adding a [ExerciseGoal] to an exercise.
+ *
+ * @hide
+ */
+public data class ExerciseGoalRequest(val packageName: String, val exerciseGoal: ExerciseGoal) :
+    Parcelable {
+    override fun describeContents(): Int = 0
+
+    override fun writeToParcel(dest: Parcel, flags: Int) {
+        dest.writeString(packageName)
+        dest.writeParcelable(exerciseGoal, flags)
+    }
+
+    public companion object {
+        @JvmField
+        public val CREATOR: Parcelable.Creator<ExerciseGoalRequest> =
+            object : Parcelable.Creator<ExerciseGoalRequest> {
+                override fun createFromParcel(source: Parcel): ExerciseGoalRequest? {
+                    return ExerciseGoalRequest(
+                        source.readString() ?: return null,
+                        source.readParcelable(ExerciseGoal::class.java.classLoader) ?: return null,
+                    )
+                }
+
+                override fun newArray(size: Int): Array<ExerciseGoalRequest?> {
+                    return arrayOfNulls(size)
+                }
+            }
+    }
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/impl/request/MeasureRegistrationRequest.kt b/health/health-services-client/src/main/java/androidx/health/services/client/impl/request/MeasureRegistrationRequest.kt
new file mode 100644
index 0000000..2d88026
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/impl/request/MeasureRegistrationRequest.kt
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 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.health.services.client.impl.request
+
+import android.os.Parcel
+import android.os.Parcelable
+import androidx.health.services.client.data.DataType
+
+/**
+ * Request for measure registration.
+ *
+ * @hide
+ */
+public data class MeasureRegistrationRequest(
+    val packageName: String,
+    val dataType: DataType,
+) : Parcelable {
+    override fun describeContents(): Int = 0
+
+    override fun writeToParcel(dest: Parcel, flags: Int) {
+        dest.writeString(packageName)
+        dest.writeParcelable(dataType, flags)
+    }
+
+    public companion object {
+        @JvmField
+        public val CREATOR: Parcelable.Creator<MeasureRegistrationRequest> =
+            object : Parcelable.Creator<MeasureRegistrationRequest> {
+                override fun createFromParcel(source: Parcel): MeasureRegistrationRequest? {
+                    return MeasureRegistrationRequest(
+                        source.readString() ?: return null,
+                        source.readParcelable(DataType::class.java.classLoader) ?: return null
+                    )
+                }
+
+                override fun newArray(size: Int): Array<MeasureRegistrationRequest?> {
+                    return arrayOfNulls(size)
+                }
+            }
+    }
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/impl/request/MeasureUnregistrationRequest.kt b/health/health-services-client/src/main/java/androidx/health/services/client/impl/request/MeasureUnregistrationRequest.kt
new file mode 100644
index 0000000..55e8054
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/impl/request/MeasureUnregistrationRequest.kt
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 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.health.services.client.impl.request
+
+import android.os.Parcel
+import android.os.Parcelable
+import androidx.health.services.client.data.DataType
+
+/**
+ * Request for measure unregistration.
+ *
+ * @hide
+ */
+public data class MeasureUnregistrationRequest(
+    val packageName: String,
+    val dataType: DataType,
+) : Parcelable {
+    override fun describeContents(): Int = 0
+
+    override fun writeToParcel(dest: Parcel, flags: Int) {
+        dest.writeString(packageName)
+        dest.writeParcelable(dataType, flags)
+    }
+
+    public companion object {
+        @JvmField
+        public val CREATOR: Parcelable.Creator<MeasureUnregistrationRequest> =
+            object : Parcelable.Creator<MeasureUnregistrationRequest> {
+                override fun createFromParcel(source: Parcel): MeasureUnregistrationRequest? {
+                    val packageName = source.readString() ?: return null
+                    val dataType =
+                        source.readParcelable<DataType>(DataType::class.java.classLoader)
+                            ?: return null
+                    return MeasureUnregistrationRequest(packageName, dataType)
+                }
+
+                override fun newArray(size: Int): Array<MeasureUnregistrationRequest?> {
+                    return arrayOfNulls(size)
+                }
+            }
+    }
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/impl/request/StartExerciseRequest.kt b/health/health-services-client/src/main/java/androidx/health/services/client/impl/request/StartExerciseRequest.kt
new file mode 100644
index 0000000..d67b359
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/impl/request/StartExerciseRequest.kt
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 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.health.services.client.impl.request
+
+import android.os.Parcel
+import android.os.Parcelable
+import androidx.health.services.client.data.ExerciseConfig
+
+/**
+ * Request for starting an exercise.
+ *
+ * @hide
+ */
+public data class StartExerciseRequest(
+    val packageName: String,
+    val exerciseConfig: ExerciseConfig,
+) : Parcelable {
+    override fun describeContents(): Int = 0
+
+    override fun writeToParcel(dest: Parcel, flags: Int) {
+        dest.writeString(packageName)
+        dest.writeParcelable(exerciseConfig, flags)
+    }
+
+    public companion object {
+        @JvmField
+        public val CREATOR: Parcelable.Creator<StartExerciseRequest> =
+            object : Parcelable.Creator<StartExerciseRequest> {
+                override fun createFromParcel(source: Parcel): StartExerciseRequest? {
+                    val packageName = source.readString() ?: return null
+                    val parcelable =
+                        source.readParcelable<ExerciseConfig>(
+                            ExerciseConfig::class.java.classLoader
+                        )
+                            ?: return null
+                    return StartExerciseRequest(packageName, parcelable)
+                }
+
+                override fun newArray(size: Int): Array<StartExerciseRequest?> {
+                    return arrayOfNulls(size)
+                }
+            }
+    }
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/impl/response/AvailabilityResponse.kt b/health/health-services-client/src/main/java/androidx/health/services/client/impl/response/AvailabilityResponse.kt
new file mode 100644
index 0000000..3a3ccf6
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/impl/response/AvailabilityResponse.kt
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 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.health.services.client.impl.response
+
+import android.os.Parcel
+import android.os.Parcelable
+import androidx.health.services.client.data.Availability
+import androidx.health.services.client.data.DataType
+
+/**
+ * Response sent on MeasureCallback with a [DataType] and its associated [Availability] status.
+ *
+ * @hide
+ */
+public data class AvailabilityResponse(
+    /** [DataType] of the [AvailabilityResponse]. */
+    val dataType: DataType,
+    /** [Availability] of the [AvailabilityResponse]. */
+    val availability: Availability,
+) : Parcelable {
+    override fun describeContents(): Int = 0
+
+    override fun writeToParcel(dest: Parcel, flags: Int) {
+        dest.writeParcelable(dataType, flags)
+        dest.writeInt(availability.id)
+    }
+
+    public companion object {
+        @JvmField
+        public val CREATOR: Parcelable.Creator<AvailabilityResponse> =
+            object : Parcelable.Creator<AvailabilityResponse> {
+                override fun createFromParcel(source: Parcel): AvailabilityResponse? {
+                    val parcelable =
+                        source.readParcelable<DataType>(DataType::class.java.classLoader)
+                            ?: return null
+                    val availability = Availability.fromId(source.readInt()) ?: return null
+                    return AvailabilityResponse(parcelable, availability)
+                }
+
+                override fun newArray(size: Int): Array<AvailabilityResponse?> {
+                    return arrayOfNulls(size)
+                }
+            }
+    }
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/impl/response/CapabilitiesResponse.kt b/health/health-services-client/src/main/java/androidx/health/services/client/impl/response/CapabilitiesResponse.kt
new file mode 100644
index 0000000..213a060
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/impl/response/CapabilitiesResponse.kt
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 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.health.services.client.impl.response
+
+import android.os.Parcel
+import android.os.Parcelable
+import androidx.health.services.client.data.Capabilities
+
+/**
+ * Response containing the capabilities of WHS client on the device.
+ *
+ * @hide
+ */
+public data class CapabilitiesResponse(
+    /** [Capabilities] supported by this device. */
+    val capabilities: Capabilities,
+) : Parcelable {
+    override fun describeContents(): Int = 0
+
+    override fun writeToParcel(dest: Parcel, flags: Int) {
+        dest.writeParcelable(capabilities, flags)
+    }
+
+    public companion object {
+        @JvmField
+        public val CREATOR: Parcelable.Creator<CapabilitiesResponse> =
+            object : Parcelable.Creator<CapabilitiesResponse> {
+                override fun createFromParcel(source: Parcel): CapabilitiesResponse? {
+                    val parcelable =
+                        source.readParcelable<Capabilities>(Capabilities::class.java.classLoader)
+                            ?: return null
+                    return CapabilitiesResponse(parcelable)
+                }
+
+                override fun newArray(size: Int): Array<CapabilitiesResponse?> {
+                    return arrayOfNulls(size)
+                }
+            }
+    }
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/impl/response/DataPointsResponse.kt b/health/health-services-client/src/main/java/androidx/health/services/client/impl/response/DataPointsResponse.kt
new file mode 100644
index 0000000..0b30f6d
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/impl/response/DataPointsResponse.kt
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 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.health.services.client.impl.response
+
+import android.os.Parcel
+import android.os.Parcelable
+import androidx.health.services.client.data.DataPoint
+
+/**
+ * Response sent on MeasureCallback when new [DataPoints] [DataPoint] are available.
+ *
+ * @hide
+ */
+public data class DataPointsResponse(val dataPoints: List<DataPoint>) : Parcelable {
+    override fun describeContents(): Int = 0
+
+    override fun writeToParcel(dest: Parcel, flags: Int) {
+        dest.writeTypedList(dataPoints)
+    }
+
+    public companion object {
+        @JvmField
+        public val CREATOR: Parcelable.Creator<DataPointsResponse> =
+            object : Parcelable.Creator<DataPointsResponse> {
+                override fun createFromParcel(source: Parcel): DataPointsResponse? {
+                    val list = ArrayList<DataPoint>()
+                    source.readTypedList(list, DataPoint.CREATOR)
+                    return DataPointsResponse(list)
+                }
+
+                override fun newArray(size: Int): Array<DataPointsResponse?> {
+                    return arrayOfNulls(size)
+                }
+            }
+    }
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/impl/response/ExerciseInfoResponse.kt b/health/health-services-client/src/main/java/androidx/health/services/client/impl/response/ExerciseInfoResponse.kt
new file mode 100644
index 0000000..7a44767
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/impl/response/ExerciseInfoResponse.kt
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 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.health.services.client.impl.response
+
+import android.os.Parcel
+import android.os.Parcelable
+import androidx.health.services.client.data.ExerciseInfo
+
+/**
+ * Response containing [ExerciseInfo] when changed.
+ *
+ * @hide
+ */
+public data class ExerciseInfoResponse(val exerciseInfo: ExerciseInfo) : Parcelable {
+    override fun describeContents(): Int = 0
+
+    override fun writeToParcel(dest: Parcel, flags: Int) {
+        dest.writeParcelable(exerciseInfo, flags)
+    }
+
+    public companion object {
+        @JvmField
+        public val CREATOR: Parcelable.Creator<ExerciseInfoResponse> =
+            object : Parcelable.Creator<ExerciseInfoResponse> {
+                override fun createFromParcel(source: Parcel): ExerciseInfoResponse? {
+                    val parcelable: ExerciseInfo =
+                        source.readParcelable(ExerciseInfo::class.java.classLoader) ?: return null
+                    return ExerciseInfoResponse(parcelable)
+                }
+
+                override fun newArray(size: Int): Array<ExerciseInfoResponse?> {
+                    return arrayOfNulls(size)
+                }
+            }
+    }
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/impl/response/ExerciseLapSummaryResponse.kt b/health/health-services-client/src/main/java/androidx/health/services/client/impl/response/ExerciseLapSummaryResponse.kt
new file mode 100644
index 0000000..769346d
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/impl/response/ExerciseLapSummaryResponse.kt
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 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.health.services.client.impl.response
+
+import android.os.Parcel
+import android.os.Parcelable
+import androidx.health.services.client.data.ExerciseLapSummary
+
+/**
+ * Response containing [ExerciseLapSummary] when it's updated.
+ *
+ * @hide
+ */
+public data class ExerciseLapSummaryResponse(val exerciseLapSummary: ExerciseLapSummary) :
+    Parcelable {
+    override fun describeContents(): Int = 0
+
+    override fun writeToParcel(dest: Parcel, flags: Int) {
+        dest.writeParcelable(exerciseLapSummary, flags)
+    }
+
+    public companion object {
+        @JvmField
+        public val CREATOR: Parcelable.Creator<ExerciseLapSummaryResponse> =
+            object : Parcelable.Creator<ExerciseLapSummaryResponse> {
+                override fun createFromParcel(source: Parcel): ExerciseLapSummaryResponse? {
+                    val parcelable =
+                        source.readParcelable<ExerciseLapSummary>(
+                            ExerciseLapSummary::class.java.classLoader
+                        )
+                            ?: return null
+                    return ExerciseLapSummaryResponse(parcelable)
+                }
+
+                override fun newArray(size: Int): Array<ExerciseLapSummaryResponse?> {
+                    return arrayOfNulls(size)
+                }
+            }
+    }
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/impl/response/ExerciseUpdateResponse.kt b/health/health-services-client/src/main/java/androidx/health/services/client/impl/response/ExerciseUpdateResponse.kt
new file mode 100644
index 0000000..6c800d4
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/impl/response/ExerciseUpdateResponse.kt
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 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.health.services.client.impl.response
+
+import android.os.Parcel
+import android.os.Parcelable
+import androidx.health.services.client.data.ExerciseUpdate
+
+/**
+ * Response containing [ExerciseUpdate] when it's updated.
+ *
+ * @hide
+ */
+public data class ExerciseUpdateResponse(val exerciseUpdate: ExerciseUpdate) : Parcelable {
+    override fun describeContents(): Int = 0
+
+    override fun writeToParcel(dest: Parcel, flags: Int) {
+        dest.writeParcelable(exerciseUpdate, flags)
+    }
+
+    public companion object {
+        @JvmField
+        public val CREATOR: Parcelable.Creator<ExerciseUpdateResponse> =
+            object : Parcelable.Creator<ExerciseUpdateResponse> {
+                override fun createFromParcel(source: Parcel): ExerciseUpdateResponse? {
+                    val parcelable =
+                        source.readParcelable<ExerciseUpdate>(
+                            ExerciseUpdate::class.java.classLoader
+                        )
+                            ?: return null
+                    return ExerciseUpdateResponse(parcelable)
+                }
+
+                override fun newArray(size: Int): Array<ExerciseUpdateResponse?> {
+                    return arrayOfNulls(size)
+                }
+            }
+    }
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/impl/response/MeasureCapabilitiesResponse.kt b/health/health-services-client/src/main/java/androidx/health/services/client/impl/response/MeasureCapabilitiesResponse.kt
new file mode 100644
index 0000000..12f80e3
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/impl/response/MeasureCapabilitiesResponse.kt
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 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.health.services.client.impl.response
+
+import android.os.Parcel
+import android.os.Parcelable
+import androidx.health.services.client.data.MeasureCapabilities
+
+/**
+ * Response containing the capabilities of WHS client on the device.
+ *
+ * @hide
+ */
+public data class MeasureCapabilitiesResponse(
+    /** [MeasureCapabilities] supported by this device. */
+    val measureCapabilities: MeasureCapabilities,
+) : Parcelable {
+    override fun describeContents(): Int = 0
+
+    override fun writeToParcel(dest: Parcel, flags: Int) {
+        dest.writeParcelable(measureCapabilities, flags)
+    }
+
+    public companion object {
+        @JvmField
+        public val CREATOR: Parcelable.Creator<MeasureCapabilitiesResponse> =
+            object : Parcelable.Creator<MeasureCapabilitiesResponse> {
+                override fun createFromParcel(source: Parcel): MeasureCapabilitiesResponse? {
+                    val parcelable =
+                        source.readParcelable<MeasureCapabilities>(
+                            MeasureCapabilities::class.java.classLoader
+                        )
+                            ?: return null
+                    return MeasureCapabilitiesResponse(parcelable)
+                }
+
+                override fun newArray(size: Int): Array<MeasureCapabilitiesResponse?> {
+                    return arrayOfNulls(size)
+                }
+            }
+    }
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/impl/response/PassiveActivityStateResponse.kt b/health/health-services-client/src/main/java/androidx/health/services/client/impl/response/PassiveActivityStateResponse.kt
new file mode 100644
index 0000000..77e98b5
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/impl/response/PassiveActivityStateResponse.kt
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 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.health.services.client.impl.response
+
+import android.os.Parcel
+import android.os.Parcelable
+import androidx.health.services.client.data.PassiveActivityState
+
+/**
+ * Response containing [PassiveActivityState].
+ *
+ * @hide
+ */
+public data class PassiveActivityStateResponse(val passiveActivityState: PassiveActivityState) :
+    Parcelable {
+    override fun describeContents(): Int = 0
+
+    override fun writeToParcel(dest: Parcel, flags: Int) {
+        dest.writeParcelable(passiveActivityState, flags)
+    }
+
+    public companion object {
+        @JvmField
+        public val CREATOR: Parcelable.Creator<PassiveActivityStateResponse> =
+            object : Parcelable.Creator<PassiveActivityStateResponse> {
+                override fun createFromParcel(source: Parcel): PassiveActivityStateResponse? {
+                    val parcelable =
+                        source.readParcelable<PassiveActivityState>(
+                            PassiveActivityState::class.java.classLoader
+                        )
+                            ?: return null
+                    return PassiveActivityStateResponse(parcelable)
+                }
+
+                override fun newArray(size: Int): Array<PassiveActivityStateResponse?> {
+                    return arrayOfNulls(size)
+                }
+            }
+    }
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/impl/response/PassiveMonitoringCapabilitiesResponse.kt b/health/health-services-client/src/main/java/androidx/health/services/client/impl/response/PassiveMonitoringCapabilitiesResponse.kt
new file mode 100644
index 0000000..753735e
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/impl/response/PassiveMonitoringCapabilitiesResponse.kt
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 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.health.services.client.impl.response
+
+import android.os.Parcel
+import android.os.Parcelable
+import androidx.health.services.client.data.PassiveMonitoringCapabilities
+
+/**
+ * Response containing the capabilities of WHS client on the device.
+ *
+ * @hide
+ */
+public data class PassiveMonitoringCapabilitiesResponse(
+    /** [PassiveMonitoringCapabilities] supported by this device. */
+    val passiveMonitoringCapabilities: PassiveMonitoringCapabilities,
+) : Parcelable {
+    override fun describeContents(): Int = 0
+
+    override fun writeToParcel(dest: Parcel, flags: Int) {
+        dest.writeParcelable(passiveMonitoringCapabilities, flags)
+    }
+
+    public companion object {
+        @JvmField
+        public val CREATOR: Parcelable.Creator<PassiveMonitoringCapabilitiesResponse> =
+            object : Parcelable.Creator<PassiveMonitoringCapabilitiesResponse> {
+                override fun createFromParcel(
+                    source: Parcel
+                ): PassiveMonitoringCapabilitiesResponse? {
+                    val parcelable =
+                        source.readParcelable<PassiveMonitoringCapabilities>(
+                            PassiveMonitoringCapabilities::class.java.classLoader
+                        )
+                            ?: return null
+                    return PassiveMonitoringCapabilitiesResponse(parcelable)
+                }
+
+                override fun newArray(size: Int): Array<PassiveMonitoringCapabilitiesResponse?> {
+                    return arrayOfNulls(size)
+                }
+            }
+    }
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/package-info.java b/health/health-services-client/src/main/java/androidx/health/services/client/package-info.java
new file mode 100644
index 0000000..09f9b13
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/package-info.java
@@ -0,0 +1,21 @@
+/*
+ * 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.
+ */
+
+/**
+ * This package contains top level interfaces for the Health Services client.
+ */
+package androidx.health.services.client;
+
diff --git a/hilt/hilt-common/lint-baseline.xml b/hilt/hilt-common/lint-baseline.xml
deleted file mode 100644
index 8794ae8..0000000
--- a/hilt/hilt-common/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" version="4.2.0-beta06">
-
-</issues>
diff --git a/hilt/hilt-compiler/build.gradle b/hilt/hilt-compiler/build.gradle
index 2151577..d14a347 100644
--- a/hilt/hilt-compiler/build.gradle
+++ b/hilt/hilt-compiler/build.gradle
@@ -51,7 +51,7 @@
 }
 
 tasks.named("compileKotlin").configure {
-    dependsOn(":hilt:hilt-work:jarDebug")
+    dependsOn(":hilt:hilt-work:jarRelease")
 }
 
 androidx {
diff --git a/hilt/hilt-compiler/lint-baseline.xml b/hilt/hilt-compiler/lint-baseline.xml
deleted file mode 100644
index 8794ae8..0000000
--- a/hilt/hilt-compiler/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" version="4.2.0-beta06">
-
-</issues>
diff --git a/hilt/hilt-navigation-compose/build.gradle b/hilt/hilt-navigation-compose/build.gradle
index 8ef423c..b41c57c 100644
--- a/hilt/hilt-navigation-compose/build.gradle
+++ b/hilt/hilt-navigation-compose/build.gradle
@@ -41,7 +41,7 @@
     kotlinPlugin projectOrArtifact(":compose:compiler:compiler")
 
     implementation(KOTLIN_STDLIB)
-    api(project(":hilt:hilt-navigation"))
+    api("androidx.hilt:hilt-navigation:1.0.0")
     api(projectOrArtifact(":compose:runtime:runtime"))
     api(projectOrArtifact(":compose:ui:ui"))
     api(projectOrArtifact(":lifecycle:lifecycle-viewmodel-compose"))
diff --git a/hilt/hilt-navigation-compose/samples/src/main/java/androidx/hilt/navigation/compose/samples/HiltViewModelSamples.kt b/hilt/hilt-navigation-compose/samples/src/main/java/androidx/hilt/navigation/compose/samples/HiltViewModelSamples.kt
index 2a13592..e52fb79 100644
--- a/hilt/hilt-navigation-compose/samples/src/main/java/androidx/hilt/navigation/compose/samples/HiltViewModelSamples.kt
+++ b/hilt/hilt-navigation-compose/samples/src/main/java/androidx/hilt/navigation/compose/samples/HiltViewModelSamples.kt
@@ -24,9 +24,8 @@
 import androidx.lifecycle.ViewModel
 import androidx.navigation.compose.NavHost
 import androidx.navigation.compose.composable
-import androidx.navigation.compose.getBackStackEntry
-import androidx.navigation.compose.navigation
 import androidx.navigation.compose.rememberNavController
+import androidx.navigation.navigation
 import dagger.hilt.android.lifecycle.HiltViewModel
 import javax.inject.Inject
 
diff --git a/hilt/hilt-navigation-compose/src/androidTest/java/androidx/hilt/navigation/compose/HiltViewModelComposeTest.kt b/hilt/hilt-navigation-compose/src/androidTest/java/androidx/hilt/navigation/compose/HiltViewModelComposeTest.kt
index e45832f..8c51e0e 100644
--- a/hilt/hilt-navigation-compose/src/androidTest/java/androidx/hilt/navigation/compose/HiltViewModelComposeTest.kt
+++ b/hilt/hilt-navigation-compose/src/androidTest/java/androidx/hilt/navigation/compose/HiltViewModelComposeTest.kt
@@ -20,19 +20,17 @@
 import androidx.compose.material.Button
 import androidx.compose.material.Text
 import androidx.compose.runtime.Composable
+import androidx.compose.testutils.runBlockingWithManualClock
 import androidx.compose.ui.test.ExperimentalTestApi
 import androidx.compose.ui.test.junit4.createAndroidComposeRule
 import androidx.compose.ui.test.onNodeWithText
 import androidx.compose.ui.test.performClick
-import androidx.compose.testutils.runBlockingWithManualClock
 import androidx.lifecycle.SavedStateHandle
 import androidx.lifecycle.ViewModel
 import androidx.navigation.compose.NavHost
 import androidx.navigation.compose.composable
-import androidx.navigation.compose.getBackStackEntry
-import androidx.navigation.compose.navigate
-import androidx.navigation.compose.navigation
 import androidx.navigation.compose.rememberNavController
+import androidx.navigation.navigation
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.LargeTest
 import com.google.common.truth.Truth.assertThat
diff --git a/hilt/hilt-navigation-compose/src/main/java/androidx/hilt/navigation/compose/HiltViewModel.kt b/hilt/hilt-navigation-compose/src/main/java/androidx/hilt/navigation/compose/HiltViewModel.kt
index d6ce28c..7a1210c 100644
--- a/hilt/hilt-navigation-compose/src/main/java/androidx/hilt/navigation/compose/HiltViewModel.kt
+++ b/hilt/hilt-navigation-compose/src/main/java/androidx/hilt/navigation/compose/HiltViewModel.kt
@@ -25,7 +25,6 @@
 import androidx.lifecycle.viewmodel.compose.viewModel
 import androidx.navigation.NavBackStackEntry
 import androidx.navigation.NavController
-import androidx.navigation.compose.getBackStackEntry
 import kotlin.reflect.KClass
 
 /**
diff --git a/hilt/hilt-navigation-fragment/lint-baseline.xml b/hilt/hilt-navigation-fragment/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/hilt/hilt-navigation-fragment/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/hilt/hilt-work/lint-baseline.xml b/hilt/hilt-work/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/hilt/hilt-work/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/hilt/integration-tests/viewmodelapp/lint-baseline.xml b/hilt/integration-tests/viewmodelapp/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/hilt/integration-tests/viewmodelapp/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/hilt/integration-tests/workerapp/lint-baseline.xml b/hilt/integration-tests/workerapp/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/hilt/integration-tests/workerapp/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/inspection/inspection-gradle-plugin/lint-baseline.xml b/inspection/inspection-gradle-plugin/lint-baseline.xml
deleted file mode 100644
index 8794ae8..0000000
--- a/inspection/inspection-gradle-plugin/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" version="4.2.0-beta06">
-
-</issues>
diff --git a/interpolator/interpolator/lint-baseline.xml b/interpolator/interpolator/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/interpolator/interpolator/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/jetifier/jetifier/core/lint-baseline.xml b/jetifier/jetifier/core/lint-baseline.xml
deleted file mode 100644
index 8794ae8..0000000
--- a/jetifier/jetifier/core/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" version="4.2.0-beta06">
-
-</issues>
diff --git a/jetifier/jetifier/preprocessor/lint-baseline.xml b/jetifier/jetifier/preprocessor/lint-baseline.xml
deleted file mode 100644
index 8794ae8..0000000
--- a/jetifier/jetifier/preprocessor/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" version="4.2.0-beta06">
-
-</issues>
diff --git a/jetifier/jetifier/processor/lint-baseline.xml b/jetifier/jetifier/processor/lint-baseline.xml
deleted file mode 100644
index 8794ae8..0000000
--- a/jetifier/jetifier/processor/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" version="4.2.0-beta06">
-
-</issues>
diff --git a/jetifier/jetifier/standalone/lint-baseline.xml b/jetifier/jetifier/standalone/lint-baseline.xml
deleted file mode 100644
index 8794ae8..0000000
--- a/jetifier/jetifier/standalone/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" version="4.2.0-beta06">
-
-</issues>
diff --git a/leanback/leanback-paging/lint-baseline.xml b/leanback/leanback-paging/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/leanback/leanback-paging/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/leanback/leanback-tab/lint-baseline.xml b/leanback/leanback-tab/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/leanback/leanback-tab/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/leanback/leanback/src/androidTest/java/androidx/leanback/widget/GridWidgetTest.java b/leanback/leanback/src/androidTest/java/androidx/leanback/widget/GridWidgetTest.java
index b6bae78..ce23b2f 100644
--- a/leanback/leanback/src/androidTest/java/androidx/leanback/widget/GridWidgetTest.java
+++ b/leanback/leanback/src/androidTest/java/androidx/leanback/widget/GridWidgetTest.java
@@ -4567,7 +4567,7 @@
         assertEquals(0, mGridView.getSelectedPosition());
     }
 
-    @FlakyTest
+    @FlakyTest(bugId = 187191618)
     @Test
     public void testExtraLayoutSpace() throws Throwable {
         Intent intent = new Intent();
diff --git a/lifecycle/integration-tests/incrementality/lint-baseline.xml b/lifecycle/integration-tests/incrementality/lint-baseline.xml
deleted file mode 100644
index 8794ae8..0000000
--- a/lifecycle/integration-tests/incrementality/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" version="4.2.0-beta06">
-
-</issues>
diff --git a/lifecycle/integration-tests/kotlintestapp/lint-baseline.xml b/lifecycle/integration-tests/kotlintestapp/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/lifecycle/integration-tests/kotlintestapp/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/lifecycle/lifecycle-common-java8/lint-baseline.xml b/lifecycle/lifecycle-common-java8/lint-baseline.xml
deleted file mode 100644
index 8794ae8..0000000
--- a/lifecycle/lifecycle-common-java8/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" version="4.2.0-beta06">
-
-</issues>
diff --git a/lifecycle/lifecycle-compiler/lint-baseline.xml b/lifecycle/lifecycle-compiler/lint-baseline.xml
deleted file mode 100644
index 8794ae8..0000000
--- a/lifecycle/lifecycle-compiler/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" version="4.2.0-beta06">
-
-</issues>
diff --git a/lifecycle/lifecycle-livedata-core-ktx-lint/lint-baseline.xml b/lifecycle/lifecycle-livedata-core-ktx-lint/lint-baseline.xml
deleted file mode 100644
index 8794ae8..0000000
--- a/lifecycle/lifecycle-livedata-core-ktx-lint/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" version="4.2.0-beta06">
-
-</issues>
diff --git a/lifecycle/lifecycle-livedata-core-ktx-lint/src/main/java/androidx/lifecycle/lint/NonNullableMutableLiveDataDetector.kt b/lifecycle/lifecycle-livedata-core-ktx-lint/src/main/java/androidx/lifecycle/lint/NonNullableMutableLiveDataDetector.kt
index 098de7c..9b801e1 100644
--- a/lifecycle/lifecycle-livedata-core-ktx-lint/src/main/java/androidx/lifecycle/lint/NonNullableMutableLiveDataDetector.kt
+++ b/lifecycle/lifecycle-livedata-core-ktx-lint/src/main/java/androidx/lifecycle/lint/NonNullableMutableLiveDataDetector.kt
@@ -30,6 +30,8 @@
 import com.android.tools.lint.detector.api.isKotlin
 import com.intellij.psi.PsiClassType
 import com.intellij.psi.PsiVariable
+import com.intellij.psi.impl.source.PsiImmediateClassType
+import org.jetbrains.kotlin.asJava.elements.KtLightTypeParameter
 import org.jetbrains.kotlin.psi.KtCallExpression
 import org.jetbrains.kotlin.psi.KtNullableType
 import org.jetbrains.kotlin.psi.KtTypeReference
@@ -94,15 +96,26 @@
             }
 
             private fun getFieldTypeReference(element: KotlinUField): KtTypeReference? {
-                // We need to extract type from the expression
+                // If field has type reference, we need to use type reference
+                // Given the field `val liveDataField: MutableLiveData<Boolean> = MutableLiveData()`
+                // reference: `MutableLiveData<Boolean>`
+                // argument: `Boolean`
+                val typeReference = element.sourcePsi
+                    ?.children
+                    ?.firstOrNull { it is KtTypeReference } as? KtTypeReference
+                val typeArgument = typeReference?.typeElement?.typeArgumentsAsTypes?.singleOrNull()
+                if (typeArgument != null) {
+                    return typeArgument
+                }
+
+                // We need to extract type from the call expression
                 // Given the field `val liveDataField = MutableLiveData<Boolean>()`
                 // expression: `MutableLiveData<Boolean>()`
                 // argument: `Boolean`
                 val expression = element.sourcePsi
                     ?.children
                     ?.firstOrNull { it is KtCallExpression } as? KtCallExpression
-                val argument = expression?.typeArguments?.singleOrNull()
-                return argument?.typeReference
+                return expression?.typeArguments?.singleOrNull()?.typeReference
             }
 
             override fun visitCallExpression(node: UCallExpression) {
@@ -170,6 +183,9 @@
         context: JavaContext,
         node: UCallExpression
     ) {
+        // ignore generic types
+        if (node.isGenericTypeDefinition()) return
+
         if (liveDataType.typeElement !is KtNullableType) {
             val fixes = mutableListOf<LintFix>()
             if (context.getLocation(liveDataType).file == context.file) {
@@ -198,6 +214,12 @@
         }
     }
 
+    private fun UCallExpression.isGenericTypeDefinition(): Boolean {
+        val classType = typeArguments.singleOrNull() as? PsiImmediateClassType
+        val resolveGenerics = classType?.resolveGenerics()
+        return resolveGenerics?.element is KtLightTypeParameter
+    }
+
     /**
      * Reports a lint error at [element]'s location with message and quick fixes.
      *
diff --git a/lifecycle/lifecycle-livedata-core-ktx-lint/src/test/java/androidx/lifecycle/lint/NonNullableMutableLiveDataDetectorTest.kt b/lifecycle/lifecycle-livedata-core-ktx-lint/src/test/java/androidx/lifecycle/lint/NonNullableMutableLiveDataDetectorTest.kt
index 095d9f6..591e6a9 100644
--- a/lifecycle/lifecycle-livedata-core-ktx-lint/src/test/java/androidx/lifecycle/lint/NonNullableMutableLiveDataDetectorTest.kt
+++ b/lifecycle/lifecycle-livedata-core-ktx-lint/src/test/java/androidx/lifecycle/lint/NonNullableMutableLiveDataDetectorTest.kt
@@ -199,23 +199,28 @@
                 import androidx.lifecycle.MutableLiveData
 
                 val liveDataField = MutableLiveData<Boolean>()
-                val secondLiveDataField = MutableLiveData<String>()
+                val secondLiveDataField: MutableLiveData<String> = MutableLiveData()
+                val thirdLiveDataField: MutableLiveData<String> = MutableLiveData<String>("Value")
 
                 fun foo() {
                     liveDataField.value = null
                     secondLiveDataField.value = null
+                    thirdLiveDataField.value = null
                 }
             """
             ).indented()
         ).expect(
             """
-src/com/example/test.kt:9: Error: Cannot set non-nullable LiveData value to null [NullSafeMutableLiveData]
+src/com/example/test.kt:10: Error: Cannot set non-nullable LiveData value to null [NullSafeMutableLiveData]
     liveDataField.value = null
                           ~~~~
-src/com/example/test.kt:10: Error: Cannot set non-nullable LiveData value to null [NullSafeMutableLiveData]
+src/com/example/test.kt:11: Error: Cannot set non-nullable LiveData value to null [NullSafeMutableLiveData]
     secondLiveDataField.value = null
                                 ~~~~
-2 errors, 0 warnings
+src/com/example/test.kt:12: Error: Cannot set non-nullable LiveData value to null [NullSafeMutableLiveData]
+    thirdLiveDataField.value = null
+                               ~~~~
+3 errors, 0 warnings
         """
         )
     }
@@ -760,4 +765,39 @@
             ).indented()
         ).expectClean()
     }
+
+    @Test
+    fun justKotlinObject() {
+        check(
+            kotlin(
+                """
+                package com.example
+
+                object Foo
+            """
+            ).indented()
+        ).expectClean()
+    }
+
+    @Test
+    fun genericParameterDefinition() {
+        check(
+            kotlin(
+                """
+                package com.example
+
+                import androidx.lifecycle.MutableLiveData
+
+                class Foo<T>(
+                    var target: MutableLiveData<T>
+                ) {
+
+                    fun foo(value: T) {
+                        target.value = null
+                    }
+                }
+            """
+            ).indented()
+        ).expectClean()
+    }
 }
diff --git a/lifecycle/lifecycle-livedata-core-ktx/lint-baseline.xml b/lifecycle/lifecycle-livedata-core-ktx/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/lifecycle/lifecycle-livedata-core-ktx/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/lifecycle/lifecycle-livedata-core-truth/lint-baseline.xml b/lifecycle/lifecycle-livedata-core-truth/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/lifecycle/lifecycle-livedata-core-truth/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/lifecycle/lifecycle-reactivestreams-ktx/lint-baseline.xml b/lifecycle/lifecycle-reactivestreams-ktx/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/lifecycle/lifecycle-reactivestreams-ktx/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/lifecycle/lifecycle-runtime-ktx-lint/lint-baseline.xml b/lifecycle/lifecycle-runtime-ktx-lint/lint-baseline.xml
deleted file mode 100644
index 8794ae8..0000000
--- a/lifecycle/lifecycle-runtime-ktx-lint/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" version="4.2.0-beta06">
-
-</issues>
diff --git a/lifecycle/lifecycle-runtime-ktx/lint-baseline.xml b/lifecycle/lifecycle-runtime-ktx/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/lifecycle/lifecycle-runtime-ktx/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/lifecycle/lifecycle-runtime-testing/lint-baseline.xml b/lifecycle/lifecycle-runtime-testing/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/lifecycle/lifecycle-runtime-testing/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/lifecycle/lifecycle-service/lint-baseline.xml b/lifecycle/lifecycle-service/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/lifecycle/lifecycle-service/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/lifecycle/lifecycle-viewmodel-ktx/lint-baseline.xml b/lifecycle/lifecycle-viewmodel-ktx/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/lifecycle/lifecycle-viewmodel-ktx/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/lifecycle/lifecycle-viewmodel/lint-baseline.xml b/lifecycle/lifecycle-viewmodel/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/lifecycle/lifecycle-viewmodel/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/lint-checks/integration-tests/build.gradle b/lint-checks/integration-tests/build.gradle
index a01e2e6..c7f4c61 100644
--- a/lint-checks/integration-tests/build.gradle
+++ b/lint-checks/integration-tests/build.gradle
@@ -14,9 +14,6 @@
  * limitations under the License.
  */
 
-import androidx.build.BuildOnServerKt
-import androidx.build.dependencyTracker.AffectedModuleDetector
-import androidx.build.uptodatedness.EnableCachingKt
 import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
 
 import static androidx.build.dependencies.DependenciesKt.KOTLIN_STDLIB
@@ -82,28 +79,3 @@
         }
     }
 }
-
-def lintOutputFile = project.file("${buildDir}/reports/lint-results-debug.xml")
-def lintOutputFileNormalized = project.file("${buildDir}/lint-results-normalized/lint-results-debug.xml.normalized")
-
-def normalizeLintOutput = tasks.register("normalizeLintOutput", Copy) {
-    def supportRootFolder = project.rootProject.ext.supportRootFolder
-    from(lintOutputFile) {
-        filter { line ->
-            return line.replace(supportRootFolder.getAbsolutePath(), "\$SUPPORT")
-        }
-    }
-    into(lintOutputFileNormalized.parentFile)
-    rename(".*", lintOutputFileNormalized.name)
-    dependsOn("lintDebug")
-}
-
-def validateLint = tasks.register("validateLint", CompareFilesTask) { task ->
-    task.actualFile = lintOutputFileNormalized
-    task.expectedFile = project.file("expected-lint-results.xml")
-    EnableCachingKt.cacheEvenIfNoOutputs(task)
-    dependsOn(normalizeLintOutput)
-    AffectedModuleDetector.configureTaskGuard(task)
-}
-
-BuildOnServerKt.addToBuildOnServer(project, validateLint)
diff --git a/lint-checks/integration-tests/expected-lint-results.xml b/lint-checks/integration-tests/expected-lint-results.xml
index 79b7190..698b8cd 100644
--- a/lint-checks/integration-tests/expected-lint-results.xml
+++ b/lint-checks/integration-tests/expected-lint-results.xml
@@ -20,6 +20,22 @@
     <issue
         id="ClassVerificationFailure"
         severity="Error"
+        message="This call references a method added in API level 30; however, the containing class androidx.AutofixUnsafeConstructorReferenceJava is reachable from earlier API levels and will fail run-time class verification."
+        category="Correctness"
+        priority="5"
+        summary="Even in cases where references to new APIs are gated on SDK_INT checks, run-time class verification will still fail on references to APIs that may not be available at run time, including platform APIs introduced after a library&apos;s minSdkVersion."
+        explanation="The Java language requires a virtual machine to verify the class files it&#xA;loads and executes. A class may fail verification for a wide variety of&#xA;reasons, but in practice it‘s usually because the class’s code refers to&#xA;unknown classes or methods.&#xA;&#xA;References to APIs added after a library&apos;s minSdkVersion -- regardless of&#xA;any surrounding version checks -- will fail run-time class verification if&#xA;the API does not exist on the device, leading to reduced run-time&#xA;performance.&#xA;&#xA;Gating references on SDK checks alone DOES NOT address class verification&#xA;failures.&#xA;&#xA;To prevent class verification failures, references to new APIs must be&#xA;moved to inner classes that are only initialized inside of an appropriate&#xA;SDK check.&#xA;&#xA;For example, if our minimum SDK is 14 and platform method a.x(params...)&#xA;was added in SDK 16, the method call must be moved to an inner class like:&#xA;&#xA;@RequiresApi(16)&#xA;private static class Api16Impl{&#xA;  @DoNotInline&#xA;  static void callX(params...) {&#xA;    a.x(params...);&#xA;  }&#xA;}&#xA;&#xA;The call site is changed from a.x(params...) to Api16Impl.callX(params).&#xA;&#xA;Since ART will only try to optimize Api16Impl when it&apos;s on the execution&#xA;path, we are guaranteed to have a.x(...) available.&#xA;&#xA;In addition, optimizers like R8 or Proguard may inline the method in the&#xA;separate class and replace the wrapper call with the actual call, so you&#xA;must disable inlining using the @DoNotInline annotation.&#xA;&#xA;Failure to do the above may result in overall performance degradation."
+        errorLine1="            AccessibilityNodeInfo node = new AccessibilityNodeInfo(new View(context), 1);"
+        errorLine2="                                         ~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="$SUPPORT/lint-checks/integration-tests/src/main/java/androidx/AutofixUnsafeConstructorReferenceJava.java"
+            line="35"
+            column="42"/>
+    </issue>
+
+    <issue
+        id="ClassVerificationFailure"
+        severity="Error"
         message="This call references a method added in API level 23; however, the containing class androidx.AutofixUnsafeGenericMethodReferenceJava is reachable from earlier API levels and will fail run-time class verification."
         category="Correctness"
         priority="5"
diff --git a/lint-checks/tests/lint-baseline.xml b/lint-checks/tests/lint-baseline.xml
deleted file mode 100644
index 8794ae8..0000000
--- a/lint-checks/tests/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" version="4.2.0-beta06">
-
-</issues>
diff --git a/loader/loader-ktx/lint-baseline.xml b/loader/loader-ktx/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/loader/loader-ktx/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/loader/loader/lint-baseline.xml b/loader/loader/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/loader/loader/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/localbroadcastmanager/localbroadcastmanager/lint-baseline.xml b/localbroadcastmanager/localbroadcastmanager/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/localbroadcastmanager/localbroadcastmanager/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/media/media/api/current.txt b/media/media/api/current.txt
index ab43c37..e82dbe2 100644
--- a/media/media/api/current.txt
+++ b/media/media/api/current.txt
@@ -729,6 +729,7 @@
     field public static final String METADATA_KEY_IS_ADVERTISEMENT = "android.media.metadata.ADVERTISEMENT";
     field public static final String METADATA_KEY_IS_EXPLICIT = "android.media.IS_EXPLICIT";
     field public static final String METADATA_KEY_NEXT_EPISODE_CONTENT_ID = "androidx.media.MediaMetadatCompat.METADATA_KEY_NEXT_EPISODE_CONTENT_ID";
+    field public static final String METADATA_KEY_SERIES_CONTENT_ID = "androidx.media.MediaMetadatCompat.METADATA_KEY_SERIES_CONTENT_ID";
     field public static final long METADATA_VALUE_ATTRIBUTE_PRESENT = 1L; // 0x1L
     field public static final String PLAYBACK_STATE_EXTRAS_KEY_ERROR_RESOLUTION_ACTION_INTENT = "android.media.extras.ERROR_RESOLUTION_ACTION_INTENT";
     field public static final String PLAYBACK_STATE_EXTRAS_KEY_ERROR_RESOLUTION_ACTION_LABEL = "android.media.extras.ERROR_RESOLUTION_ACTION_LABEL";
diff --git a/media/media/api/public_plus_experimental_current.txt b/media/media/api/public_plus_experimental_current.txt
index cf5874c..d9037ca 100644
--- a/media/media/api/public_plus_experimental_current.txt
+++ b/media/media/api/public_plus_experimental_current.txt
@@ -729,6 +729,7 @@
     field public static final String METADATA_KEY_IS_ADVERTISEMENT = "android.media.metadata.ADVERTISEMENT";
     field public static final String METADATA_KEY_IS_EXPLICIT = "android.media.IS_EXPLICIT";
     field public static final String METADATA_KEY_NEXT_EPISODE_CONTENT_ID = "androidx.media.MediaMetadatCompat.METADATA_KEY_NEXT_EPISODE_CONTENT_ID";
+    field public static final String METADATA_KEY_SERIES_CONTENT_ID = "androidx.media.MediaMetadatCompat.METADATA_KEY_SERIES_CONTENT_ID";
     field public static final long METADATA_VALUE_ATTRIBUTE_PRESENT = 1L; // 0x1L
     field public static final String PLAYBACK_STATE_EXTRAS_KEY_ERROR_RESOLUTION_ACTION_INTENT = "android.media.extras.ERROR_RESOLUTION_ACTION_INTENT";
     field public static final String PLAYBACK_STATE_EXTRAS_KEY_ERROR_RESOLUTION_ACTION_LABEL = "android.media.extras.ERROR_RESOLUTION_ACTION_LABEL";
diff --git a/media/media/api/restricted_current.txt b/media/media/api/restricted_current.txt
index 3f5c9d9..bd39a46 100644
--- a/media/media/api/restricted_current.txt
+++ b/media/media/api/restricted_current.txt
@@ -761,6 +761,7 @@
     field public static final String METADATA_KEY_IS_ADVERTISEMENT = "android.media.metadata.ADVERTISEMENT";
     field public static final String METADATA_KEY_IS_EXPLICIT = "android.media.IS_EXPLICIT";
     field public static final String METADATA_KEY_NEXT_EPISODE_CONTENT_ID = "androidx.media.MediaMetadatCompat.METADATA_KEY_NEXT_EPISODE_CONTENT_ID";
+    field public static final String METADATA_KEY_SERIES_CONTENT_ID = "androidx.media.MediaMetadatCompat.METADATA_KEY_SERIES_CONTENT_ID";
     field public static final long METADATA_VALUE_ATTRIBUTE_PRESENT = 1L; // 0x1L
     field public static final String PLAYBACK_STATE_EXTRAS_KEY_ERROR_RESOLUTION_ACTION_INTENT = "android.media.extras.ERROR_RESOLUTION_ACTION_INTENT";
     field public static final String PLAYBACK_STATE_EXTRAS_KEY_ERROR_RESOLUTION_ACTION_LABEL = "android.media.extras.ERROR_RESOLUTION_ACTION_LABEL";
diff --git a/media/media/src/main/java/androidx/media/utils/MediaConstants.java b/media/media/src/main/java/androidx/media/utils/MediaConstants.java
index 8d81004..bd04425 100644
--- a/media/media/src/main/java/androidx/media/utils/MediaConstants.java
+++ b/media/media/src/main/java/androidx/media/utils/MediaConstants.java
@@ -113,7 +113,7 @@
     /**
      * Bundle key used for media content id in {@link MediaMetadataCompat metadata}, should contain
      * the same ID provided to Media Actions Catalog in reference to this title (e.g., episode,
-     * movie). Google uses this information to allow users to resume watching this title on your app
+     * movie). This information can be used to allow users to resume watching this title on your app
      * across the supported surfaces (e.g., Android TV's Play Next row)
      *
      * <p>TYPE: String
@@ -126,12 +126,10 @@
 
     /**
      * Bundle key used for next episode's media content ID in {@link MediaMetadataCompat metadata},
-     * following the same ID and format provided to
-     * <a href="https://developers.google.com/actions/media">Media Actions Catalog</a> in reference
-     * to the next episode of the current title episode. Google uses this information to allow users
-     * to resume watching the next episode of this title on your app once the current episode ends
-     * across the supported surfaces (e.g., Android TV's Play Next row). This can be left blank for
-     * movies.
+     * following the same ID and format provided to Media Actions Catalog in reference to the next
+     * episode of the current title episode. This information can be used to allow users to resume
+     * watching the next episode of this title on your app once the current episode ends across the
+     * supported surfaces (e.g., Android TV's Play Next row). This can be left blank for movies.
      *
      * <p>TYPE: String
      *
@@ -142,6 +140,22 @@
             "androidx.media.MediaMetadatCompat.METADATA_KEY_NEXT_EPISODE_CONTENT_ID";
 
     /**
+     * Bundle key used for the TV series's media content ID in {@link MediaMetadataCompat metadata},
+     * following the same ID and format provided to Media Actions Catalog</a> in reference to the
+     * TV series of the title episode. This information can be used to allow users to resume
+     * watching the current episode or next episode of this title on your app across the
+     * supported surfaces (e.g., Android TV's Play Next row). This value is only valid for TV
+     * Episode content type.
+     *
+     * <p>TYPE: String
+     *
+     * @see MediaMetadataCompat
+     */
+    @SuppressLint("IntentName")
+    public static final String METADATA_KEY_SERIES_CONTENT_ID =
+            "androidx.media.MediaMetadatCompat.METADATA_KEY_SERIES_CONTENT_ID";
+
+    /**
      * Key sent through a key-value mapping in {@link MediaMetadataCompat#getLong(String)} or in the
      * {@link MediaDescriptionCompat#getExtras()} bundle to the hosting {@link MediaBrowserCompat}
      * to indicate that the corresponding {@link MediaMetadataCompat} or {@link
diff --git a/media/version-compat-tests/current/client/lint-baseline.xml b/media/version-compat-tests/current/client/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/media/version-compat-tests/current/client/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/media/version-compat-tests/current/client/src/androidTest/java/android/support/mediacompat/client/RemoteUserInfoWithMediaControllerCompatTest.java b/media/version-compat-tests/current/client/src/androidTest/java/android/support/mediacompat/client/RemoteUserInfoWithMediaControllerCompatTest.java
index a509209..8bde7c9 100644
--- a/media/version-compat-tests/current/client/src/androidTest/java/android/support/mediacompat/client/RemoteUserInfoWithMediaControllerCompatTest.java
+++ b/media/version-compat-tests/current/client/src/androidTest/java/android/support/mediacompat/client/RemoteUserInfoWithMediaControllerCompatTest.java
@@ -50,7 +50,6 @@
 
 import org.junit.After;
 import org.junit.Before;
-import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -138,7 +137,6 @@
     }
 
     @Test
-    @Ignore("b/185140325")
     public void testSendCommand() throws InterruptedException {
         mMediaControllerCallback.setExpectedCallbackMethodName("onCommand");
         mMediaController.sendCommand("anyCommand", /* extras= */ null, /* cb= */ null);
diff --git a/media/version-compat-tests/current/service/lint-baseline.xml b/media/version-compat-tests/current/service/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/media/version-compat-tests/current/service/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/media/version-compat-tests/previous/client/lint-baseline.xml b/media/version-compat-tests/previous/client/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/media/version-compat-tests/previous/client/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/media/version-compat-tests/previous/service/lint-baseline.xml b/media/version-compat-tests/previous/service/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/media/version-compat-tests/previous/service/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/media2/media2-exoplayer/lint-baseline.xml b/media2/media2-exoplayer/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/media2/media2-exoplayer/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/media2/media2-player/src/androidTest/java/androidx/media2/player/MediaPlayer2Test.java b/media2/media2-player/src/androidTest/java/androidx/media2/player/MediaPlayer2Test.java
index e51f2fa..d39c2ad 100644
--- a/media2/media2-player/src/androidTest/java/androidx/media2/player/MediaPlayer2Test.java
+++ b/media2/media2-player/src/androidTest/java/androidx/media2/player/MediaPlayer2Test.java
@@ -50,6 +50,7 @@
 import androidx.media2.player.TestUtils.Monitor;
 import androidx.media2.player.test.R;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.FlakyTest;
 import androidx.test.filters.LargeTest;
 import androidx.test.filters.MediumTest;
 import androidx.test.filters.SdkSuppress;
@@ -1089,6 +1090,7 @@
     }
     */
 
+    @FlakyTest(bugId = 187340262)
     @Test
     @LargeTest
     public void playbackRate() throws Exception {
diff --git a/media2/media2-session/version-compat-tests/current/client/lint-baseline.xml b/media2/media2-session/version-compat-tests/current/client/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/media2/media2-session/version-compat-tests/current/client/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/media2/media2-session/version-compat-tests/current/client/src/androidTest/java/androidx/media2/test/client/tests/MediaControllerCompatCallbackWithMediaSessionTest.java b/media2/media2-session/version-compat-tests/current/client/src/androidTest/java/androidx/media2/test/client/tests/MediaControllerCompatCallbackWithMediaSessionTest.java
index b7d0f5e..e7b4a70 100644
--- a/media2/media2-session/version-compat-tests/current/client/src/androidTest/java/androidx/media2/test/client/tests/MediaControllerCompatCallbackWithMediaSessionTest.java
+++ b/media2/media2-session/version-compat-tests/current/client/src/androidTest/java/androidx/media2/test/client/tests/MediaControllerCompatCallbackWithMediaSessionTest.java
@@ -46,6 +46,7 @@
 import androidx.media2.test.common.PollingCheck;
 import androidx.media2.test.common.TestUtils;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.FlakyTest;
 import androidx.test.filters.LargeTest;
 
 import org.junit.After;
@@ -597,6 +598,7 @@
         assertEquals(testSeekPosition, mControllerCompat.getPlaybackState().getPosition());
     }
 
+    @FlakyTest(bugId = 187338985)
     @Test
     public void currentMediaItemChange() throws Exception {
         int testItemIndex = 3;
diff --git a/media2/media2-session/version-compat-tests/current/service/lint-baseline.xml b/media2/media2-session/version-compat-tests/current/service/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/media2/media2-session/version-compat-tests/current/service/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/media2/media2-session/version-compat-tests/previous/client/lint-baseline.xml b/media2/media2-session/version-compat-tests/previous/client/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/media2/media2-session/version-compat-tests/previous/client/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/media2/media2-session/version-compat-tests/previous/service/lint-baseline.xml b/media2/media2-session/version-compat-tests/previous/service/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/media2/media2-session/version-compat-tests/previous/service/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/navigation/benchmark/lint-baseline.xml b/navigation/benchmark/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/navigation/benchmark/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/navigation/navigation-common-ktx/lint-baseline.xml b/navigation/navigation-common-ktx/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/navigation/navigation-common-ktx/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/navigation/navigation-common/api/api_lint.ignore b/navigation/navigation-common/api/api_lint.ignore
index aefa4c8..8610f6c 100644
--- a/navigation/navigation-common/api/api_lint.ignore
+++ b/navigation/navigation-common/api/api_lint.ignore
@@ -37,6 +37,8 @@
     Builder methods names should use setFoo() / addFoo() / clearFoo() style: method androidx.navigation.NavDestinationBuilder.deepLink(kotlin.jvm.functions.Function1<? super androidx.navigation.NavDeepLinkDslBuilder,kotlin.Unit>)
 BuilderSetStyle: androidx.navigation.NavOptionsBuilder#anim(kotlin.jvm.functions.Function1<? super androidx.navigation.AnimBuilder,kotlin.Unit>):
     Builder methods names should use setFoo() / addFoo() / clearFoo() style: method androidx.navigation.NavOptionsBuilder.anim(kotlin.jvm.functions.Function1<? super androidx.navigation.AnimBuilder,kotlin.Unit>)
+BuilderSetStyle: androidx.navigation.NavOptionsBuilder#popUpTo(String, kotlin.jvm.functions.Function1<? super androidx.navigation.PopUpToBuilder,kotlin.Unit>):
+    Builder methods names should use setFoo() / addFoo() / clearFoo() style: method androidx.navigation.NavOptionsBuilder.popUpTo(String,kotlin.jvm.functions.Function1<? super androidx.navigation.PopUpToBuilder,kotlin.Unit>)
 BuilderSetStyle: androidx.navigation.NavOptionsBuilder#popUpTo(int, kotlin.jvm.functions.Function1<? super androidx.navigation.PopUpToBuilder,kotlin.Unit>):
     Builder methods names should use setFoo() / addFoo() / clearFoo() style: method androidx.navigation.NavOptionsBuilder.popUpTo(int,kotlin.jvm.functions.Function1<? super androidx.navigation.PopUpToBuilder,kotlin.Unit>)
 
@@ -81,8 +83,10 @@
     Getter should be on the built object, not the builder: method androidx.navigation.NavDestinationBuilder.getRoute()
 GetterOnBuilder: androidx.navigation.NavOptionsBuilder#getLaunchSingleTop():
     Getter should be on the built object, not the builder: method androidx.navigation.NavOptionsBuilder.getLaunchSingleTop()
-GetterOnBuilder: androidx.navigation.NavOptionsBuilder#getPopUpTo():
-    Getter should be on the built object, not the builder: method androidx.navigation.NavOptionsBuilder.getPopUpTo()
+GetterOnBuilder: androidx.navigation.NavOptionsBuilder#getPopUpToId():
+    Getter should be on the built object, not the builder: method androidx.navigation.NavOptionsBuilder.getPopUpToId()
+GetterOnBuilder: androidx.navigation.NavOptionsBuilder#getPopUpToRoute():
+    Getter should be on the built object, not the builder: method androidx.navigation.NavOptionsBuilder.getPopUpToRoute()
 GetterOnBuilder: androidx.navigation.PopUpToBuilder#getInclusive():
     Getter should be on the built object, not the builder: method androidx.navigation.PopUpToBuilder.getInclusive()
 
@@ -115,6 +119,12 @@
     androidx.navigation.NavOptions does not declare a `isLaunchSingleTop()` method matching method androidx.navigation.NavOptions.Builder.setLaunchSingleTop(boolean)
 
 
+MissingJvmstatic: androidx.navigation.NavOptionsBuilder#popUpTo(String, kotlin.jvm.functions.Function1<? super androidx.navigation.PopUpToBuilder,kotlin.Unit>):
+    A Kotlin method with default parameter values should be annotated with @JvmOverloads for better Java interoperability; see https://android.github.io/kotlin-guides/interop.html#function-overloads-for-defaults
+MissingJvmstatic: androidx.navigation.NavOptionsBuilder#popUpTo(int, kotlin.jvm.functions.Function1<? super androidx.navigation.PopUpToBuilder,kotlin.Unit>):
+    A Kotlin method with default parameter values should be annotated with @JvmOverloads for better Java interoperability; see https://android.github.io/kotlin-guides/interop.html#function-overloads-for-defaults
+
+
 NullableCollection: androidx.navigation.NavAction#getDefaultArguments():
     Return type of method androidx.navigation.NavAction.getDefaultArguments() is a nullable collection (`android.os.Bundle`); must be non-null
 NullableCollection: androidx.navigation.NavBackStackEntry#getArguments():
@@ -123,6 +133,10 @@
     Return type of method androidx.navigation.Navigator.onSaveState() is a nullable collection (`android.os.Bundle`); must be non-null
 
 
+OptionalBuilderConstructorArgument: androidx.navigation.NavDestinationBuilder#NavDestinationBuilder(androidx.navigation.Navigator<? extends D>, String) parameter #1:
+    Builder constructor arguments must be mandatory (i.e. not @Nullable): parameter route in androidx.navigation.NavDestinationBuilder(androidx.navigation.Navigator<? extends D> navigator, String route)
+
+
 SetterReturnsThis: androidx.navigation.AnimBuilder#setEnter(int):
     Methods must return the builder object (return type androidx.navigation.AnimBuilder instead of void): method androidx.navigation.AnimBuilder.setEnter(int)
 SetterReturnsThis: androidx.navigation.AnimBuilder#setExit(int):
@@ -149,8 +163,6 @@
     Methods must return the builder object (return type androidx.navigation.NavDestinationBuilder<D> instead of void): method androidx.navigation.NavDestinationBuilder.setLabel(CharSequence)
 SetterReturnsThis: androidx.navigation.NavOptionsBuilder#setLaunchSingleTop(boolean):
     Methods must return the builder object (return type androidx.navigation.NavOptionsBuilder instead of void): method androidx.navigation.NavOptionsBuilder.setLaunchSingleTop(boolean)
-SetterReturnsThis: androidx.navigation.NavOptionsBuilder#setPopUpTo(int):
-    Methods must return the builder object (return type androidx.navigation.NavOptionsBuilder instead of void): method androidx.navigation.NavOptionsBuilder.setPopUpTo(int)
 SetterReturnsThis: androidx.navigation.PopUpToBuilder#setInclusive(boolean):
     Methods must return the builder object (return type androidx.navigation.PopUpToBuilder instead of void): method androidx.navigation.PopUpToBuilder.setInclusive(boolean)
 
diff --git a/navigation/navigation-common/api/current.txt b/navigation/navigation-common/api/current.txt
index d136e44..5f8904b 100644
--- a/navigation/navigation-common/api/current.txt
+++ b/navigation/navigation-common/api/current.txt
@@ -104,12 +104,14 @@
     method public android.os.Bundle? getArguments();
     method public androidx.lifecycle.ViewModelProvider.Factory getDefaultViewModelProviderFactory();
     method public androidx.navigation.NavDestination getDestination();
+    method public String getId();
     method public androidx.lifecycle.Lifecycle getLifecycle();
     method public androidx.lifecycle.SavedStateHandle getSavedStateHandle();
     method public androidx.savedstate.SavedStateRegistry getSavedStateRegistry();
     method public androidx.lifecycle.ViewModelStore getViewModelStore();
     property public final android.os.Bundle? arguments;
     property public final androidx.navigation.NavDestination destination;
+    property public final String id;
     property public final androidx.lifecycle.SavedStateHandle savedStateHandle;
   }
 
@@ -222,7 +224,7 @@
 
   @androidx.navigation.NavDestinationDsl public class NavDestinationBuilder<D extends androidx.navigation.NavDestination> {
     ctor public NavDestinationBuilder(androidx.navigation.Navigator<? extends D> navigator, @IdRes int id);
-    ctor public NavDestinationBuilder(androidx.navigation.Navigator<? extends D> navigator, String route);
+    ctor public NavDestinationBuilder(androidx.navigation.Navigator<? extends D> navigator, String? route);
     method public final void action(int actionId, kotlin.jvm.functions.Function1<? super androidx.navigation.NavActionBuilder,kotlin.Unit> actionBuilder);
     method public final void argument(String name, kotlin.jvm.functions.Function1<? super androidx.navigation.NavArgumentBuilder,kotlin.Unit> argumentBuilder);
     method public D build();
@@ -257,15 +259,21 @@
     method public final void addDestinations(androidx.navigation.NavDestination... nodes);
     method public final void clear();
     method public final androidx.navigation.NavDestination? findNode(@IdRes int resid);
-    method @IdRes public final int getStartDestination();
+    method public final androidx.navigation.NavDestination? findNode(String? route);
+    method @Deprecated @IdRes public final int getStartDestination();
+    method @IdRes public final int getStartDestinationId();
+    method public final String? getStartDestinationRoute();
     method public final java.util.Iterator<androidx.navigation.NavDestination> iterator();
     method public final void remove(androidx.navigation.NavDestination node);
     method public final void setStartDestination(int startDestId);
-    property @IdRes public final int startDestination;
+    method public final void setStartDestination(String startDestRoute);
+    property @IdRes public final int startDestinationId;
+    property public final String? startDestinationRoute;
   }
 
   @androidx.navigation.NavDestinationDsl public class NavGraphBuilder extends androidx.navigation.NavDestinationBuilder<androidx.navigation.NavGraph> {
     ctor public NavGraphBuilder(androidx.navigation.NavigatorProvider provider, @IdRes int id, @IdRes int startDestination);
+    ctor public NavGraphBuilder(androidx.navigation.NavigatorProvider provider, String startDestination, String? route);
     method public final void addDestination(androidx.navigation.NavDestination destination);
     method public androidx.navigation.NavGraph build();
     method public final <D extends androidx.navigation.NavDestination> void destination(androidx.navigation.NavDestinationBuilder<? extends D> navDestination);
@@ -276,12 +284,16 @@
 
   public final class NavGraphBuilderKt {
     method public static inline androidx.navigation.NavGraph navigation(androidx.navigation.NavigatorProvider, optional @IdRes int id, @IdRes int startDestination, kotlin.jvm.functions.Function1<? super androidx.navigation.NavGraphBuilder,kotlin.Unit> builder);
+    method public static inline androidx.navigation.NavGraph navigation(androidx.navigation.NavigatorProvider, String startDestination, optional String? route, kotlin.jvm.functions.Function1<? super androidx.navigation.NavGraphBuilder,kotlin.Unit> builder);
     method public static inline void navigation(androidx.navigation.NavGraphBuilder, @IdRes int id, @IdRes int startDestination, kotlin.jvm.functions.Function1<? super androidx.navigation.NavGraphBuilder,kotlin.Unit> builder);
+    method public static inline void navigation(androidx.navigation.NavGraphBuilder, String startDestination, String route, kotlin.jvm.functions.Function1<? super androidx.navigation.NavGraphBuilder,kotlin.Unit> builder);
   }
 
   public final class NavGraphKt {
     method public static operator boolean contains(androidx.navigation.NavGraph, @IdRes int id);
+    method public static operator boolean contains(androidx.navigation.NavGraph, String route);
     method public static inline operator androidx.navigation.NavDestination get(androidx.navigation.NavGraph, @IdRes int id);
+    method public static inline operator androidx.navigation.NavDestination get(androidx.navigation.NavGraph, String route);
     method public static inline operator void minusAssign(androidx.navigation.NavGraph, androidx.navigation.NavDestination node);
     method public static inline operator void plusAssign(androidx.navigation.NavGraph, androidx.navigation.NavDestination node);
     method public static inline operator void plusAssign(androidx.navigation.NavGraph, androidx.navigation.NavGraph other);
@@ -290,7 +302,6 @@
   @androidx.navigation.Navigator.Name("navigation") public class NavGraphNavigator extends androidx.navigation.Navigator<androidx.navigation.NavGraph> {
     ctor public NavGraphNavigator(androidx.navigation.NavigatorProvider navigatorProvider);
     method public androidx.navigation.NavGraph createDestination();
-    method public androidx.navigation.NavDestination? navigate(androidx.navigation.NavGraph destination, android.os.Bundle? args, androidx.navigation.NavOptions? navOptions, androidx.navigation.Navigator.Extras? navigatorExtras);
   }
 
   public final class NavOptions {
@@ -298,7 +309,9 @@
     method @AnimRes @AnimatorRes public int getExitAnim();
     method @AnimRes @AnimatorRes public int getPopEnterAnim();
     method @AnimRes @AnimatorRes public int getPopExitAnim();
-    method @IdRes public int getPopUpTo();
+    method @Deprecated @IdRes public int getPopUpTo();
+    method @IdRes public int getPopUpToId();
+    method public String? getPopUpToRoute();
     method public boolean isPopUpToInclusive();
     method public boolean shouldLaunchSingleTop();
     method public boolean shouldPopUpToSaveState();
@@ -307,7 +320,8 @@
     property @AnimRes @AnimatorRes public final int exitAnim;
     property @AnimRes @AnimatorRes public final int popEnterAnim;
     property @AnimRes @AnimatorRes public final int popExitAnim;
-    property @IdRes public final int popUpTo;
+    property @IdRes public final int popUpToId;
+    property public final String? popUpToRoute;
   }
 
   public static final class NavOptions.Builder {
@@ -320,6 +334,8 @@
     method public androidx.navigation.NavOptions.Builder setPopExitAnim(@AnimRes @AnimatorRes int popExitAnim);
     method public androidx.navigation.NavOptions.Builder setPopUpTo(@IdRes int destinationId, boolean inclusive, optional boolean saveState);
     method public androidx.navigation.NavOptions.Builder setPopUpTo(@IdRes int destinationId, boolean inclusive);
+    method public androidx.navigation.NavOptions.Builder setPopUpTo(String? route, boolean inclusive, optional boolean saveState);
+    method public androidx.navigation.NavOptions.Builder setPopUpTo(String? route, boolean inclusive);
     method public androidx.navigation.NavOptions.Builder setRestoreState(boolean restoreState);
   }
 
@@ -327,14 +343,19 @@
     ctor public NavOptionsBuilder();
     method public void anim(kotlin.jvm.functions.Function1<? super androidx.navigation.AnimBuilder,kotlin.Unit> animBuilder);
     method public boolean getLaunchSingleTop();
-    method public int getPopUpTo();
+    method @Deprecated public int getPopUpTo();
+    method public int getPopUpToId();
+    method public String? getPopUpToRoute();
     method public boolean getRestoreState();
-    method public void popUpTo(@IdRes int id, kotlin.jvm.functions.Function1<? super androidx.navigation.PopUpToBuilder,kotlin.Unit> popUpToBuilder);
+    method public void popUpTo(@IdRes int id, optional kotlin.jvm.functions.Function1<? super androidx.navigation.PopUpToBuilder,kotlin.Unit> popUpToBuilder);
+    method public void popUpTo(String route, optional kotlin.jvm.functions.Function1<? super androidx.navigation.PopUpToBuilder,kotlin.Unit> popUpToBuilder);
     method public void setLaunchSingleTop(boolean p);
-    method public void setPopUpTo(int value);
+    method @Deprecated public void setPopUpTo(int value);
     method public void setRestoreState(boolean p);
     property public final boolean launchSingleTop;
-    property public final int popUpTo;
+    property @Deprecated public final int popUpTo;
+    property public final int popUpToId;
+    property public final String? popUpToRoute;
     property public final boolean restoreState;
   }
 
diff --git a/navigation/navigation-common/api/public_plus_experimental_current.txt b/navigation/navigation-common/api/public_plus_experimental_current.txt
index 2eb2397..5159fc4 100644
--- a/navigation/navigation-common/api/public_plus_experimental_current.txt
+++ b/navigation/navigation-common/api/public_plus_experimental_current.txt
@@ -106,7 +106,7 @@
     method public android.os.Bundle? getArguments();
     method public androidx.lifecycle.ViewModelProvider.Factory getDefaultViewModelProviderFactory();
     method public androidx.navigation.NavDestination getDestination();
-    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public java.util.UUID getId();
+    method public String getId();
     method public androidx.lifecycle.Lifecycle getLifecycle();
     method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public androidx.lifecycle.Lifecycle.State getMaxLifecycle();
     method public androidx.lifecycle.SavedStateHandle getSavedStateHandle();
@@ -120,7 +120,7 @@
     method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public void updateState();
     property public final android.os.Bundle? arguments;
     property public final androidx.navigation.NavDestination destination;
-    property @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public final java.util.UUID id;
+    property public final String id;
     property @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public final androidx.lifecycle.Lifecycle.State maxLifecycle;
     property public final androidx.lifecycle.SavedStateHandle savedStateHandle;
   }
@@ -261,7 +261,7 @@
 
   @androidx.navigation.NavDestinationDsl public class NavDestinationBuilder<D extends androidx.navigation.NavDestination> {
     ctor public NavDestinationBuilder(androidx.navigation.Navigator<? extends D> navigator, @IdRes int id);
-    ctor public NavDestinationBuilder(androidx.navigation.Navigator<? extends D> navigator, String route);
+    ctor public NavDestinationBuilder(androidx.navigation.Navigator<? extends D> navigator, String? route);
     method public final void action(int actionId, kotlin.jvm.functions.Function1<? super androidx.navigation.NavActionBuilder,kotlin.Unit> actionBuilder);
     method public final void argument(String name, kotlin.jvm.functions.Function1<? super androidx.navigation.NavArgumentBuilder,kotlin.Unit> argumentBuilder);
     method public D build();
@@ -296,20 +296,25 @@
     method public final void addDestinations(androidx.navigation.NavDestination... nodes);
     method public final void clear();
     method public final androidx.navigation.NavDestination? findNode(@IdRes int resid);
-    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public final androidx.navigation.NavDestination? findNode(@IdRes int resid, boolean searchParents);
+    method public final androidx.navigation.NavDestination? findNode(String? route);
     method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public final androidx.collection.SparseArrayCompat<androidx.navigation.NavDestination> getNodes();
     method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public final String getStartDestDisplayName();
-    method @IdRes public final int getStartDestination();
+    method @Deprecated @IdRes public final int getStartDestination();
+    method @IdRes public final int getStartDestinationId();
+    method public final String? getStartDestinationRoute();
     method public final java.util.Iterator<androidx.navigation.NavDestination> iterator();
     method public final void remove(androidx.navigation.NavDestination node);
     method public final void setStartDestination(int startDestId);
+    method public final void setStartDestination(String startDestRoute);
     property @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public final androidx.collection.SparseArrayCompat<androidx.navigation.NavDestination> nodes;
     property @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public final String startDestDisplayName;
-    property @IdRes public final int startDestination;
+    property @IdRes public final int startDestinationId;
+    property public final String? startDestinationRoute;
   }
 
   @androidx.navigation.NavDestinationDsl public class NavGraphBuilder extends androidx.navigation.NavDestinationBuilder<androidx.navigation.NavGraph> {
     ctor public NavGraphBuilder(androidx.navigation.NavigatorProvider provider, @IdRes int id, @IdRes int startDestination);
+    ctor public NavGraphBuilder(androidx.navigation.NavigatorProvider provider, String startDestination, String? route);
     method public final void addDestination(androidx.navigation.NavDestination destination);
     method public androidx.navigation.NavGraph build();
     method public final <D extends androidx.navigation.NavDestination> void destination(androidx.navigation.NavDestinationBuilder<? extends D> navDestination);
@@ -320,12 +325,16 @@
 
   public final class NavGraphBuilderKt {
     method public static inline androidx.navigation.NavGraph navigation(androidx.navigation.NavigatorProvider, optional @IdRes int id, @IdRes int startDestination, kotlin.jvm.functions.Function1<? super androidx.navigation.NavGraphBuilder,kotlin.Unit> builder);
+    method public static inline androidx.navigation.NavGraph navigation(androidx.navigation.NavigatorProvider, String startDestination, optional String? route, kotlin.jvm.functions.Function1<? super androidx.navigation.NavGraphBuilder,kotlin.Unit> builder);
     method public static inline void navigation(androidx.navigation.NavGraphBuilder, @IdRes int id, @IdRes int startDestination, kotlin.jvm.functions.Function1<? super androidx.navigation.NavGraphBuilder,kotlin.Unit> builder);
+    method public static inline void navigation(androidx.navigation.NavGraphBuilder, String startDestination, String route, kotlin.jvm.functions.Function1<? super androidx.navigation.NavGraphBuilder,kotlin.Unit> builder);
   }
 
   public final class NavGraphKt {
     method public static operator boolean contains(androidx.navigation.NavGraph, @IdRes int id);
+    method public static operator boolean contains(androidx.navigation.NavGraph, String route);
     method public static inline operator androidx.navigation.NavDestination get(androidx.navigation.NavGraph, @IdRes int id);
+    method public static inline operator androidx.navigation.NavDestination get(androidx.navigation.NavGraph, String route);
     method public static inline operator void minusAssign(androidx.navigation.NavGraph, androidx.navigation.NavDestination node);
     method public static inline operator void plusAssign(androidx.navigation.NavGraph, androidx.navigation.NavDestination node);
     method public static inline operator void plusAssign(androidx.navigation.NavGraph, androidx.navigation.NavGraph other);
@@ -334,7 +343,6 @@
   @androidx.navigation.Navigator.Name("navigation") public class NavGraphNavigator extends androidx.navigation.Navigator<androidx.navigation.NavGraph> {
     ctor public NavGraphNavigator(androidx.navigation.NavigatorProvider navigatorProvider);
     method public androidx.navigation.NavGraph createDestination();
-    method public androidx.navigation.NavDestination? navigate(androidx.navigation.NavGraph destination, android.os.Bundle? args, androidx.navigation.NavOptions? navOptions, androidx.navigation.Navigator.Extras? navigatorExtras);
   }
 
   public final class NavOptions {
@@ -342,7 +350,9 @@
     method @AnimRes @AnimatorRes public int getExitAnim();
     method @AnimRes @AnimatorRes public int getPopEnterAnim();
     method @AnimRes @AnimatorRes public int getPopExitAnim();
-    method @IdRes public int getPopUpTo();
+    method @Deprecated @IdRes public int getPopUpTo();
+    method @IdRes public int getPopUpToId();
+    method public String? getPopUpToRoute();
     method public boolean isPopUpToInclusive();
     method public boolean shouldLaunchSingleTop();
     method public boolean shouldPopUpToSaveState();
@@ -351,7 +361,8 @@
     property @AnimRes @AnimatorRes public final int exitAnim;
     property @AnimRes @AnimatorRes public final int popEnterAnim;
     property @AnimRes @AnimatorRes public final int popExitAnim;
-    property @IdRes public final int popUpTo;
+    property @IdRes public final int popUpToId;
+    property public final String? popUpToRoute;
   }
 
   public static final class NavOptions.Builder {
@@ -364,6 +375,8 @@
     method public androidx.navigation.NavOptions.Builder setPopExitAnim(@AnimRes @AnimatorRes int popExitAnim);
     method public androidx.navigation.NavOptions.Builder setPopUpTo(@IdRes int destinationId, boolean inclusive, optional boolean saveState);
     method public androidx.navigation.NavOptions.Builder setPopUpTo(@IdRes int destinationId, boolean inclusive);
+    method public androidx.navigation.NavOptions.Builder setPopUpTo(String? route, boolean inclusive, optional boolean saveState);
+    method public androidx.navigation.NavOptions.Builder setPopUpTo(String? route, boolean inclusive);
     method public androidx.navigation.NavOptions.Builder setRestoreState(boolean restoreState);
   }
 
@@ -371,14 +384,19 @@
     ctor public NavOptionsBuilder();
     method public void anim(kotlin.jvm.functions.Function1<? super androidx.navigation.AnimBuilder,kotlin.Unit> animBuilder);
     method public boolean getLaunchSingleTop();
-    method public int getPopUpTo();
+    method @Deprecated public int getPopUpTo();
+    method public int getPopUpToId();
+    method public String? getPopUpToRoute();
     method public boolean getRestoreState();
-    method public void popUpTo(@IdRes int id, kotlin.jvm.functions.Function1<? super androidx.navigation.PopUpToBuilder,kotlin.Unit> popUpToBuilder);
+    method public void popUpTo(@IdRes int id, optional kotlin.jvm.functions.Function1<? super androidx.navigation.PopUpToBuilder,kotlin.Unit> popUpToBuilder);
+    method public void popUpTo(String route, optional kotlin.jvm.functions.Function1<? super androidx.navigation.PopUpToBuilder,kotlin.Unit> popUpToBuilder);
     method public void setLaunchSingleTop(boolean p);
-    method public void setPopUpTo(int value);
+    method @Deprecated public void setPopUpTo(int value);
     method public void setRestoreState(boolean p);
     property public final boolean launchSingleTop;
-    property public final int popUpTo;
+    property @Deprecated public final int popUpTo;
+    property public final int popUpToId;
+    property public final String? popUpToRoute;
     property public final boolean restoreState;
   }
 
diff --git a/navigation/navigation-common/api/restricted_current.txt b/navigation/navigation-common/api/restricted_current.txt
index d136e44..5f8904b 100644
--- a/navigation/navigation-common/api/restricted_current.txt
+++ b/navigation/navigation-common/api/restricted_current.txt
@@ -104,12 +104,14 @@
     method public android.os.Bundle? getArguments();
     method public androidx.lifecycle.ViewModelProvider.Factory getDefaultViewModelProviderFactory();
     method public androidx.navigation.NavDestination getDestination();
+    method public String getId();
     method public androidx.lifecycle.Lifecycle getLifecycle();
     method public androidx.lifecycle.SavedStateHandle getSavedStateHandle();
     method public androidx.savedstate.SavedStateRegistry getSavedStateRegistry();
     method public androidx.lifecycle.ViewModelStore getViewModelStore();
     property public final android.os.Bundle? arguments;
     property public final androidx.navigation.NavDestination destination;
+    property public final String id;
     property public final androidx.lifecycle.SavedStateHandle savedStateHandle;
   }
 
@@ -222,7 +224,7 @@
 
   @androidx.navigation.NavDestinationDsl public class NavDestinationBuilder<D extends androidx.navigation.NavDestination> {
     ctor public NavDestinationBuilder(androidx.navigation.Navigator<? extends D> navigator, @IdRes int id);
-    ctor public NavDestinationBuilder(androidx.navigation.Navigator<? extends D> navigator, String route);
+    ctor public NavDestinationBuilder(androidx.navigation.Navigator<? extends D> navigator, String? route);
     method public final void action(int actionId, kotlin.jvm.functions.Function1<? super androidx.navigation.NavActionBuilder,kotlin.Unit> actionBuilder);
     method public final void argument(String name, kotlin.jvm.functions.Function1<? super androidx.navigation.NavArgumentBuilder,kotlin.Unit> argumentBuilder);
     method public D build();
@@ -257,15 +259,21 @@
     method public final void addDestinations(androidx.navigation.NavDestination... nodes);
     method public final void clear();
     method public final androidx.navigation.NavDestination? findNode(@IdRes int resid);
-    method @IdRes public final int getStartDestination();
+    method public final androidx.navigation.NavDestination? findNode(String? route);
+    method @Deprecated @IdRes public final int getStartDestination();
+    method @IdRes public final int getStartDestinationId();
+    method public final String? getStartDestinationRoute();
     method public final java.util.Iterator<androidx.navigation.NavDestination> iterator();
     method public final void remove(androidx.navigation.NavDestination node);
     method public final void setStartDestination(int startDestId);
-    property @IdRes public final int startDestination;
+    method public final void setStartDestination(String startDestRoute);
+    property @IdRes public final int startDestinationId;
+    property public final String? startDestinationRoute;
   }
 
   @androidx.navigation.NavDestinationDsl public class NavGraphBuilder extends androidx.navigation.NavDestinationBuilder<androidx.navigation.NavGraph> {
     ctor public NavGraphBuilder(androidx.navigation.NavigatorProvider provider, @IdRes int id, @IdRes int startDestination);
+    ctor public NavGraphBuilder(androidx.navigation.NavigatorProvider provider, String startDestination, String? route);
     method public final void addDestination(androidx.navigation.NavDestination destination);
     method public androidx.navigation.NavGraph build();
     method public final <D extends androidx.navigation.NavDestination> void destination(androidx.navigation.NavDestinationBuilder<? extends D> navDestination);
@@ -276,12 +284,16 @@
 
   public final class NavGraphBuilderKt {
     method public static inline androidx.navigation.NavGraph navigation(androidx.navigation.NavigatorProvider, optional @IdRes int id, @IdRes int startDestination, kotlin.jvm.functions.Function1<? super androidx.navigation.NavGraphBuilder,kotlin.Unit> builder);
+    method public static inline androidx.navigation.NavGraph navigation(androidx.navigation.NavigatorProvider, String startDestination, optional String? route, kotlin.jvm.functions.Function1<? super androidx.navigation.NavGraphBuilder,kotlin.Unit> builder);
     method public static inline void navigation(androidx.navigation.NavGraphBuilder, @IdRes int id, @IdRes int startDestination, kotlin.jvm.functions.Function1<? super androidx.navigation.NavGraphBuilder,kotlin.Unit> builder);
+    method public static inline void navigation(androidx.navigation.NavGraphBuilder, String startDestination, String route, kotlin.jvm.functions.Function1<? super androidx.navigation.NavGraphBuilder,kotlin.Unit> builder);
   }
 
   public final class NavGraphKt {
     method public static operator boolean contains(androidx.navigation.NavGraph, @IdRes int id);
+    method public static operator boolean contains(androidx.navigation.NavGraph, String route);
     method public static inline operator androidx.navigation.NavDestination get(androidx.navigation.NavGraph, @IdRes int id);
+    method public static inline operator androidx.navigation.NavDestination get(androidx.navigation.NavGraph, String route);
     method public static inline operator void minusAssign(androidx.navigation.NavGraph, androidx.navigation.NavDestination node);
     method public static inline operator void plusAssign(androidx.navigation.NavGraph, androidx.navigation.NavDestination node);
     method public static inline operator void plusAssign(androidx.navigation.NavGraph, androidx.navigation.NavGraph other);
@@ -290,7 +302,6 @@
   @androidx.navigation.Navigator.Name("navigation") public class NavGraphNavigator extends androidx.navigation.Navigator<androidx.navigation.NavGraph> {
     ctor public NavGraphNavigator(androidx.navigation.NavigatorProvider navigatorProvider);
     method public androidx.navigation.NavGraph createDestination();
-    method public androidx.navigation.NavDestination? navigate(androidx.navigation.NavGraph destination, android.os.Bundle? args, androidx.navigation.NavOptions? navOptions, androidx.navigation.Navigator.Extras? navigatorExtras);
   }
 
   public final class NavOptions {
@@ -298,7 +309,9 @@
     method @AnimRes @AnimatorRes public int getExitAnim();
     method @AnimRes @AnimatorRes public int getPopEnterAnim();
     method @AnimRes @AnimatorRes public int getPopExitAnim();
-    method @IdRes public int getPopUpTo();
+    method @Deprecated @IdRes public int getPopUpTo();
+    method @IdRes public int getPopUpToId();
+    method public String? getPopUpToRoute();
     method public boolean isPopUpToInclusive();
     method public boolean shouldLaunchSingleTop();
     method public boolean shouldPopUpToSaveState();
@@ -307,7 +320,8 @@
     property @AnimRes @AnimatorRes public final int exitAnim;
     property @AnimRes @AnimatorRes public final int popEnterAnim;
     property @AnimRes @AnimatorRes public final int popExitAnim;
-    property @IdRes public final int popUpTo;
+    property @IdRes public final int popUpToId;
+    property public final String? popUpToRoute;
   }
 
   public static final class NavOptions.Builder {
@@ -320,6 +334,8 @@
     method public androidx.navigation.NavOptions.Builder setPopExitAnim(@AnimRes @AnimatorRes int popExitAnim);
     method public androidx.navigation.NavOptions.Builder setPopUpTo(@IdRes int destinationId, boolean inclusive, optional boolean saveState);
     method public androidx.navigation.NavOptions.Builder setPopUpTo(@IdRes int destinationId, boolean inclusive);
+    method public androidx.navigation.NavOptions.Builder setPopUpTo(String? route, boolean inclusive, optional boolean saveState);
+    method public androidx.navigation.NavOptions.Builder setPopUpTo(String? route, boolean inclusive);
     method public androidx.navigation.NavOptions.Builder setRestoreState(boolean restoreState);
   }
 
@@ -327,14 +343,19 @@
     ctor public NavOptionsBuilder();
     method public void anim(kotlin.jvm.functions.Function1<? super androidx.navigation.AnimBuilder,kotlin.Unit> animBuilder);
     method public boolean getLaunchSingleTop();
-    method public int getPopUpTo();
+    method @Deprecated public int getPopUpTo();
+    method public int getPopUpToId();
+    method public String? getPopUpToRoute();
     method public boolean getRestoreState();
-    method public void popUpTo(@IdRes int id, kotlin.jvm.functions.Function1<? super androidx.navigation.PopUpToBuilder,kotlin.Unit> popUpToBuilder);
+    method public void popUpTo(@IdRes int id, optional kotlin.jvm.functions.Function1<? super androidx.navigation.PopUpToBuilder,kotlin.Unit> popUpToBuilder);
+    method public void popUpTo(String route, optional kotlin.jvm.functions.Function1<? super androidx.navigation.PopUpToBuilder,kotlin.Unit> popUpToBuilder);
     method public void setLaunchSingleTop(boolean p);
-    method public void setPopUpTo(int value);
+    method @Deprecated public void setPopUpTo(int value);
     method public void setRestoreState(boolean p);
     property public final boolean launchSingleTop;
-    property public final int popUpTo;
+    property @Deprecated public final int popUpTo;
+    property public final int popUpToId;
+    property public final String? popUpToRoute;
     property public final boolean restoreState;
   }
 
diff --git a/navigation/navigation-common/build.gradle b/navigation/navigation-common/build.gradle
index 5ecb6d0..fe1af8a 100644
--- a/navigation/navigation-common/build.gradle
+++ b/navigation/navigation-common/build.gradle
@@ -36,6 +36,8 @@
     implementation("androidx.collection:collection-ktx:1.1.0")
 
     api(KOTLIN_STDLIB)
+    testImplementation(project(":navigation:navigation-testing"))
+    testImplementation("androidx.arch.core:core-testing:2.1.0")
     testImplementation(JUNIT)
     testImplementation(MOCKITO_CORE)
     testImplementation(TRUTH)
diff --git a/navigation/navigation-common/lint-baseline.xml b/navigation/navigation-common/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/navigation/navigation-common/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/navigation/navigation-common/src/androidTest/java/androidx/navigation/NavDestinationBuilderTest.kt b/navigation/navigation-common/src/androidTest/java/androidx/navigation/NavDestinationBuilderTest.kt
index fd6d922..68fa76a 100644
--- a/navigation/navigation-common/src/androidTest/java/androidx/navigation/NavDestinationBuilderTest.kt
+++ b/navigation/navigation-common/src/androidTest/java/androidx/navigation/NavDestinationBuilderTest.kt
@@ -94,7 +94,7 @@
             action(ACTION_ID) {
                 destinationId = DESTINATION_ID
                 navOptions {
-                    popUpTo = DESTINATION_ID
+                    popUpTo(DESTINATION_ID)
                 }
                 defaultArguments[ACTION_ARGUMENT_KEY] = ACTION_ARGUMENT_VALUE
             }
@@ -104,7 +104,7 @@
             .that(action)
             .isNotNull()
         assertWithMessage("NavAction should have NavOptions set")
-            .that(action?.navOptions?.popUpTo)
+            .that(action?.navOptions?.popUpToId)
             .isEqualTo(DESTINATION_ID)
         assertWithMessage("NavAction should have its default argument set")
             .that(action?.defaultArguments?.getString(ACTION_ARGUMENT_KEY))
diff --git a/navigation/navigation-common/src/androidTest/java/androidx/navigation/NavGraphAndroidTest.kt b/navigation/navigation-common/src/androidTest/java/androidx/navigation/NavGraphAndroidTest.kt
index a0cdb66..d47d5f2 100644
--- a/navigation/navigation-common/src/androidTest/java/androidx/navigation/NavGraphAndroidTest.kt
+++ b/navigation/navigation-common/src/androidTest/java/androidx/navigation/NavGraphAndroidTest.kt
@@ -26,8 +26,10 @@
 class NavGraphAndroidTest {
     companion object {
         const val DESTINATION_ID = 1
+        const val DESTINATION_ROUTE = "destination_route"
         const val DESTINATION_LABEL = "test_label"
         const val GRAPH_ID = 2
+        const val GRAPH_ROUTE = "graph_route"
         const val GRAPH_LABEL = "graph_label"
     }
 
@@ -240,7 +242,7 @@
             .createDestination().apply {
                 id = GRAPH_ID
                 label = GRAPH_LABEL
-                startDestination = DESTINATION_ID
+                setStartDestination(DESTINATION_ID)
             }
         val expected = "NavGraph(0x${GRAPH_ID.toString(16)}) label=$GRAPH_LABEL " +
             "startDestination=0x${DESTINATION_ID.toString(16)}"
@@ -248,6 +250,24 @@
     }
 
     @Test
+    fun toStringStartDestRouteOnly() {
+        val navigatorProvider = NavigatorProvider().apply {
+            addNavigator(NavGraphNavigator(this))
+            addNavigator(NoOpNavigator())
+        }
+        val graph = navigatorProvider.getNavigator(NavGraphNavigator::class.java)
+            .createDestination().apply {
+                route = GRAPH_ROUTE
+                id = GRAPH_ID
+                label = GRAPH_LABEL
+                setStartDestination(DESTINATION_ROUTE)
+            }
+        val expected = "NavGraph(0x${GRAPH_ID.toString(16)}) route=$GRAPH_ROUTE " +
+            "label=$GRAPH_LABEL startDestination=$DESTINATION_ROUTE"
+        assertThat(graph.toString()).isEqualTo(expected)
+    }
+
+    @Test
     fun toStringStartDestInNodes() {
         val navigatorProvider = NavigatorProvider().apply {
             addNavigator(NavGraphNavigator(this))
@@ -262,7 +282,7 @@
             .createDestination().apply {
                 id = GRAPH_ID
                 label = GRAPH_LABEL
-                startDestination = DESTINATION_ID
+                setStartDestination(DESTINATION_ID)
                 addDestination(destination)
             }
         val expected = "NavGraph(0x${GRAPH_ID.toString(16)}) label=$GRAPH_LABEL " +
@@ -270,4 +290,82 @@
             "label=$DESTINATION_LABEL}"
         assertThat(graph.toString()).isEqualTo(expected)
     }
+
+    @Test
+    fun toStringStartDestInNodesRoute() {
+        val navigatorProvider = NavigatorProvider().apply {
+            addNavigator(NavGraphNavigator(this))
+            addNavigator(NoOpNavigator())
+        }
+        val destination = navigatorProvider.getNavigator(NoOpNavigator::class.java)
+            .createDestination().apply {
+                route = DESTINATION_ROUTE
+                id = DESTINATION_ID
+                label = DESTINATION_LABEL
+            }
+        val graph = navigatorProvider.getNavigator(NavGraphNavigator::class.java)
+            .createDestination().apply {
+                route = GRAPH_ROUTE
+                id = GRAPH_ID
+                label = GRAPH_LABEL
+                setStartDestination(DESTINATION_ROUTE)
+                setStartDestination(DESTINATION_ID)
+                addDestination(destination)
+            }
+        val expected = "NavGraph(0x${GRAPH_ID.toString(16)}) route=$GRAPH_ROUTE " +
+            "label=$GRAPH_LABEL " +
+            "startDestination={NavDestination(0x${DESTINATION_ID.toString(16)}) " +
+            "route=$DESTINATION_ROUTE label=$DESTINATION_LABEL}"
+        assertThat(graph.toString()).isEqualTo(expected)
+    }
+
+    @Test
+    fun toStringStartDestInNodesRouteWithStartDestID() {
+        val navigatorProvider = NavigatorProvider().apply {
+            addNavigator(NavGraphNavigator(this))
+            addNavigator(NoOpNavigator())
+        }
+        val destination = navigatorProvider.getNavigator(NoOpNavigator::class.java)
+            .createDestination().apply {
+                route = DESTINATION_ROUTE
+                label = DESTINATION_LABEL
+            }
+        val graph = navigatorProvider.getNavigator(NavGraphNavigator::class.java)
+            .createDestination().apply {
+                route = GRAPH_ROUTE
+                id = GRAPH_ID
+                label = GRAPH_LABEL
+                setStartDestination(DESTINATION_ROUTE)
+                setStartDestination(DESTINATION_ID)
+                addDestination(destination)
+            }
+        val expected = "NavGraph(0x${GRAPH_ID.toString(16)}) route=$GRAPH_ROUTE " +
+            "label=$GRAPH_LABEL startDestination=0x${DESTINATION_ID.toString(16)}"
+        assertThat(graph.toString()).isEqualTo(expected)
+    }
+
+    @Test
+    fun toStringStartDestInNodesRouteWithID() {
+        val navigatorProvider = NavigatorProvider().apply {
+            addNavigator(NavGraphNavigator(this))
+            addNavigator(NoOpNavigator())
+        }
+        val destination = navigatorProvider.getNavigator(NoOpNavigator::class.java)
+            .createDestination().apply {
+                route = DESTINATION_ROUTE
+                id = DESTINATION_ID
+                label = DESTINATION_LABEL
+            }
+        val graph = navigatorProvider.getNavigator(NavGraphNavigator::class.java)
+            .createDestination().apply {
+                route = GRAPH_ROUTE
+                id = GRAPH_ID
+                label = GRAPH_LABEL
+                setStartDestination(DESTINATION_ROUTE)
+                addDestination(destination)
+            }
+        val expected = "NavGraph(0x${GRAPH_ID.toString(16)}) route=$GRAPH_ROUTE " +
+            "label=$GRAPH_LABEL startDestination=$DESTINATION_ROUTE"
+        assertThat(graph.toString()).isEqualTo(expected)
+    }
 }
diff --git a/navigation/navigation-common/src/androidTest/java/androidx/navigation/NavGraphBuilderTest.kt b/navigation/navigation-common/src/androidTest/java/androidx/navigation/NavGraphBuilderTest.kt
index cef5597..cb75a08 100644
--- a/navigation/navigation-common/src/androidTest/java/androidx/navigation/NavGraphBuilderTest.kt
+++ b/navigation/navigation-common/src/androidTest/java/androidx/navigation/NavGraphBuilderTest.kt
@@ -43,6 +43,18 @@
     }
 
     @Test
+    fun navigationRoute() {
+        val graph = provider.navigation(
+            startDestination = DESTINATION_ROUTE
+        ) {
+            navDestination(DESTINATION_ROUTE) {}
+        }
+        assertWithMessage("Destination should be added to the graph")
+            .that(DESTINATION_ROUTE in graph)
+            .isTrue()
+    }
+
+    @Test
     fun navigationUnaryPlus() {
         val graph = provider.navigation(startDestination = DESTINATION_ID) {
             +provider[NoOpNavigator::class].createDestination().apply {
@@ -55,6 +67,20 @@
     }
 
     @Test
+    fun navigationUnaryPlusRoute() {
+        val graph = provider.navigation(
+            startDestination = DESTINATION_ROUTE
+        ) {
+            +provider[NoOpNavigator::class].createDestination().apply {
+                route = DESTINATION_ROUTE
+            }
+        }
+        assertWithMessage("Destination should be added to the graph")
+            .that(DESTINATION_ROUTE in graph)
+            .isTrue()
+    }
+
+    @Test
     fun navigationAddDestination() {
         val graph = provider.navigation(startDestination = DESTINATION_ID) {
             val destination = provider[NoOpNavigator::class].createDestination().apply {
@@ -67,6 +93,21 @@
             .isTrue()
     }
 
+    @Test
+    fun navigationAddDestinationRoute() {
+        val graph = provider.navigation(
+            startDestination = DESTINATION_ROUTE
+        ) {
+            val destination = provider[NoOpNavigator::class].createDestination().apply {
+                route = DESTINATION_ROUTE
+            }
+            addDestination(destination)
+        }
+        assertWithMessage("Destination should be added to the graph")
+            .that(DESTINATION_ROUTE in graph)
+            .isTrue()
+    }
+
     @Test(expected = IllegalStateException::class)
     fun navigationMissingStartDestination() {
         provider.navigation(startDestination = 0) {
@@ -75,6 +116,14 @@
         fail("NavGraph should throw IllegalStateException if startDestination is zero")
     }
 
+    @Test(expected = IllegalArgumentException::class)
+    fun navigationMissingStartDestinationRoute() {
+        provider.navigation(startDestination = "") {
+            navDestination(DESTINATION_ROUTE) {}
+        }
+        fail("NavGraph should throw IllegalStateException if no startDestinationRoute is set")
+    }
+
     @Test
     fun navigationNested() {
         val graph = provider.navigation(startDestination = DESTINATION_ID) {
@@ -86,10 +135,24 @@
             .that(DESTINATION_ID in graph)
             .isTrue()
     }
+
+    @Test
+    fun navigationNestedRoute() {
+        val graph = provider.navigation(startDestination = DESTINATION_ROUTE) {
+            navigation(startDestination = SECOND_DESTINATION_ROUTE, route = DESTINATION_ROUTE) {
+                navDestination(SECOND_DESTINATION_ROUTE) {}
+            }
+        }
+        assertWithMessage("Destination should be added to the graph")
+            .that(DESTINATION_ROUTE in graph)
+            .isTrue()
+    }
 }
 
 private const val DESTINATION_ID = 1
 private const val SECOND_DESTINATION_ID = 2
+private const val DESTINATION_ROUTE = "first"
+private const val SECOND_DESTINATION_ROUTE = "second"
 
 /**
  * Create a base NavDestination. Generally, only subtypes of NavDestination should be
@@ -99,3 +162,12 @@
     @IdRes id: Int,
     builder: NavDestinationBuilder<NavDestination>.() -> Unit
 ) = destination(NavDestinationBuilder(provider[NoOpNavigator::class], id).apply(builder))
+
+/**
+ * Create a base NavDestination. Generally, only subtypes of NavDestination should be
+ * added to a NavGraph (hence why this is not in the common-ktx library)
+ */
+fun NavGraphBuilder.navDestination(
+    route: String,
+    builder: NavDestinationBuilder<NavDestination>.() -> Unit
+) = destination(NavDestinationBuilder(provider[NoOpNavigator::class], route).apply(builder))
diff --git a/navigation/navigation-common/src/androidTest/java/androidx/navigation/NavGraphTest.kt b/navigation/navigation-common/src/androidTest/java/androidx/navigation/NavGraphTest.kt
index 6eb1a90c..c0b072a 100644
--- a/navigation/navigation-common/src/androidTest/java/androidx/navigation/NavGraphTest.kt
+++ b/navigation/navigation-common/src/androidTest/java/androidx/navigation/NavGraphTest.kt
@@ -75,6 +75,28 @@
             .isFalse()
     }
 
+    @Test
+    fun plusAssignGraphRoute() {
+        val graph = NavGraph(navGraphNavigator)
+        val other = NavGraph(navGraphNavigator)
+        other += navigator.createDestination().apply { route = DESTINATION_ROUTE }
+        other += navigator.createDestination().apply { route = SECOND_DESTINATION_ROUTE }
+        graph += other
+        assertWithMessage("NavGraph should have destination1 from other")
+            .that(DESTINATION_ROUTE in graph)
+            .isTrue()
+        assertWithMessage("other nav graph should not have destination1")
+            .that(DESTINATION_ROUTE in other)
+            .isFalse()
+
+        assertWithMessage("NavGraph should have destination2 from other")
+            .that(SECOND_DESTINATION_ROUTE in graph)
+            .isTrue()
+        assertWithMessage("other nav graph should not have destination2")
+            .that(SECOND_DESTINATION_ROUTE in other)
+            .isFalse()
+    }
+
     @Test(expected = IllegalArgumentException::class)
     fun getIllegalArgumentException() {
         val graph = NavGraph(navGraphNavigator)
@@ -84,3 +106,5 @@
 
 private const val DESTINATION_ID = 1
 private const val SECOND_DESTINATION_ID = 2
+private const val DESTINATION_ROUTE = "first"
+private const val SECOND_DESTINATION_ROUTE = "second"
diff --git a/navigation/navigation-common/src/androidTest/java/androidx/navigation/NavOptionsBuilderTest.kt b/navigation/navigation-common/src/androidTest/java/androidx/navigation/NavOptionsBuilderTest.kt
index f62b37e..bf40bfd 100644
--- a/navigation/navigation-common/src/androidTest/java/androidx/navigation/NavOptionsBuilderTest.kt
+++ b/navigation/navigation-common/src/androidTest/java/androidx/navigation/NavOptionsBuilderTest.kt
@@ -49,10 +49,10 @@
     @Test
     fun popUpTo() {
         val navOptions = navOptions {
-            popUpTo = DESTINATION_ID
+            popUpTo(DESTINATION_ID)
         }
         assertWithMessage("NavOptions should have popUpTo destination id set")
-            .that(navOptions.popUpTo)
+            .that(navOptions.popUpToId)
             .isEqualTo(DESTINATION_ID)
         assertWithMessage("NavOptions should have isPopUpToInclusive false by default")
             .that(navOptions.isPopUpToInclusive())
@@ -63,6 +63,19 @@
     }
 
     @Test
+    fun popUpToRoute() {
+        val navOptions = navOptions {
+            popUpTo(DESTINATION_ROUTE)
+        }
+        assertWithMessage("NavOptions should have popUpTo destination route set")
+            .that(navOptions.popUpToRoute)
+            .isEqualTo(DESTINATION_ROUTE)
+        assertWithMessage("NavOptions should have isPopUpToInclusive false by default")
+            .that(navOptions.isPopUpToInclusive())
+            .isFalse()
+    }
+
+    @Test
     fun popUpToBuilder() {
         val navOptions = navOptions {
             popUpTo(DESTINATION_ID) {
@@ -71,7 +84,7 @@
             }
         }
         assertWithMessage("NavOptions should have popUpTo destination id set")
-            .that(navOptions.popUpTo)
+            .that(navOptions.popUpToId)
             .isEqualTo(DESTINATION_ID)
         assertWithMessage("NavOptions should have isPopUpToInclusive set")
             .that(navOptions.isPopUpToInclusive())
@@ -82,6 +95,21 @@
     }
 
     @Test
+    fun popUpToRouteInclusive() {
+        val navOptions = navOptions {
+            popUpTo(DESTINATION_ROUTE) {
+                inclusive = true
+            }
+        }
+        assertWithMessage("NavOptions should have popUpTo destination id set")
+            .that(navOptions.popUpToRoute)
+            .isEqualTo(DESTINATION_ROUTE)
+        assertWithMessage("NavOptions should have isPopUpToInclusive set")
+            .that(navOptions.isPopUpToInclusive())
+            .isTrue()
+    }
+
+    @Test
     fun anim() {
         val navOptions = navOptions {
             anim {
@@ -107,6 +135,7 @@
 }
 
 private const val DESTINATION_ID = 1
+private const val DESTINATION_ROUTE = "destination_route"
 
 private const val ENTER_ANIM_ID = 10
 private const val EXIT_ANIM_ID = 11
diff --git a/navigation/navigation-common/src/main/java/androidx/navigation/NavBackStackEntry.kt b/navigation/navigation-common/src/main/java/androidx/navigation/NavBackStackEntry.kt
index a0e13a8..59e3987 100644
--- a/navigation/navigation-common/src/main/java/androidx/navigation/NavBackStackEntry.kt
+++ b/navigation/navigation-common/src/main/java/androidx/navigation/NavBackStackEntry.kt
@@ -57,9 +57,11 @@
     public var arguments: Bundle? = null,
     navControllerLifecycleOwner: LifecycleOwner? = null,
     private val viewModelStoreProvider: NavViewModelStoreProvider? = null,
-    // Internal unique name for this navBackStackEntry;
-    @get:RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-    public val id: UUID = UUID.randomUUID(),
+    /**
+     * Gets the unique ID that serves as the identity of this entry
+     * @return the unique ID of this entry
+     */
+    public val id: String = UUID.randomUUID().toString(),
     private val savedState: Bundle? = null
 ) : LifecycleOwner,
     ViewModelStoreOwner,
@@ -81,7 +83,7 @@
             arguments: Bundle? = null,
             navControllerLifecycleOwner: LifecycleOwner? = null,
             viewModelStoreProvider: NavViewModelStoreProvider? = null,
-            id: UUID = UUID.randomUUID(),
+            id: String = UUID.randomUUID().toString(),
             savedState: Bundle? = null
         ): NavBackStackEntry = NavBackStackEntry(
             context, destination, arguments,
diff --git a/navigation/navigation-common/src/main/java/androidx/navigation/NavDestination.kt b/navigation/navigation-common/src/main/java/androidx/navigation/NavDestination.kt
index 12b750b..963c1f2 100644
--- a/navigation/navigation-common/src/main/java/androidx/navigation/NavDestination.kt
+++ b/navigation/navigation-common/src/main/java/androidx/navigation/NavDestination.kt
@@ -389,7 +389,7 @@
                 hierarchy.addFirst(current)
                 break
             }
-            if (parent == null || parent.startDestination != current.id) {
+            if (parent == null || parent.startDestinationId != current.id) {
                 hierarchy.addFirst(current)
             }
             current = parent
diff --git a/navigation/navigation-common/src/main/java/androidx/navigation/NavDestinationBuilder.kt b/navigation/navigation-common/src/main/java/androidx/navigation/NavDestinationBuilder.kt
index 909f425..ceab39f 100644
--- a/navigation/navigation-common/src/main/java/androidx/navigation/NavDestinationBuilder.kt
+++ b/navigation/navigation-common/src/main/java/androidx/navigation/NavDestinationBuilder.kt
@@ -70,7 +70,7 @@
      *
      * @return the newly constructed [NavDestination]
      */
-    public constructor(navigator: Navigator<out D>, route: String) :
+    public constructor(navigator: Navigator<out D>, route: String?) :
         this(navigator, -1, route)
 
     /**
diff --git a/navigation/navigation-common/src/main/java/androidx/navigation/NavGraph.kt b/navigation/navigation-common/src/main/java/androidx/navigation/NavGraph.kt
index c826aa0..7932446 100644
--- a/navigation/navigation-common/src/main/java/androidx/navigation/NavGraph.kt
+++ b/navigation/navigation-common/src/main/java/androidx/navigation/NavGraph.kt
@@ -55,7 +55,7 @@
             attrs,
             R.styleable.NavGraphNavigator
         ).use {
-            startDestination = it.getResourceId(R.styleable.NavGraphNavigator_startDestination, 0)
+            startDestinationId = it.getResourceId(R.styleable.NavGraphNavigator_startDestination, 0)
             startDestIdName = getDisplayName(context, startDestId)
         }
     }
@@ -86,9 +86,15 @@
      */
     public fun addDestination(node: NavDestination) {
         val id = node.id
-        require(id != 0) {
-            "Destinations must have an id. Call setId() or include an android:id in your " +
-                "navigation XML."
+        val innerRoute = node.route
+        require(id != 0 || innerRoute != null) {
+            "Destinations must have an id or route. Call setId(), setRoute(), or include an " +
+                "android:id or app:route in your navigation XML."
+        }
+        if (route != null) {
+            require(innerRoute != route) {
+                "Destination $node cannot have the same route as graph $this"
+            }
         }
         require(id != this.id) { "Destination $node cannot have the same id as graph $this" }
         val existingDestination = nodes[id]
@@ -152,6 +158,20 @@
         return findNode(resid, true)
     }
 
+    /**
+     * Finds a destination in the collection by route. This will recursively check the
+     * [parent][getParent] of this navigation graph if node is not found in this navigation graph.
+     *
+     * @param route Route to locate
+     * @return the node with route
+     */
+    public fun findNode(route: String?): NavDestination? {
+        return if (!route.isNullOrBlank()) findNode(route, true) else null
+    }
+
+    /**
+     * @hide
+     */
     @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
     public fun findNode(@IdRes resid: Int, searchParents: Boolean): NavDestination? {
         val destination = nodes[resid]
@@ -162,6 +182,19 @@
     }
 
     /**
+     * @hide
+     */
+    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+    public fun findNode(route: String, searchParents: Boolean): NavDestination? {
+        val id = createRoute(route).hashCode()
+        val destination = nodes[id]
+        // Search the parent for the NavDestination if it is not a child of this navigation graph
+        // and searchParents is true
+        return destination
+            ?: if (searchParents && parent != null) parent!!.findNode(route) else null
+    }
+
+    /**
      * @throws NoSuchElementException if there no more elements
      */
     public final override fun iterator(): MutableIterator<NavDestination> {
@@ -239,8 +272,18 @@
          */
         get() = if (id != 0) super.displayName else "the root navigation"
 
+    /**
+     * Returns the starting destination for this NavGraph. When navigating to the NavGraph, this
+     * destination is the one the user will initially see.
+     *
+     * @return the start destination
+     */
+    @IdRes
+    @Deprecated("Use getStartDestinationId instead.", ReplaceWith("startDestinationId"))
+    public fun getStartDestination(): Int = startDestinationId
+
     @get:IdRes
-    public var startDestination: Int
+    public var startDestinationId: Int
         /**
          * Returns the starting destination for this NavGraph. When navigating to the NavGraph, this
          * destination is the one the user will initially see.
@@ -248,20 +291,64 @@
          * @return the start destination
          */
         get() = startDestId
-        /**
-         * Sets the starting destination for this NavGraph.
-         *
-         * @param startDestId The id of the destination to be shown when navigating to this
-         *                    NavGraph.
-         */
-        set(startDestId) {
+        private set(startDestId) {
             require(startDestId != id) {
                 "Start destination $startDestId cannot use the same id as the graph $this"
             }
+            if (startDestinationRoute != null) {
+                startDestinationRoute = null
+            }
             this.startDestId = startDestId
             startDestIdName = null
         }
 
+    /**
+     * Sets the starting destination for this NavGraph.
+     *
+     * This will clear any previously set [startDestinationRoute]
+     *
+     * @param startDestId The id of the destination to be shown when navigating to this
+     *                    NavGraph.
+     */
+    public fun setStartDestination(startDestId: Int) {
+        startDestinationId = startDestId
+    }
+
+    /**
+     * Sets the starting destination for this NavGraph.
+     *
+     * This will override any previously set [startDestinationId]
+     *
+     * @param startDestRoute The route of the destination to be shown when navigating to this
+     *                    NavGraph.
+     */
+    public fun setStartDestination(startDestRoute: String) {
+        startDestinationRoute = startDestRoute
+    }
+
+    /**
+     * Returns the route for the starting destination for this NavGraph. When navigating to the
+     * NavGraph, this destination is the one the user will initially see.
+     *
+     * @return the start destination
+     */
+    public var startDestinationRoute: String? = null
+        private set(startDestRoute) {
+            startDestId = if (startDestRoute == null) {
+                0
+            } else {
+                require(startDestRoute != route) {
+                    "Start destination $startDestRoute cannot use the same route as the graph $this"
+                }
+                require(startDestRoute.isNotBlank()) {
+                    "Cannot have an empty start destination route"
+                }
+                val internalRoute = createRoute(startDestRoute)
+                internalRoute.hashCode()
+            }
+            field = startDestRoute
+        }
+
     public val startDestDisplayName: String
         @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
         get() {
@@ -274,14 +361,13 @@
     public override fun toString(): String {
         val sb = StringBuilder()
         sb.append(super.toString())
+        val startDestination = findNode(startDestinationRoute) ?: findNode(startDestinationId)
         sb.append(" startDestination=")
-        val startDestination = findNode(startDestination)
         if (startDestination == null) {
-            if (startDestIdName == null) {
-                sb.append("0x")
-                sb.append(Integer.toHexString(startDestId))
-            } else {
-                sb.append(startDestIdName)
+            when {
+                startDestinationRoute != null -> sb.append(startDestinationRoute)
+                startDestIdName != null -> sb.append(startDestIdName)
+                else -> sb.append("0x${Integer.toHexString(startDestId)}")
             }
         } else {
             sb.append("{")
@@ -301,9 +387,22 @@
 public inline operator fun NavGraph.get(@IdRes id: Int): NavDestination =
     findNode(id) ?: throw IllegalArgumentException("No destination for $id was found in $this")
 
+/**
+ * Returns the destination with `route`.
+ *
+ * @throws IllegalArgumentException if no destination is found with that route.
+ */
+@Suppress("NOTHING_TO_INLINE")
+public inline operator fun NavGraph.get(route: String): NavDestination =
+    findNode(route)
+        ?: throw IllegalArgumentException("No destination for $route was found in $this")
+
 /** Returns `true` if a destination with `id` is found in this navigation graph. */
 public operator fun NavGraph.contains(@IdRes id: Int): Boolean = findNode(id) != null
 
+/** Returns `true` if a destination with `route` is found in this navigation graph. */
+public operator fun NavGraph.contains(route: String): Boolean = findNode(route) != null
+
 /**
  * Adds a destination to this NavGraph. The destination must have an
  * [id][NavDestination.getId] set.
diff --git a/navigation/navigation-common/src/main/java/androidx/navigation/NavGraphBuilder.kt b/navigation/navigation-common/src/main/java/androidx/navigation/NavGraphBuilder.kt
index e357290..cfe6d65 100644
--- a/navigation/navigation-common/src/main/java/androidx/navigation/NavGraphBuilder.kt
+++ b/navigation/navigation-common/src/main/java/androidx/navigation/NavGraphBuilder.kt
@@ -20,6 +20,12 @@
 
 /**
  * Construct a new [NavGraph]
+ *
+ * @param id the destination's unique id
+ * @param startDestination the starting destination for this NavGraph
+ * @param builder the builder used to construct the graph
+ *
+ * @return the newly constructed NavGraph
  */
 public inline fun NavigatorProvider.navigation(
     @IdRes id: Int = 0,
@@ -28,7 +34,29 @@
 ): NavGraph = NavGraphBuilder(this, id, startDestination).apply(builder).build()
 
 /**
+ * Construct a new [NavGraph]
+ *
+ * @param startDestination the starting destination's route for this NavGraph
+ * @param route the destination's unique route
+ * @param builder the builder used to construct the graph
+ *
+ * @return the newly constructed NavGraph
+ */
+public inline fun NavigatorProvider.navigation(
+    startDestination: String,
+    route: String? = null,
+    builder: NavGraphBuilder.() -> Unit
+): NavGraph = NavGraphBuilder(this, startDestination, route).apply(builder)
+    .build()
+
+/**
  * Construct a nested [NavGraph]
+ *
+ * @param id the destination's unique id
+ * @param startDestination the starting destination for this NavGraph
+ * @param builder the builder used to construct the graph
+ *
+ * @return the newly constructed nested NavGraph
  */
 public inline fun NavGraphBuilder.navigation(
     @IdRes id: Int,
@@ -37,14 +65,70 @@
 ): Unit = destination(NavGraphBuilder(provider, id, startDestination).apply(builder))
 
 /**
+ * Construct a nested [NavGraph]
+ *
+ * @param startDestination the starting destination's route for this NavGraph
+ * @param route the destination's unique route
+ * @param builder the builder used to construct the graph
+ *
+ * @return the newly constructed nested NavGraph
+ */
+public inline fun NavGraphBuilder.navigation(
+    startDestination: String,
+    route: String,
+    builder: NavGraphBuilder.() -> Unit
+): Unit = destination(NavGraphBuilder(provider, startDestination, route).apply(builder))
+
+/**
  * DSL for constructing a new [NavGraph]
  */
 @NavDestinationDsl
-public open class NavGraphBuilder(
-    public val provider: NavigatorProvider,
-    @IdRes id: Int,
-    @IdRes private var startDestination: Int
-) : NavDestinationBuilder<NavGraph>(provider[NavGraphNavigator::class], id) {
+public open class NavGraphBuilder : NavDestinationBuilder<NavGraph> {
+    /**
+     * Retrieve the [NavGraphBuilder]'s [NavigatorProvider].
+     *
+     * @return The [NavigatorProvider] used by this [NavGraphBuilder].
+     */
+    public val provider: NavigatorProvider
+    @IdRes private var startDestinationId: Int = 0
+    private var startDestinationRoute: String? = null
+
+    /**
+     * DSL for constructing a new [NavGraph]
+     *
+     * @param provider navigator used to create the destination
+     * @param id the graph's unique id
+     * @param startDestination the starting destination for this NavGraph
+     *
+     * @return the newly created NavGraph
+     */
+    public constructor(
+        provider: NavigatorProvider,
+        @IdRes id: Int,
+        @IdRes startDestination: Int
+    ) : super(provider[NavGraphNavigator::class], id) {
+        this.provider = provider
+        this.startDestinationId = startDestination
+    }
+
+    /**
+     * DSL for constructing a new [NavGraph]
+     *
+     * @param provider navigator used to create the destination
+     * @param startDestination the starting destination's route for this NavGraph
+     * @param route the graph's unique route
+     *
+     * @return the newly created NavGraph
+     */
+    public constructor(
+        provider: NavigatorProvider,
+        startDestination: String,
+        route: String?
+    ) : super(provider[NavGraphNavigator::class], route) {
+        this.provider = provider
+        this.startDestinationRoute = startDestination
+    }
+
     private val destinations = mutableListOf<NavDestination>()
 
     /**
@@ -70,9 +154,17 @@
 
     override fun build(): NavGraph = super.build().also { navGraph ->
         navGraph.addDestinations(destinations)
-        if (startDestination == 0) {
-            throw IllegalStateException("You must set a startDestination")
+        if (startDestinationId == 0 && startDestinationRoute == null) {
+            if (route != null) {
+                throw IllegalStateException("You must set a start destination route")
+            } else {
+                throw IllegalStateException("You must set a start destination id")
+            }
         }
-        navGraph.startDestination = startDestination
+        if (startDestinationRoute != null) {
+            navGraph.setStartDestination(startDestinationRoute!!)
+        } else {
+            navGraph.setStartDestination(startDestinationId)
+        }
     }
 }
diff --git a/navigation/navigation-common/src/main/java/androidx/navigation/NavGraphNavigator.kt b/navigation/navigation-common/src/main/java/androidx/navigation/NavGraphNavigator.kt
index 748bb59..4b58555 100644
--- a/navigation/navigation-common/src/main/java/androidx/navigation/NavGraphNavigator.kt
+++ b/navigation/navigation-common/src/main/java/androidx/navigation/NavGraphNavigator.kt
@@ -15,9 +15,6 @@
  */
 package androidx.navigation
 
-import android.os.Bundle
-import java.lang.IllegalArgumentException
-
 /**
  * A Navigator built specifically for [NavGraph] elements. Handles navigating to the
  * correct destination when the NavGraph is the target of navigation actions.
@@ -44,16 +41,32 @@
      * @throws IllegalArgumentException if given destination is not a child of the current navgraph
      */
     override fun navigate(
-        destination: NavGraph,
-        args: Bundle?,
+        entries: List<NavBackStackEntry>,
         navOptions: NavOptions?,
         navigatorExtras: Extras?
-    ): NavDestination? {
-        val startId = destination.startDestination
-        check(startId != 0) {
+    ) {
+        for (entry in entries) {
+            navigate(entry, navOptions, navigatorExtras)
+        }
+    }
+
+    private fun navigate(
+        entry: NavBackStackEntry,
+        navOptions: NavOptions?,
+        navigatorExtras: Extras?
+    ) {
+        val destination = entry.destination as NavGraph
+        val args = entry.arguments
+        val startId = destination.startDestinationId
+        val startRoute = destination.startDestinationRoute
+        check(startId != 0 || startRoute != null) {
             ("no start destination defined via app:startDestination for ${destination.displayName}")
         }
-        val startDestination = destination.findNode(startId, false)
+        val startDestination = if (startRoute != null) {
+            destination.findNode(startRoute, false)
+        } else {
+            destination.findNode(startId, false)
+        }
         requireNotNull(startDestination) {
             val dest = destination.startDestDisplayName
             throw IllegalArgumentException(
@@ -63,15 +76,10 @@
         val navigator = navigatorProvider.getNavigator<Navigator<NavDestination>>(
             startDestination.navigatorName
         )
-        return navigator.navigate(
+        val startDestinationEntry = state.createBackStackEntry(
             startDestination,
-            startDestination.addInDefaultArgs(args),
-            navOptions,
-            navigatorExtras
+            startDestination.addInDefaultArgs(args)
         )
-    }
-
-    override fun popBackStack(): Boolean {
-        return true
+        navigator.navigate(listOf(startDestinationEntry), navOptions, navigatorExtras)
     }
 }
\ No newline at end of file
diff --git a/navigation/navigation-common/src/main/java/androidx/navigation/NavOptions.kt b/navigation/navigation-common/src/main/java/androidx/navigation/NavOptions.kt
index 8e54db5..38e8fa3 100644
--- a/navigation/navigation-common/src/main/java/androidx/navigation/NavOptions.kt
+++ b/navigation/navigation-common/src/main/java/androidx/navigation/NavOptions.kt
@@ -18,6 +18,7 @@
 import androidx.annotation.AnimRes
 import androidx.annotation.AnimatorRes
 import androidx.annotation.IdRes
+import androidx.navigation.NavDestination.Companion.createRoute
 
 /**
  * NavOptions stores special options for navigate actions
@@ -35,7 +36,7 @@
      * @see shouldPopUpToSaveState
      */
     @field:IdRes @get:IdRes @param:IdRes
-    public val popUpTo: Int,
+    public val popUpToId: Int,
     private val popUpToInclusive: Boolean,
     private val popUpToSaveState: Boolean,
     /**
@@ -66,6 +67,58 @@
     public val popExitAnim: Int
 ) {
     /**
+     * The destination to pop up to before navigating. When set, all non-matching destinations
+     * should be popped from the back stack.
+     * @return the destinationId to pop up to, clearing all intervening destinations
+     * @see Builder.setPopUpTo
+     *
+     * @see isPopUpToInclusive
+     * @see shouldPopUpToSaveState
+     */
+    @IdRes
+    @Deprecated("Use popUpToId instead.", ReplaceWith("popUpToId"))
+    public fun getPopUpTo(): Int = popUpToId
+
+    /**
+     * Route for the destination to pop up to before navigating. When set, all non-matching
+     * destinations should be popped from the back stack.
+     * @return the destination route to pop up to, clearing all intervening destinations
+     * @see Builder.setPopUpTo
+     *
+     * @see isPopUpToInclusive
+     * @see shouldPopUpToSaveState
+     */
+    public var popUpToRoute: String? = null
+        private set
+
+    /**
+     * NavOptions stores special options for navigate actions
+     */
+    internal constructor(
+        singleTop: Boolean,
+        restoreState: Boolean,
+        popUpToRoute: String?,
+        popUpToInclusive: Boolean,
+        popUpToSaveState: Boolean,
+        enterAnim: Int,
+        exitAnim: Int,
+        popEnterAnim: Int,
+        popExitAnim: Int
+    ) : this(
+        singleTop,
+        restoreState,
+        createRoute(popUpToRoute).hashCode(),
+        popUpToInclusive,
+        popUpToSaveState,
+        enterAnim,
+        exitAnim,
+        popEnterAnim,
+        popExitAnim
+    ) {
+        this.popUpToRoute = popUpToRoute
+    }
+
+    /**
      * Whether this navigation action should launch as single-top (i.e., there will be at most
      * one copy of a given destination on the top of the back stack).
      *
@@ -97,9 +150,9 @@
 
     /**
      * Whether the back stack and the state of all destinations between the
-     * current destination and [popUpTo] should be saved for later restoration via
+     * current destination and [popUpToId] should be saved for later restoration via
      * [Builder.setRestoreState] or the `restoreState` attribute using the same ID
-     * as [popUpTo] (note: this matching ID is true whether [isPopUpToInclusive] is true or
+     * as [popUpToId] (note: this matching ID is true whether [isPopUpToInclusive] is true or
      * false).
      */
     public fun shouldPopUpToSaveState(): Boolean {
@@ -112,7 +165,8 @@
         val that = other as NavOptions
         return singleTop == that.singleTop &&
             restoreState == that.restoreState &&
-            popUpTo == that.popUpTo &&
+            popUpToId == that.popUpToId &&
+            popUpToRoute == that.popUpToRoute &&
             popUpToInclusive == that.popUpToInclusive &&
             popUpToSaveState == that.popUpToSaveState &&
             enterAnim == that.enterAnim &&
@@ -124,7 +178,8 @@
     override fun hashCode(): Int {
         var result = if (shouldLaunchSingleTop()) 1 else 0
         result = 31 * result + if (shouldRestoreState()) 1 else 0
-        result = 31 * result + popUpTo
+        result = 31 * result + popUpToId
+        result = 31 * result + popUpToRoute.hashCode()
         result = 31 * result + if (isPopUpToInclusive()) 1 else 0
         result = 31 * result + if (shouldPopUpToSaveState()) 1 else 0
         result = 31 * result + enterAnim
@@ -142,7 +197,8 @@
         private var restoreState = false
 
         @IdRes
-        private var popUpTo = -1
+        private var popUpToId = -1
+        private var popUpToRoute: String? = null
         private var popUpToInclusive = false
         private var popUpToSaveState = false
 
@@ -194,11 +250,11 @@
          * @param saveState true if the back stack and the state of all destinations between the
          * current destination and [destinationId] should be saved for later restoration via
          * [setRestoreState] or the `restoreState` attribute using the same ID
-         * as [popUpTo] (note: this matching ID is true whether [inclusive] is true or
+         * as [popUpToId] (note: this matching ID is true whether [inclusive] is true or
          * false).
          * @return this Builder
          *
-         * @see NavOptions.popUpTo
+         * @see NavOptions.popUpToId
          * @see NavOptions.isPopUpToInclusive
          */
         @JvmOverloads
@@ -207,7 +263,37 @@
             inclusive: Boolean,
             saveState: Boolean = false
         ): Builder {
-            popUpTo = destinationId
+            popUpToId = destinationId
+            popUpToRoute = null
+            popUpToInclusive = inclusive
+            popUpToSaveState = saveState
+            return this
+        }
+
+        /**
+         * Pop up to a given destination before navigating. This pops all non-matching destinations
+         * from the back stack until this destination is found.
+         *
+         * @param route route for destination to pop up to, clearing all intervening destinations.
+         * @param inclusive true to also pop the given destination from the back stack.
+         * @param saveState true if the back stack and the state of all destinations between the
+         * current destination and [route] should be saved for later restoration via
+         * [setRestoreState] or the `restoreState` attribute using the same ID
+         * as [popUpToRoute] (note: this matching ID is true whether [inclusive] is true or
+         * false).
+         * @return this Builder
+         *
+         * @see NavOptions.popUpToId
+         * @see NavOptions.isPopUpToInclusive
+         */
+        @JvmOverloads
+        public fun setPopUpTo(
+            route: String?,
+            inclusive: Boolean,
+            saveState: Boolean = false
+        ): Builder {
+            popUpToRoute = route
+            popUpToId = -1
             popUpToInclusive = inclusive
             popUpToSaveState = saveState
             return this
@@ -275,11 +361,18 @@
          * @return a constructed NavOptions
          */
         public fun build(): NavOptions {
-            return NavOptions(
-                singleTop, restoreState,
-                popUpTo, popUpToInclusive, popUpToSaveState,
-                enterAnim, exitAnim, popEnterAnim, popExitAnim
-            )
+            return if (popUpToRoute != null)
+                NavOptions(
+                    singleTop, restoreState,
+                    popUpToRoute, popUpToInclusive, popUpToSaveState,
+                    enterAnim, exitAnim, popEnterAnim, popExitAnim
+                )
+            else
+                NavOptions(
+                    singleTop, restoreState,
+                    popUpToId, popUpToInclusive, popUpToSaveState,
+                    enterAnim, exitAnim, popEnterAnim, popExitAnim
+                )
         }
     }
 }
diff --git a/navigation/navigation-common/src/main/java/androidx/navigation/NavOptionsBuilder.kt b/navigation/navigation-common/src/main/java/androidx/navigation/NavOptionsBuilder.kt
index 05e7986..8865788 100644
--- a/navigation/navigation-common/src/main/java/androidx/navigation/NavOptionsBuilder.kt
+++ b/navigation/navigation-common/src/main/java/androidx/navigation/NavOptionsBuilder.kt
@@ -55,14 +55,38 @@
     public var restoreState: Boolean = false
 
     /**
+     * Returns the current destination that the builder will pop up to.
+     */
+    @IdRes
+    public var popUpToId: Int = -1
+        internal set(value) {
+            field = value
+            inclusive = false
+        }
+
+    /**
      * Pop up to a given destination before navigating. This pops all non-matching destinations
      * from the back stack until this destination is found.
      */
-    @IdRes
-    public var popUpTo: Int = -1
+    @Deprecated("Use the popUpToId property.")
+    public var popUpTo: Int
+        get() = popUpToId
+        @Deprecated("Use the popUpTo function and passing in the id.")
         set(value) {
-            field = value
-            inclusive = false
+            popUpTo(value)
+        }
+
+    /**
+     * Pop up to a given destination before navigating. This pops all non-matching destinations
+     * from the back stack until this destination is found.
+     */
+    public var popUpToRoute: String? = null
+        private set(value) {
+            if (value != null) {
+                require(value.isNotBlank()) { "Cannot pop up to an empty route" }
+                field = value
+                inclusive = false
+            }
         }
     private var inclusive = false
     private var saveState = false
@@ -71,8 +95,24 @@
      * Pop up to a given destination before navigating. This pops all non-matching destinations
      * from the back stack until this destination is found.
      */
-    public fun popUpTo(@IdRes id: Int, popUpToBuilder: PopUpToBuilder.() -> Unit) {
-        popUpTo = id
+    public fun popUpTo(@IdRes id: Int, popUpToBuilder: PopUpToBuilder.() -> Unit = {}) {
+        popUpToId = id
+        popUpToRoute = null
+        val builder = PopUpToBuilder().apply(popUpToBuilder)
+        inclusive = builder.inclusive
+        saveState = builder.saveState
+    }
+
+    /**
+     * Pop up to a given destination before navigating. This pops all non-matching destination routes
+     * from the back stack until the destination with a matching route is found.
+     *
+     * @param route route for the destination
+     * @param popUpToBuilder builder used to construct a popUpTo operation
+     */
+    public fun popUpTo(route: String, popUpToBuilder: PopUpToBuilder.() -> Unit = {}) {
+        popUpToRoute = route
+        popUpToId = -1
         val builder = PopUpToBuilder().apply(popUpToBuilder)
         inclusive = builder.inclusive
         saveState = builder.saveState
@@ -95,7 +135,11 @@
     internal fun build() = builder.apply {
         setLaunchSingleTop(launchSingleTop)
         setRestoreState(restoreState)
-        setPopUpTo(popUpTo, inclusive, saveState)
+        if (popUpToRoute != null) {
+            setPopUpTo(popUpToRoute, inclusive, saveState)
+        } else {
+            setPopUpTo(popUpToId, inclusive, saveState)
+        }
     }.build()
 }
 
diff --git a/navigation/navigation-common/src/main/java/androidx/navigation/NavType.kt b/navigation/navigation-common/src/main/java/androidx/navigation/NavType.kt
index ef64348..b52cb29 100644
--- a/navigation/navigation-common/src/main/java/androidx/navigation/NavType.kt
+++ b/navigation/navigation-common/src/main/java/androidx/navigation/NavType.kt
@@ -29,7 +29,7 @@
  *
  * You should only use one of the static NavType instances and subclasses defined in this class.
  *
- * @param <T> the type of the data that is supported by this NavType
+ * @param T the type of the data that is supported by this NavType
  */
 public abstract class NavType<T> internal constructor(
     /**
diff --git a/navigation/navigation-common/src/main/java/androidx/navigation/NavViewModelStoreProvider.kt b/navigation/navigation-common/src/main/java/androidx/navigation/NavViewModelStoreProvider.kt
index 0357e57..74fdc56 100644
--- a/navigation/navigation-common/src/main/java/androidx/navigation/NavViewModelStoreProvider.kt
+++ b/navigation/navigation-common/src/main/java/androidx/navigation/NavViewModelStoreProvider.kt
@@ -18,15 +18,14 @@
 
 import androidx.annotation.RestrictTo
 import androidx.lifecycle.ViewModelStore
-import java.util.UUID
 
 /**
  * Interface that allows you to retrieve a [ViewModelStore] associated with a
- * particular [UUID].
+ * particular [NavBackStackEntry.id].
  *
  * @hide
  */
 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
 public interface NavViewModelStoreProvider {
-    public fun getViewModelStore(backStackEntryUUID: UUID): ViewModelStore
+    public fun getViewModelStore(backStackEntryId: String): ViewModelStore
 }
diff --git a/navigation/navigation-common/src/main/java/androidx/navigation/NavigatorProvider.kt b/navigation/navigation-common/src/main/java/androidx/navigation/NavigatorProvider.kt
index 1d91ff0..8162b45 100644
--- a/navigation/navigation-common/src/main/java/androidx/navigation/NavigatorProvider.kt
+++ b/navigation/navigation-common/src/main/java/androidx/navigation/NavigatorProvider.kt
@@ -103,6 +103,9 @@
     ): Navigator<out NavDestination>? {
         require(validateName(name)) { "navigator name cannot be an empty string" }
         val previousNavigator = _navigators[name]
+        if (previousNavigator == navigator) {
+            return navigator
+        }
         check(previousNavigator?.isAttached != true) {
             "Navigator $navigator is replacing an already attached $previousNavigator"
         }
diff --git a/navigation/navigation-common/src/test/java/androidx/navigation/NavDestinationTest.kt b/navigation/navigation-common/src/test/java/androidx/navigation/NavDestinationTest.kt
index 2d031b6..a0a0af1 100644
--- a/navigation/navigation-common/src/test/java/androidx/navigation/NavDestinationTest.kt
+++ b/navigation/navigation-common/src/test/java/androidx/navigation/NavDestinationTest.kt
@@ -150,7 +150,7 @@
         val navGraphNavigator = NavGraphNavigator(mock(NavigatorProvider::class.java))
         val parent = navGraphNavigator.createDestination().apply {
             id = parentId
-            startDestination = DESTINATION_ID
+            setStartDestination(DESTINATION_ID)
         }
         destination.parent = parent
         val deepLinkIds = destination.buildDeepLinkIds()
@@ -181,13 +181,13 @@
         val navGraphNavigator = NavGraphNavigator(mock(NavigatorProvider::class.java))
         val parent = navGraphNavigator.createDestination().apply {
             id = parentId
-            startDestination = DESTINATION_ID
+            setStartDestination(DESTINATION_ID)
         }
         destination.parent = parent
         val grandparentId = 3
         val grandparent = navGraphNavigator.createDestination().apply {
             id = grandparentId
-            startDestination = parentId
+            setStartDestination(parentId)
         }
         parent.parent = grandparent
         val deepLinkIds = destination.buildDeepLinkIds()
@@ -208,7 +208,7 @@
         val grandparentId = 3
         val grandparent = navGraphNavigator.createDestination().apply {
             id = grandparentId
-            startDestination = parentId
+            setStartDestination(parentId)
         }
         parent.parent = grandparent
         val deepLinkIds = destination.buildDeepLinkIds()
@@ -229,7 +229,7 @@
         val grandparentId = 3
         val grandparent = navGraphNavigator.createDestination().apply {
             id = grandparentId
-            startDestination = parentId
+            setStartDestination(parentId)
         }
         parent.parent = grandparent
         val deepLinkIds = destination.buildDeepLinkIds()
diff --git a/navigation/navigation-common/src/test/java/androidx/navigation/NavGraphNavigatorTest.kt b/navigation/navigation-common/src/test/java/androidx/navigation/NavGraphNavigatorTest.kt
index cd5e75f..5cd574a 100644
--- a/navigation/navigation-common/src/test/java/androidx/navigation/NavGraphNavigatorTest.kt
+++ b/navigation/navigation-common/src/test/java/androidx/navigation/NavGraphNavigatorTest.kt
@@ -17,9 +17,11 @@
 package androidx.navigation
 
 import androidx.annotation.IdRes
+import androidx.arch.core.executor.testing.InstantTaskExecutorRule
+import androidx.navigation.testing.TestNavigatorState
 import com.google.common.truth.Truth.assertThat
-import com.google.common.truth.Truth.assertWithMessage
 import org.junit.Before
+import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.JUnit4
@@ -34,8 +36,13 @@
         private const val SECOND_DESTINATION_ID = 2
     }
 
+    @get:Rule
+    val instantTaskExecutorRule = InstantTaskExecutorRule()
+
     private lateinit var provider: NavigatorProvider
+    private lateinit var noOpState: TestNavigatorState
     private lateinit var noOpNavigator: NoOpNavigator
+    private lateinit var navGraphState: TestNavigatorState
     private lateinit var navGraphNavigator: NavGraphNavigator
 
     @Before
@@ -48,6 +55,10 @@
                 }
             )
         }
+        noOpState = TestNavigatorState()
+        noOpNavigator.onAttach(noOpState)
+        navGraphState = TestNavigatorState()
+        navGraphNavigator.onAttach(navGraphState)
     }
 
     private fun createFirstDestination() = noOpNavigator.createDestination().apply {
@@ -63,7 +74,7 @@
         startId: Int = destination.id
     ) = navGraphNavigator.createDestination().apply {
         addDestination(destination)
-        startDestination = startId
+        setStartDestination(startId)
     }
 
     @Test(expected = IllegalStateException::class)
@@ -72,94 +83,19 @@
         val graph = navGraphNavigator.createDestination().apply {
             addDestination(destination)
             id = 2 // can't match id of first destination or the start destination
-            startDestination = 0
+            setStartDestination(0)
         }
-        navGraphNavigator.navigate(graph, null, null, null)
+        val entry = navGraphState.createBackStackEntry(graph, null)
+        navGraphNavigator.navigate(listOf(entry), null, null)
     }
 
     @Test
     fun navigate() {
         val destination = createFirstDestination()
         val graph = createGraphWithDestination(destination)
-        assertThat(navGraphNavigator.navigate(graph, null, null, null))
-            .isEqualTo(destination)
-    }
-
-    @Test
-    fun navigateThenPop() {
-        val destination = createFirstDestination()
-        val graph = createGraphWithDestination(destination)
-        assertThat(navGraphNavigator.navigate(graph, null, null, null))
-            .isEqualTo(destination)
-        val success = navGraphNavigator.popBackStack()
-        assertWithMessage("popBackStack should return true")
-            .that(success)
-            .isTrue()
-    }
-
-    @Test
-    fun navigateSingleTopOnEmptyStack() {
-        val destination = createFirstDestination()
-        val graph = createGraphWithDestination(destination)
-        // singleTop should still show as added on an empty stack
-        assertThat(
-            navGraphNavigator.navigate(
-                graph, null,
-                NavOptions.Builder().setLaunchSingleTop(true).build(), null
-            )
-        )
-            .isEqualTo(destination)
-    }
-
-    @Test
-    fun navigateSingleTop() {
-        val destination = createFirstDestination()
-        val graph = createGraphWithDestination(destination)
-        assertThat(navGraphNavigator.navigate(graph, null, null, null))
-            .isEqualTo(destination)
-        assertThat(
-            navGraphNavigator.navigate(
-                graph, null,
-                NavOptions.Builder().setLaunchSingleTop(true).build(), null
-            )
-        )
-            .isEqualTo(destination)
-    }
-
-    @Test
-    fun navigateSingleTopNotTop() {
-        val destination = createFirstDestination()
-        val graph = createGraphWithDestination(destination)
-        val secondDestination = createSecondDestination()
-        val secondGraph = createGraphWithDestination(secondDestination).apply {
-            id = SECOND_DESTINATION_ID
-        }
-        assertThat(navGraphNavigator.navigate(graph, null, null, null))
-            .isEqualTo(destination)
-        assertThat(
-            navGraphNavigator.navigate(
-                secondGraph, null,
-                NavOptions.Builder().setLaunchSingleTop(true).build(), null
-            )
-        )
-            .isEqualTo(secondDestination)
-    }
-
-    @Test
-    fun navigateSingleTopNested() {
-        val destination = createFirstDestination()
-        val nestedGraph = createGraphWithDestination(destination).apply {
-            id = FIRST_DESTINATION_ID
-        }
-        val graph = createGraphWithDestination(nestedGraph)
-        assertThat(navGraphNavigator.navigate(graph, null, null, null))
-            .isEqualTo(destination)
-        assertThat(
-            navGraphNavigator.navigate(
-                graph, null,
-                NavOptions.Builder().setLaunchSingleTop(true).build(), null
-            )
-        )
-            .isEqualTo(destination)
+        val entry = navGraphState.createBackStackEntry(graph, null)
+        navGraphNavigator.navigate(listOf(entry), null, null)
+        assertThat(noOpState.backStack.value.map { it.destination })
+            .containsExactly(destination)
     }
 }
diff --git a/navigation/navigation-common/src/test/java/androidx/navigation/NavGraphTest.kt b/navigation/navigation-common/src/test/java/androidx/navigation/NavGraphTest.kt
index 003c8b2..296c666 100644
--- a/navigation/navigation-common/src/test/java/androidx/navigation/NavGraphTest.kt
+++ b/navigation/navigation-common/src/test/java/androidx/navigation/NavGraphTest.kt
@@ -107,7 +107,7 @@
             id = FIRST_DESTINATION_ID
         }
         try {
-            graph.startDestination = destination.id
+            graph.setStartDestination(destination.id)
         } catch (e: IllegalArgumentException) {
             assertWithMessage("Setting a start destination with same id as its parent should fail")
                 .that(e).hasMessageThat().contains(
diff --git a/navigation/navigation-common/src/test/java/androidx/navigation/NavigatorProviderTest.kt b/navigation/navigation-common/src/test/java/androidx/navigation/NavigatorProviderTest.kt
index d4dcfe6..8a6c0c6 100644
--- a/navigation/navigation-common/src/test/java/androidx/navigation/NavigatorProviderTest.kt
+++ b/navigation/navigation-common/src/test/java/androidx/navigation/NavigatorProviderTest.kt
@@ -17,6 +17,7 @@
 package androidx.navigation
 
 import android.os.Bundle
+import androidx.navigation.testing.TestNavigatorState
 import com.google.common.truth.Truth.assertThat
 import com.google.common.truth.Truth.assertWithMessage
 import org.junit.Assert.fail
@@ -100,6 +101,50 @@
             .isEqualTo(navigator)
     }
 
+    @Test
+    fun addExistingNavigatorDoesntReplace() {
+        val navigatorState = TestNavigatorState()
+        val provider = NavigatorProvider()
+        val navigator = EmptyNavigator()
+
+        provider.addNavigator(navigator)
+        assertThat(provider.getNavigator<EmptyNavigator>(EmptyNavigator.NAME))
+            .isEqualTo(navigator)
+
+        navigator.onAttach(navigatorState)
+        assertWithMessage("Navigator should be attached")
+            .that(provider.getNavigator<EmptyNavigator>(EmptyNavigator.NAME).isAttached)
+            .isTrue()
+
+        // addNavigator should throw when trying to replace an existing, attached navigator, but
+        // we should have returned before that
+        try {
+            provider.addNavigator(navigator)
+        } catch (navigatorAlreadyAttached: IllegalStateException) {
+            fail(
+                "addNavigator with an existing navigator should return early and not " +
+                    "attempt to replace"
+            )
+        }
+    }
+
+    @Test
+    fun addWithSameNameButUnequalNavigatorDoesReplace() {
+        val provider = NavigatorProvider()
+        val navigatorA = EmptyNavigator()
+        val navigatorB = EmptyNavigator()
+
+        assertThat(navigatorA).isNotEqualTo(navigatorB)
+
+        provider.addNavigator(navigatorA)
+        assertThat(provider.getNavigator<EmptyNavigator>(EmptyNavigator.NAME))
+            .isEqualTo(navigatorA)
+
+        provider.addNavigator(navigatorB)
+        assertThat(provider.getNavigator<EmptyNavigator>(EmptyNavigator.NAME))
+            .isEqualTo(navigatorB)
+    }
+
     private val provider = NavigatorProvider()
 
     @Test
diff --git a/navigation/navigation-compose/api/current.txt b/navigation/navigation-compose/api/current.txt
index 3e486eb..7a9fbf0 100644
--- a/navigation/navigation-compose/api/current.txt
+++ b/navigation/navigation-compose/api/current.txt
@@ -11,24 +11,29 @@
   }
 
   public final class NamedNavArgument {
+    method public operator String component1();
+    method public operator androidx.navigation.NavArgument component2();
+    method public androidx.navigation.NavArgument getArgument();
+    method public String getName();
+    property public final androidx.navigation.NavArgument argument;
+    property public final String name;
   }
 
   public final class NamedNavArgumentKt {
     method @androidx.navigation.NavDestinationDsl public static androidx.navigation.compose.NamedNavArgument navArgument(String name, kotlin.jvm.functions.Function1<? super androidx.navigation.NavArgumentBuilder,kotlin.Unit> builder);
   }
 
+  public final class NavBackStackEntryProviderKt {
+    method @androidx.compose.runtime.Composable public static void LocalOwnersProvider(androidx.navigation.NavBackStackEntry, androidx.compose.runtime.saveable.SaveableStateHolder saveableStateHolder, kotlin.jvm.functions.Function0<kotlin.Unit> content);
+  }
+
   public final class NavGraphBuilderKt {
     method public static void composable(androidx.navigation.NavGraphBuilder, String route, optional java.util.List<androidx.navigation.compose.NamedNavArgument> arguments, optional java.util.List<androidx.navigation.NavDeepLink> deepLinks, kotlin.jvm.functions.Function1<? super androidx.navigation.NavBackStackEntry,kotlin.Unit> content);
-    method public static void navigation(androidx.navigation.NavGraphBuilder, String startDestination, String route, kotlin.jvm.functions.Function1<? super androidx.navigation.NavGraphBuilder,kotlin.Unit> builder);
   }
 
   public final class NavHostControllerKt {
-    method public static androidx.navigation.NavGraph createGraph(androidx.navigation.NavController, String startDestination, optional String? route, kotlin.jvm.functions.Function1<? super androidx.navigation.NavGraphBuilder,kotlin.Unit> builder);
     method @androidx.compose.runtime.Composable public static androidx.compose.runtime.State<androidx.navigation.NavBackStackEntry> currentBackStackEntryAsState(androidx.navigation.NavController);
-    method public static androidx.navigation.NavBackStackEntry getBackStackEntry(androidx.navigation.NavController, String route);
-    method public static void navigate(androidx.navigation.NavController, String route, optional kotlin.jvm.functions.Function1<? super androidx.navigation.NavOptionsBuilder,kotlin.Unit> builder);
     method @androidx.compose.runtime.Composable public static androidx.navigation.NavHostController rememberNavController();
-    field public static final String KEY_ROUTE = "android-support-nav:controller:route";
   }
 
   public final class NavHostKt {
@@ -36,9 +41,5 @@
     method @androidx.compose.runtime.Composable public static void NavHost(androidx.navigation.NavHostController navController, androidx.navigation.NavGraph graph, optional androidx.compose.ui.Modifier modifier);
   }
 
-  public final class NavOptionsBuilderKt {
-    method public static void popUpTo(androidx.navigation.NavOptionsBuilder, String route, kotlin.jvm.functions.Function1<? super androidx.navigation.PopUpToBuilder,kotlin.Unit> popUpToBuilder);
-  }
-
 }
 
diff --git a/navigation/navigation-compose/api/public_plus_experimental_current.txt b/navigation/navigation-compose/api/public_plus_experimental_current.txt
index 3e486eb..7a9fbf0 100644
--- a/navigation/navigation-compose/api/public_plus_experimental_current.txt
+++ b/navigation/navigation-compose/api/public_plus_experimental_current.txt
@@ -11,24 +11,29 @@
   }
 
   public final class NamedNavArgument {
+    method public operator String component1();
+    method public operator androidx.navigation.NavArgument component2();
+    method public androidx.navigation.NavArgument getArgument();
+    method public String getName();
+    property public final androidx.navigation.NavArgument argument;
+    property public final String name;
   }
 
   public final class NamedNavArgumentKt {
     method @androidx.navigation.NavDestinationDsl public static androidx.navigation.compose.NamedNavArgument navArgument(String name, kotlin.jvm.functions.Function1<? super androidx.navigation.NavArgumentBuilder,kotlin.Unit> builder);
   }
 
+  public final class NavBackStackEntryProviderKt {
+    method @androidx.compose.runtime.Composable public static void LocalOwnersProvider(androidx.navigation.NavBackStackEntry, androidx.compose.runtime.saveable.SaveableStateHolder saveableStateHolder, kotlin.jvm.functions.Function0<kotlin.Unit> content);
+  }
+
   public final class NavGraphBuilderKt {
     method public static void composable(androidx.navigation.NavGraphBuilder, String route, optional java.util.List<androidx.navigation.compose.NamedNavArgument> arguments, optional java.util.List<androidx.navigation.NavDeepLink> deepLinks, kotlin.jvm.functions.Function1<? super androidx.navigation.NavBackStackEntry,kotlin.Unit> content);
-    method public static void navigation(androidx.navigation.NavGraphBuilder, String startDestination, String route, kotlin.jvm.functions.Function1<? super androidx.navigation.NavGraphBuilder,kotlin.Unit> builder);
   }
 
   public final class NavHostControllerKt {
-    method public static androidx.navigation.NavGraph createGraph(androidx.navigation.NavController, String startDestination, optional String? route, kotlin.jvm.functions.Function1<? super androidx.navigation.NavGraphBuilder,kotlin.Unit> builder);
     method @androidx.compose.runtime.Composable public static androidx.compose.runtime.State<androidx.navigation.NavBackStackEntry> currentBackStackEntryAsState(androidx.navigation.NavController);
-    method public static androidx.navigation.NavBackStackEntry getBackStackEntry(androidx.navigation.NavController, String route);
-    method public static void navigate(androidx.navigation.NavController, String route, optional kotlin.jvm.functions.Function1<? super androidx.navigation.NavOptionsBuilder,kotlin.Unit> builder);
     method @androidx.compose.runtime.Composable public static androidx.navigation.NavHostController rememberNavController();
-    field public static final String KEY_ROUTE = "android-support-nav:controller:route";
   }
 
   public final class NavHostKt {
@@ -36,9 +41,5 @@
     method @androidx.compose.runtime.Composable public static void NavHost(androidx.navigation.NavHostController navController, androidx.navigation.NavGraph graph, optional androidx.compose.ui.Modifier modifier);
   }
 
-  public final class NavOptionsBuilderKt {
-    method public static void popUpTo(androidx.navigation.NavOptionsBuilder, String route, kotlin.jvm.functions.Function1<? super androidx.navigation.PopUpToBuilder,kotlin.Unit> popUpToBuilder);
-  }
-
 }
 
diff --git a/navigation/navigation-compose/api/restricted_current.txt b/navigation/navigation-compose/api/restricted_current.txt
index 3e486eb..7a9fbf0 100644
--- a/navigation/navigation-compose/api/restricted_current.txt
+++ b/navigation/navigation-compose/api/restricted_current.txt
@@ -11,24 +11,29 @@
   }
 
   public final class NamedNavArgument {
+    method public operator String component1();
+    method public operator androidx.navigation.NavArgument component2();
+    method public androidx.navigation.NavArgument getArgument();
+    method public String getName();
+    property public final androidx.navigation.NavArgument argument;
+    property public final String name;
   }
 
   public final class NamedNavArgumentKt {
     method @androidx.navigation.NavDestinationDsl public static androidx.navigation.compose.NamedNavArgument navArgument(String name, kotlin.jvm.functions.Function1<? super androidx.navigation.NavArgumentBuilder,kotlin.Unit> builder);
   }
 
+  public final class NavBackStackEntryProviderKt {
+    method @androidx.compose.runtime.Composable public static void LocalOwnersProvider(androidx.navigation.NavBackStackEntry, androidx.compose.runtime.saveable.SaveableStateHolder saveableStateHolder, kotlin.jvm.functions.Function0<kotlin.Unit> content);
+  }
+
   public final class NavGraphBuilderKt {
     method public static void composable(androidx.navigation.NavGraphBuilder, String route, optional java.util.List<androidx.navigation.compose.NamedNavArgument> arguments, optional java.util.List<androidx.navigation.NavDeepLink> deepLinks, kotlin.jvm.functions.Function1<? super androidx.navigation.NavBackStackEntry,kotlin.Unit> content);
-    method public static void navigation(androidx.navigation.NavGraphBuilder, String startDestination, String route, kotlin.jvm.functions.Function1<? super androidx.navigation.NavGraphBuilder,kotlin.Unit> builder);
   }
 
   public final class NavHostControllerKt {
-    method public static androidx.navigation.NavGraph createGraph(androidx.navigation.NavController, String startDestination, optional String? route, kotlin.jvm.functions.Function1<? super androidx.navigation.NavGraphBuilder,kotlin.Unit> builder);
     method @androidx.compose.runtime.Composable public static androidx.compose.runtime.State<androidx.navigation.NavBackStackEntry> currentBackStackEntryAsState(androidx.navigation.NavController);
-    method public static androidx.navigation.NavBackStackEntry getBackStackEntry(androidx.navigation.NavController, String route);
-    method public static void navigate(androidx.navigation.NavController, String route, optional kotlin.jvm.functions.Function1<? super androidx.navigation.NavOptionsBuilder,kotlin.Unit> builder);
     method @androidx.compose.runtime.Composable public static androidx.navigation.NavHostController rememberNavController();
-    field public static final String KEY_ROUTE = "android-support-nav:controller:route";
   }
 
   public final class NavHostKt {
@@ -36,9 +41,5 @@
     method @androidx.compose.runtime.Composable public static void NavHost(androidx.navigation.NavHostController navController, androidx.navigation.NavGraph graph, optional androidx.compose.ui.Modifier modifier);
   }
 
-  public final class NavOptionsBuilderKt {
-    method public static void popUpTo(androidx.navigation.NavOptionsBuilder, String route, kotlin.jvm.functions.Function1<? super androidx.navigation.PopUpToBuilder,kotlin.Unit> popUpToBuilder);
-  }
-
 }
 
diff --git a/navigation/navigation-compose/integration-tests/navigation-demos/src/main/java/androidx/navigation/compose/demos/BottomBarNavDemo.kt b/navigation/navigation-compose/integration-tests/navigation-demos/src/main/java/androidx/navigation/compose/demos/BottomBarNavDemo.kt
index 60b3df3..6f7d3de 100644
--- a/navigation/navigation-compose/integration-tests/navigation-demos/src/main/java/androidx/navigation/compose/demos/BottomBarNavDemo.kt
+++ b/navigation/navigation-compose/integration-tests/navigation-demos/src/main/java/androidx/navigation/compose/demos/BottomBarNavDemo.kt
@@ -27,11 +27,9 @@
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.res.stringResource
-import androidx.navigation.compose.KEY_ROUTE
 import androidx.navigation.compose.NavHost
 import androidx.navigation.compose.composable
 import androidx.navigation.compose.currentBackStackEntryAsState
-import androidx.navigation.compose.navigate
 import androidx.navigation.compose.rememberNavController
 import androidx.navigation.compose.samples.Dashboard
 import androidx.navigation.compose.samples.Profile
@@ -52,7 +50,7 @@
         bottomBar = {
             BottomNavigation {
                 val navBackStackEntry = navController.currentBackStackEntryAsState().value
-                val entryRoute = navBackStackEntry?.arguments?.getString(KEY_ROUTE)
+                val entryRoute = navBackStackEntry?.destination?.route
                 items.forEach { (name, route) ->
                     BottomNavigationItem(
                         icon = { Icon(Icons.Filled.Favorite, contentDescription = null) },
@@ -60,8 +58,9 @@
                         selected = entryRoute == route,
                         onClick = {
                             navController.navigate(route) {
+                                launchSingleTop = true
                                 restoreState = true
-                                popUpTo(navController.graph.startDestination) {
+                                popUpTo(navController.graph.startDestinationId) {
                                     saveState = true
                                 }
                             }
diff --git a/navigation/navigation-compose/integration-tests/navigation-demos/src/main/java/androidx/navigation/compose/demos/NavPopUpToDemo.kt b/navigation/navigation-compose/integration-tests/navigation-demos/src/main/java/androidx/navigation/compose/demos/NavPopUpToDemo.kt
index a195480a..243a1d1 100644
--- a/navigation/navigation-compose/integration-tests/navigation-demos/src/main/java/androidx/navigation/compose/demos/NavPopUpToDemo.kt
+++ b/navigation/navigation-compose/integration-tests/navigation-demos/src/main/java/androidx/navigation/compose/demos/NavPopUpToDemo.kt
@@ -30,8 +30,6 @@
 import androidx.navigation.NavController
 import androidx.navigation.compose.NavHost
 import androidx.navigation.compose.composable
-import androidx.navigation.compose.navigate
-import androidx.navigation.compose.popUpTo
 import androidx.navigation.compose.rememberNavController
 
 @Composable
diff --git a/navigation/navigation-compose/integration-tests/navigation-demos/src/main/java/androidx/navigation/compose/demos/NavSingleTopDemo.kt b/navigation/navigation-compose/integration-tests/navigation-demos/src/main/java/androidx/navigation/compose/demos/NavSingleTopDemo.kt
index 0cc7bcb..051d40b 100644
--- a/navigation/navigation-compose/integration-tests/navigation-demos/src/main/java/androidx/navigation/compose/demos/NavSingleTopDemo.kt
+++ b/navigation/navigation-compose/integration-tests/navigation-demos/src/main/java/androidx/navigation/compose/demos/NavSingleTopDemo.kt
@@ -30,7 +30,6 @@
 import androidx.compose.ui.unit.dp
 import androidx.navigation.compose.NavHost
 import androidx.navigation.compose.composable
-import androidx.navigation.compose.navigate
 import androidx.navigation.compose.rememberNavController
 import androidx.navigation.compose.samples.NavigateButton
 
diff --git a/navigation/navigation-compose/integration-tests/navigation-demos/src/main/java/androidx/navigation/compose/demos/NavWithArgsDemo.kt b/navigation/navigation-compose/integration-tests/navigation-demos/src/main/java/androidx/navigation/compose/demos/NavWithArgsDemo.kt
index e5253fb..2dbaf21 100644
--- a/navigation/navigation-compose/integration-tests/navigation-demos/src/main/java/androidx/navigation/compose/demos/NavWithArgsDemo.kt
+++ b/navigation/navigation-compose/integration-tests/navigation-demos/src/main/java/androidx/navigation/compose/demos/NavWithArgsDemo.kt
@@ -33,7 +33,6 @@
 import androidx.navigation.NavController
 import androidx.navigation.compose.NavHost
 import androidx.navigation.compose.composable
-import androidx.navigation.compose.navigate
 import androidx.navigation.compose.rememberNavController
 import androidx.navigation.compose.samples.Dashboard
 import androidx.navigation.compose.samples.NavigateButton
diff --git a/navigation/navigation-compose/samples/src/main/java/androidx/navigation/compose/samples/NavigationSamples.kt b/navigation/navigation-compose/samples/src/main/java/androidx/navigation/compose/samples/NavigationSamples.kt
index 639aeef..3f988fc 100644
--- a/navigation/navigation-compose/samples/src/main/java/androidx/navigation/compose/samples/NavigationSamples.kt
+++ b/navigation/navigation-compose/samples/src/main/java/androidx/navigation/compose/samples/NavigationSamples.kt
@@ -41,9 +41,8 @@
 import androidx.navigation.NavHostController
 import androidx.navigation.compose.NavHost
 import androidx.navigation.compose.composable
-import androidx.navigation.compose.navigate
-import androidx.navigation.compose.navigation
 import androidx.navigation.compose.rememberNavController
+import androidx.navigation.navigation
 
 sealed class Screen(val route: String, @StringRes val resourceId: Int) {
     object Profile : Screen("profile", R.string.profile)
@@ -61,7 +60,6 @@
     }
 }
 
-@Sampled
 @Composable
 fun NestedNavStartDestination() {
     val navController = rememberNavController()
@@ -74,7 +72,6 @@
     }
 }
 
-@Sampled
 @Composable
 fun NestedNavInGraph() {
     val navController = rememberNavController()
diff --git a/navigation/navigation-compose/src/androidTest/java/androidx/navigation/compose/NavBackStackEntryProviderTest.kt b/navigation/navigation-compose/src/androidTest/java/androidx/navigation/compose/NavBackStackEntryProviderTest.kt
new file mode 100644
index 0000000..20edc30
--- /dev/null
+++ b/navigation/navigation-compose/src/androidTest/java/androidx/navigation/compose/NavBackStackEntryProviderTest.kt
@@ -0,0 +1,163 @@
+/*
+ * 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.navigation.compose
+
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.saveable.rememberSaveable
+import androidx.compose.runtime.saveable.rememberSaveableStateHolder
+import androidx.compose.ui.platform.LocalLifecycleOwner
+import androidx.compose.ui.platform.LocalSavedStateRegistryOwner
+import androidx.compose.ui.test.junit4.StateRestorationTester
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.lifecycle.LifecycleOwner
+import androidx.lifecycle.ViewModelStoreOwner
+import androidx.lifecycle.viewmodel.compose.LocalViewModelStoreOwner
+import androidx.navigation.NavBackStackEntry
+import androidx.navigation.testing.TestNavigatorState
+import androidx.savedstate.SavedStateRegistryOwner
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.LargeTest
+import androidx.testutils.TestNavigator
+import com.google.common.truth.Truth.assertThat
+import com.google.common.truth.Truth.assertWithMessage
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@LargeTest
+@RunWith(AndroidJUnit4::class)
+class NavBackStackEntryProviderTest {
+
+    @get:Rule
+    val composeTestRule = createComposeRule()
+
+    @Test
+    fun testViewModelStoreOwnerProvided() {
+        val backStackEntry = createBackStackEntry()
+        var viewModelStoreOwner: ViewModelStoreOwner? = null
+
+        composeTestRule.setContent {
+            val saveableStateHolder = rememberSaveableStateHolder()
+            backStackEntry.LocalOwnersProvider(saveableStateHolder) {
+                viewModelStoreOwner = LocalViewModelStoreOwner.current
+            }
+        }
+
+        assertWithMessage("ViewModelStoreOwner is provided by $backStackEntry")
+            .that(viewModelStoreOwner).isEqualTo(backStackEntry)
+    }
+
+    @Test
+    fun testLifecycleOwnerProvided() {
+        val backStackEntry = createBackStackEntry()
+        var lifecycleOwner: LifecycleOwner? = null
+
+        composeTestRule.setContent {
+            val saveableStateHolder = rememberSaveableStateHolder()
+            backStackEntry.LocalOwnersProvider(saveableStateHolder) {
+                lifecycleOwner = LocalLifecycleOwner.current
+            }
+        }
+
+        assertWithMessage("LifecycleOwner is provided by $backStackEntry")
+            .that(lifecycleOwner).isEqualTo(backStackEntry)
+    }
+
+    @Test
+    fun testLocalSavedStateRegistryOwnerProvided() {
+        val backStackEntry = createBackStackEntry()
+        var localSavedStateRegistryOwner: SavedStateRegistryOwner? = null
+
+        composeTestRule.setContent {
+            val saveableStateHolder = rememberSaveableStateHolder()
+            backStackEntry.LocalOwnersProvider(saveableStateHolder) {
+                localSavedStateRegistryOwner = LocalSavedStateRegistryOwner.current
+            }
+        }
+
+        assertWithMessage("LocalSavedStateRegistryOwner is provided by $backStackEntry")
+            .that(localSavedStateRegistryOwner).isEqualTo(backStackEntry)
+    }
+
+    @Test
+    fun testSaveableValueInContentIsSaved() {
+        val backStackEntry = createBackStackEntry()
+        val restorationTester = StateRestorationTester(composeTestRule)
+        var array: IntArray? = null
+
+        restorationTester.setContent {
+            val saveableStateHolder = rememberSaveableStateHolder()
+            backStackEntry.LocalOwnersProvider(saveableStateHolder) {
+                array = rememberSaveable {
+                    intArrayOf(0)
+                }
+            }
+        }
+
+        assertThat(array).isEqualTo(intArrayOf(0))
+
+        composeTestRule.runOnUiThread {
+            array!![0] = 1
+            // we null it to ensure recomposition happened
+            array = null
+        }
+
+        restorationTester.emulateSavedInstanceStateRestore()
+
+        assertThat(array).isEqualTo(intArrayOf(1))
+    }
+
+    @Test
+    fun testNonSaveableValueInContentIsNotSaved() {
+        val backStackEntry = createBackStackEntry()
+        val restorationTester = StateRestorationTester(composeTestRule)
+        var nonSaveable: IntArray? = null
+        val initialValue = intArrayOf(10)
+
+        restorationTester.setContent {
+            val saveableStateHolder = rememberSaveableStateHolder()
+            backStackEntry.LocalOwnersProvider(saveableStateHolder) {
+                nonSaveable = remember { initialValue }
+            }
+        }
+
+        assertThat(nonSaveable).isEqualTo(initialValue)
+
+        composeTestRule.runOnUiThread {
+            nonSaveable!![0] = 1
+            // we null it to ensure recomposition happened
+            nonSaveable = null
+        }
+
+        restorationTester.emulateSavedInstanceStateRestore()
+
+        assertThat(nonSaveable).isEqualTo(initialValue)
+    }
+
+    private fun createBackStackEntry(): NavBackStackEntry {
+        val testNavigator = TestNavigator()
+        val testNavigatorState = TestNavigatorState()
+        testNavigator.onAttach(testNavigatorState)
+        val backStackEntry = testNavigatorState.createBackStackEntry(
+            testNavigator.createDestination(),
+            null
+        )
+        // We navigate to move the NavBackStackEntry to the correct state
+        testNavigator.navigate(listOf(backStackEntry), null, null)
+        return backStackEntry
+    }
+}
diff --git a/navigation/navigation-compose/src/androidTest/java/androidx/navigation/compose/NavGraphBuilderTest.kt b/navigation/navigation-compose/src/androidTest/java/androidx/navigation/compose/NavGraphBuilderTest.kt
index eac32a3..776d4a2 100644
--- a/navigation/navigation-compose/src/androidTest/java/androidx/navigation/compose/NavGraphBuilderTest.kt
+++ b/navigation/navigation-compose/src/androidTest/java/androidx/navigation/compose/NavGraphBuilderTest.kt
@@ -20,8 +20,10 @@
 import androidx.compose.ui.platform.LocalContext
 import androidx.compose.ui.test.junit4.createComposeRule
 import androidx.core.net.toUri
+import androidx.navigation.contains
 import androidx.navigation.NavDeepLinkRequest
 import androidx.navigation.navDeepLink
+import androidx.navigation.navigation
 import androidx.navigation.testing.TestNavHostController
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.LargeTest
diff --git a/navigation/navigation-compose/src/androidTest/java/androidx/navigation/compose/NavHostControllerTest.kt b/navigation/navigation-compose/src/androidTest/java/androidx/navigation/compose/NavHostControllerTest.kt
index 48fa470..e89ce23 100644
--- a/navigation/navigation-compose/src/androidTest/java/androidx/navigation/compose/NavHostControllerTest.kt
+++ b/navigation/navigation-compose/src/androidTest/java/androidx/navigation/compose/NavHostControllerTest.kt
@@ -16,26 +16,26 @@
 
 package androidx.navigation.compose
 
+import androidx.compose.runtime.Composable
 import androidx.compose.runtime.State
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.ui.platform.LocalContext
 import androidx.compose.ui.test.junit4.createComposeRule
 import androidx.navigation.NavBackStackEntry
 import androidx.navigation.NavController
-import androidx.navigation.NavDestination
-import androidx.navigation.NavDestinationBuilder
-import androidx.navigation.NavGraphBuilder
+import androidx.navigation.createGraph
 import androidx.navigation.get
+import androidx.navigation.plusAssign
 import androidx.navigation.testing.TestNavHostController
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.LargeTest
 import androidx.testutils.TestNavigator
+import androidx.testutils.test
 import com.google.common.truth.Truth.assertThat
 import com.google.common.truth.Truth.assertWithMessage
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
-import java.lang.IllegalArgumentException
 
 @LargeTest
 @RunWith(AndroidJUnit4::class)
@@ -47,7 +47,7 @@
     fun testCurrentBackStackEntrySetGraph() {
         var currentBackStackEntry: State<NavBackStackEntry?> = mutableStateOf(null)
         composeTestRule.setContent {
-            val navController = TestNavHostController(LocalContext.current)
+            val navController = createNavController()
 
             navController.graph = navController.createGraph(startDestination = FIRST_DESTINATION) {
                 test(FIRST_DESTINATION)
@@ -57,7 +57,7 @@
         }
 
         assertWithMessage("the currentBackStackEntry should be set with the graph")
-            .that(currentBackStackEntry.value?.arguments?.getString(KEY_ROUTE))
+            .that(currentBackStackEntry.value?.destination?.route)
             .isEqualTo(FIRST_DESTINATION)
     }
 
@@ -66,7 +66,7 @@
         var currentBackStackEntry: State<NavBackStackEntry?> = mutableStateOf(null)
         lateinit var navController: NavController
         composeTestRule.setContent {
-            navController = TestNavHostController(LocalContext.current)
+            navController = createNavController()
 
             navController.graph = navController.createGraph(startDestination = FIRST_DESTINATION) {
                 test(FIRST_DESTINATION)
@@ -77,7 +77,7 @@
         }
 
         assertWithMessage("the currentBackStackEntry should be set with the graph")
-            .that(currentBackStackEntry.value?.arguments?.getString(KEY_ROUTE))
+            .that(currentBackStackEntry.value?.destination?.route)
             .isEqualTo(FIRST_DESTINATION)
 
         composeTestRule.runOnUiThread {
@@ -85,7 +85,7 @@
         }
 
         assertWithMessage("the currentBackStackEntry should be after navigate")
-            .that(currentBackStackEntry.value?.arguments?.getString(KEY_ROUTE))
+            .that(currentBackStackEntry.value?.destination?.route)
             .isEqualTo(SECOND_DESTINATION)
     }
 
@@ -94,7 +94,7 @@
         var currentBackStackEntry: State<NavBackStackEntry?> = mutableStateOf(null)
         lateinit var navController: TestNavHostController
         composeTestRule.setContent {
-            navController = TestNavHostController(LocalContext.current)
+            navController = createNavController()
 
             navController.graph = navController.createGraph(startDestination = FIRST_DESTINATION) {
                 test(FIRST_DESTINATION)
@@ -110,7 +110,7 @@
         }
 
         assertWithMessage("the currentBackStackEntry should return to first destination after pop")
-            .that(currentBackStackEntry.value?.arguments?.getString(KEY_ROUTE))
+            .that(currentBackStackEntry.value?.destination?.route)
             .isEqualTo(FIRST_DESTINATION)
     }
 
@@ -118,10 +118,8 @@
     fun testNavigateThenNavigateWithPop() {
         var currentBackStackEntry: State<NavBackStackEntry?> = mutableStateOf(null)
         lateinit var navController: NavController
-        val navigator = TestNavigator()
         composeTestRule.setContent {
-            navController = TestNavHostController(LocalContext.current)
-            navController.navigatorProvider.addNavigator(navigator)
+            navController = createNavController()
 
             navController.graph = navController.createGraph(startDestination = FIRST_DESTINATION) {
                 test(FIRST_DESTINATION)
@@ -131,8 +129,10 @@
             currentBackStackEntry = navController.currentBackStackEntryAsState()
         }
 
+        val navigator = navController.navigatorProvider[TestNavigator::class]
+
         assertWithMessage("the currentBackStackEntry should be set with the graph")
-            .that(currentBackStackEntry.value?.arguments?.getString(KEY_ROUTE))
+            .that(currentBackStackEntry.value?.destination?.route)
             .isEqualTo(FIRST_DESTINATION)
         assertThat(navigator.backStack.size).isEqualTo(1)
 
@@ -143,7 +143,7 @@
         }
 
         assertWithMessage("the currentBackStackEntry should be after navigate")
-            .that(currentBackStackEntry.value?.arguments?.getString(KEY_ROUTE))
+            .that(currentBackStackEntry.value?.destination?.route)
             .isEqualTo(SECOND_DESTINATION)
         assertWithMessage("the second destination should be the only one on the back stack")
             .that(navigator.backStack.size)
@@ -154,10 +154,8 @@
     fun testNavigateOptionSingleTop() {
         var currentBackStackEntry: State<NavBackStackEntry?> = mutableStateOf(null)
         lateinit var navController: NavController
-        val navigator = TestNavigator()
         composeTestRule.setContent {
-            navController = TestNavHostController(LocalContext.current)
-            navController.navigatorProvider.addNavigator(navigator)
+            navController = createNavController()
 
             navController.graph = navController.createGraph(startDestination = FIRST_DESTINATION) {
                 test(FIRST_DESTINATION)
@@ -167,8 +165,9 @@
             currentBackStackEntry = navController.currentBackStackEntryAsState()
         }
 
+        val navigator = navController.navigatorProvider[TestNavigator::class]
         assertWithMessage("the currentBackStackEntry should be set with the graph")
-            .that(currentBackStackEntry.value?.arguments?.getString(KEY_ROUTE))
+            .that(currentBackStackEntry.value?.destination?.route)
             .isEqualTo(FIRST_DESTINATION)
         assertThat(navigator.backStack.size).isEqualTo(1)
 
@@ -195,7 +194,7 @@
     fun testGetBackStackEntry() {
         lateinit var navController: NavController
         composeTestRule.setContent {
-            navController = TestNavHostController(LocalContext.current)
+            navController = createNavController()
 
             navController.graph = navController.createGraph(startDestination = FIRST_DESTINATION) {
                 test(FIRST_DESTINATION)
@@ -209,13 +208,13 @@
 
         assertWithMessage("first destination should be on back stack")
             .that(
-                navController.getBackStackEntry(FIRST_DESTINATION).arguments?.getString(KEY_ROUTE)
+                navController.getBackStackEntry(FIRST_DESTINATION).destination.route
             )
             .isEqualTo(FIRST_DESTINATION)
 
         assertWithMessage("second destination should be on back stack")
             .that(
-                navController.getBackStackEntry(SECOND_DESTINATION).arguments?.getString(KEY_ROUTE)
+                navController.getBackStackEntry(SECOND_DESTINATION).destination.route
             )
             .isEqualTo(SECOND_DESTINATION)
     }
@@ -224,7 +223,7 @@
     fun testGetBackStackEntryNoEntryFound() {
         lateinit var navController: NavController
         composeTestRule.setContent {
-            navController = TestNavHostController(LocalContext.current)
+            navController = createNavController()
 
             navController.graph = navController.createGraph(startDestination = FIRST_DESTINATION) {
                 test(FIRST_DESTINATION)
@@ -247,21 +246,15 @@
                 )
         }
     }
+
+    @Composable
+    private fun createNavController(): TestNavHostController {
+        val navController = TestNavHostController(LocalContext.current)
+        val navigator = TestNavigator()
+        navController.navigatorProvider += navigator
+        return navController
+    }
 }
 
-internal inline fun NavGraphBuilder.test(
-    route: String,
-    builder: NavDestinationBuilder<NavDestination>.() -> Unit = { deepLink(createRoute(route)) }
-) = destination(
-    NavDestinationBuilder<NavDestination>(
-        provider["test"],
-        createRoute(route).hashCode()
-    ).apply(builder).apply { argument(KEY_ROUTE) { defaultValue = route } }
-)
-
-internal fun TestNavHostController.setCurrentDestination(
-    route: String
-) = setCurrentDestination(createRoute(route).hashCode())
-
 private const val FIRST_DESTINATION = "first"
 private const val SECOND_DESTINATION = "second"
diff --git a/navigation/navigation-compose/src/androidTest/java/androidx/navigation/compose/NavHostTest.kt b/navigation/navigation-compose/src/androidTest/java/androidx/navigation/compose/NavHostTest.kt
index 637f279..68c9654 100644
--- a/navigation/navigation-compose/src/androidTest/java/androidx/navigation/compose/NavHostTest.kt
+++ b/navigation/navigation-compose/src/androidTest/java/androidx/navigation/compose/NavHostTest.kt
@@ -17,7 +17,7 @@
 package androidx.navigation.compose
 
 import android.annotation.SuppressLint
-import android.net.Uri
+import android.content.Context
 import android.os.Bundle
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.fillMaxSize
@@ -35,17 +35,19 @@
 import androidx.compose.ui.test.junit4.createComposeRule
 import androidx.compose.ui.test.onNodeWithText
 import androidx.compose.ui.test.performClick
-import androidx.core.net.toUri
 import androidx.lifecycle.ViewModel
 import androidx.lifecycle.ViewModelProvider
 import androidx.lifecycle.viewmodel.compose.LocalViewModelStoreOwner
-import androidx.navigation.NavGraph
+import androidx.navigation.contains
 import androidx.navigation.NavHostController
+import androidx.navigation.plusAssign
 import androidx.navigation.testing.TestNavHostController
 import androidx.savedstate.SavedStateRegistry
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.LargeTest
 import androidx.test.internal.runner.junit4.statement.UiThreadStatement.runOnUiThread
+import androidx.testutils.TestNavigator
+import androidx.testutils.test
 import com.google.common.truth.Truth.assertThat
 import com.google.common.truth.Truth.assertWithMessage
 import org.junit.Rule
@@ -62,7 +64,7 @@
     fun testSingleDestinationSet() {
         lateinit var navController: NavHostController
         composeTestRule.setContent {
-            navController = TestNavHostController(LocalContext.current)
+            navController = createNavController(LocalContext.current)
 
             NavHost(navController, startDestination = "first") {
                 test("first")
@@ -78,7 +80,7 @@
     fun testNavigate() {
         lateinit var navController: NavHostController
         composeTestRule.setContent {
-            navController = TestNavHostController(LocalContext.current)
+            navController = createNavController(LocalContext.current)
 
             NavHost(navController, startDestination = "first") {
                 test("first")
@@ -95,9 +97,7 @@
         }
 
         assertWithMessage("second destination should be current")
-            .that(
-                navController.currentDestination?.hasDeepLink(Uri.parse(createRoute("second")))
-            ).isTrue()
+            .that(navController.currentDestination?.route).isEqualTo("second")
     }
 
     @Test
@@ -134,9 +134,7 @@
 
         composeTestRule.runOnIdle {
             assertWithMessage("second destination should be current")
-                .that(
-                    navController.currentDestination?.hasDeepLink(Uri.parse(createRoute("second")))
-                ).isTrue()
+                .that(navController.currentDestination?.route).isEqualTo("second")
         }
 
         composeTestRule.onNodeWithText(text)
@@ -146,9 +144,7 @@
             // ensure our click listener was fired
             assertThat(counter).isEqualTo(1)
             assertWithMessage("second destination should be current")
-                .that(
-                    navController.currentDestination?.hasDeepLink(Uri.parse(createRoute("second")))
-                ).isTrue()
+                .that(navController.currentDestination?.route).isEqualTo("second")
         }
     }
 
@@ -156,7 +152,7 @@
     fun testPop() {
         lateinit var navController: TestNavHostController
         composeTestRule.setContent {
-            navController = TestNavHostController(LocalContext.current)
+            navController = createNavController(LocalContext.current)
 
             NavHost(navController, startDestination = "first") {
                 test("first")
@@ -170,10 +166,7 @@
         }
 
         assertWithMessage("First destination should be current")
-            .that(
-                navController.currentDestination?.hasDeepLink(createRoute("first").toUri())
-            )
-            .isTrue()
+            .that(navController.currentDestination?.route).isEqualTo("first")
     }
 
     @Test
@@ -185,7 +178,7 @@
             val context = LocalContext.current
             // added to avoid lint error b/184349025
             @SuppressLint("RememberReturnType")
-            navController = remember { TestNavHostController(context) }
+            navController = remember { createNavController(context) }
 
             NavHost(navController, startDestination = state.value) {
                 test("first")
@@ -199,9 +192,7 @@
 
         composeTestRule.runOnIdle {
             assertWithMessage("First destination should be current")
-                .that(
-                    navController.currentDestination?.hasDeepLink(createRoute("first").toUri())
-                ).isTrue()
+                .that(navController.currentDestination?.route).isEqualTo("first")
         }
     }
 
@@ -214,7 +205,7 @@
             state = remember { mutableStateOf(0) }
             // added to avoid lint error b/184349025
             @SuppressLint("RememberReturnType")
-            navController = remember { TestNavHostController(context) }
+            navController = remember { createNavController(context) }
             if (state.value == 0) {
                 NavHost(navController, startDestination = "first") {
                     test("first")
@@ -234,9 +225,7 @@
 
         composeTestRule.runOnIdle {
             assertWithMessage("First destination should be current")
-                .that(
-                    navController.currentDestination?.hasDeepLink(createRoute("first").toUri())
-                ).isTrue()
+                .that(navController.currentDestination?.route).isEqualTo("first")
         }
     }
 
@@ -283,9 +272,7 @@
 
         composeTestRule.runOnIdle {
             assertWithMessage("First destination should be current")
-                .that(
-                    navController.currentDestination?.hasDeepLink(createRoute("first").toUri())
-                ).isTrue()
+                .that(navController.currentDestination?.route).isEqualTo("first")
             assertThat(savedViewModel.value).isEqualTo(viewModel.value)
         }
     }
@@ -393,11 +380,14 @@
                 .isNotEqualTo(registry2)
         }
     }
-}
 
-operator fun NavGraph.contains(
-    route: String
-): Boolean = findNode(createRoute(route).hashCode()) != null
+    private fun createNavController(context: Context): TestNavHostController {
+        val navController = TestNavHostController(context)
+        val navigator = TestNavigator()
+        navController.navigatorProvider += navigator
+        return navController
+    }
+}
 
 class TestViewModel : ViewModel() {
     var value: String = "nothing"
diff --git a/navigation/navigation-compose/src/main/java/androidx/navigation/compose/NamedNavArgument.kt b/navigation/navigation-compose/src/main/java/androidx/navigation/compose/NamedNavArgument.kt
index 3589239..988c7c2 100644
--- a/navigation/navigation-compose/src/main/java/androidx/navigation/compose/NamedNavArgument.kt
+++ b/navigation/navigation-compose/src/main/java/androidx/navigation/compose/NamedNavArgument.kt
@@ -33,9 +33,24 @@
  * Construct a named [NavArgument] by using the [navArgument] method.
  */
 public class NamedNavArgument internal constructor(
-    private val name: String,
-    private val argument: NavArgument
+
+    /**
+     * The name the argument is associated with
+     */
+    public val name: String,
+
+    /**
+     * The [NavArgument] associated with the name
+     */
+    public val argument: NavArgument
 ) {
-    internal operator fun component1(): String = name
-    internal operator fun component2(): NavArgument = argument
+    /**
+     * Provides destructuring access to this [NamedNavArgument]'s [name]
+     */
+    public operator fun component1(): String = name
+
+    /**
+     * Provides destructuring access to this [NamedNavArgument]'s [argument]
+     */
+    public operator fun component2(): NavArgument = argument
 }
diff --git a/navigation/navigation-compose/src/main/java/androidx/navigation/compose/NavBackStackEntryProvider.kt b/navigation/navigation-compose/src/main/java/androidx/navigation/compose/NavBackStackEntryProvider.kt
new file mode 100644
index 0000000..c9ecf81
--- /dev/null
+++ b/navigation/navigation-compose/src/main/java/androidx/navigation/compose/NavBackStackEntryProvider.kt
@@ -0,0 +1,79 @@
+/*
+ * 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.navigation.compose
+
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.runtime.saveable.SaveableStateHolder
+import androidx.compose.ui.platform.LocalLifecycleOwner
+import androidx.compose.ui.platform.LocalSavedStateRegistryOwner
+import androidx.lifecycle.SavedStateHandle
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewmodel.compose.LocalViewModelStoreOwner
+import androidx.lifecycle.viewmodel.compose.viewModel
+import androidx.navigation.NavBackStackEntry
+import java.util.UUID
+
+/**
+ * Provides [this] [NavBackStackEntry] as [LocalViewModelStoreOwner], [LocalLifecycleOwner] and
+ * [LocalSavedStateRegistryOwner] to the [content] and saves the [content]'s saveable states with
+ * the given [saveableStateHolder].
+ *
+ * @param saveableStateHolder The [SaveableStateHolder] that holds the saved states. The same
+ * holder should be used for all [NavBackStackEntry]s in the encapsulating [Composable] and the
+ * holder should be hoisted.
+ * @param content The content [Composable]
+ */
+@Composable
+public fun NavBackStackEntry.LocalOwnersProvider(
+    saveableStateHolder: SaveableStateHolder,
+    content: @Composable () -> Unit
+) {
+    CompositionLocalProvider(
+        LocalViewModelStoreOwner provides this,
+        LocalLifecycleOwner provides this,
+        LocalSavedStateRegistryOwner provides this
+    ) {
+        saveableStateHolder.SaveableStateProvider(content)
+    }
+}
+
+@Composable
+private fun SaveableStateHolder.SaveableStateProvider(content: @Composable () -> Unit) {
+    val viewModel = viewModel<BackStackEntryIdViewModel>()
+    viewModel.saveableStateHolder = this
+    SaveableStateProvider(viewModel.id, content)
+}
+
+internal class BackStackEntryIdViewModel(handle: SavedStateHandle) : ViewModel() {
+
+    private val IdKey = "SaveableStateHolder_BackStackEntryKey"
+
+    // we create our own id for each back stack entry to support multiple entries of the same
+    // destination. this id will be restored by SavedStateHandle
+    val id: UUID = handle.get<UUID>(IdKey) ?: UUID.randomUUID().also { handle.set(IdKey, it) }
+
+    var saveableStateHolder: SaveableStateHolder? = null
+
+    // onCleared will be called on the entries removed from the back stack. here we notify
+    // RestorableStateHolder that we shouldn't save the state for this id, so when we open this
+    // destination again the state will not be restored.
+    override fun onCleared() {
+        super.onCleared()
+        saveableStateHolder?.removeState(id)
+    }
+}
diff --git a/navigation/navigation-compose/src/main/java/androidx/navigation/compose/NavGraphBuilder.kt b/navigation/navigation-compose/src/main/java/androidx/navigation/compose/NavGraphBuilder.kt
index bda379b..3774f72 100644
--- a/navigation/navigation-compose/src/main/java/androidx/navigation/compose/NavGraphBuilder.kt
+++ b/navigation/navigation-compose/src/main/java/androidx/navigation/compose/NavGraphBuilder.kt
@@ -19,10 +19,8 @@
 import androidx.compose.runtime.Composable
 import androidx.navigation.NavBackStackEntry
 import androidx.navigation.NavDeepLink
-import androidx.navigation.NavGraph
 import androidx.navigation.NavGraphBuilder
 import androidx.navigation.get
-import androidx.navigation.navigation
 
 /**
  * Add the [Composable] to the [NavGraphBuilder]
@@ -40,11 +38,7 @@
 ) {
     addDestination(
         ComposeNavigator.Destination(provider[ComposeNavigator::class], content).apply {
-            val internalRoute = createRoute(route)
-            addDeepLink(internalRoute)
-            val argument = navArgument(KEY_ROUTE) { defaultValue = route }
-            addArgument(argument.component1(), argument.component2())
-            id = internalRoute.hashCode()
+            this.route = route
             arguments.forEach { (argumentName, argument) ->
                 addArgument(argumentName, argument)
             }
@@ -54,21 +48,3 @@
         }
     )
 }
-
-/**
- * Construct a nested [NavGraph]
- *
- * @sample androidx.navigation.compose.samples.NestedNavStartDestination
- * @sample androidx.navigation.compose.samples.NestedNavInGraph
- */
-public fun NavGraphBuilder.navigation(
-    startDestination: String,
-    route: String,
-    builder: NavGraphBuilder.() -> Unit
-): Unit = navigation(
-    createRoute(route).hashCode(),
-    createRoute(startDestination).hashCode()
-) {
-    deepLink(createRoute(route))
-    apply(builder)
-}
diff --git a/navigation/navigation-compose/src/main/java/androidx/navigation/compose/NavHost.kt b/navigation/navigation-compose/src/main/java/androidx/navigation/compose/NavHost.kt
index c2cad53..61eb0d8 100644
--- a/navigation/navigation-compose/src/main/java/androidx/navigation/compose/NavHost.kt
+++ b/navigation/navigation-compose/src/main/java/androidx/navigation/compose/NavHost.kt
@@ -19,28 +19,22 @@
 import androidx.activity.compose.LocalOnBackPressedDispatcherOwner
 import androidx.compose.foundation.layout.Box
 import androidx.compose.runtime.Composable
-import androidx.compose.runtime.CompositionLocalProvider
 import androidx.compose.runtime.DisposableEffect
 import androidx.compose.runtime.collectAsState
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.remember
-import androidx.compose.runtime.saveable.SaveableStateHolder
 import androidx.compose.runtime.saveable.rememberSaveableStateHolder
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.platform.LocalLifecycleOwner
-import androidx.compose.ui.platform.LocalSavedStateRegistryOwner
 import androidx.lifecycle.Lifecycle
-import androidx.lifecycle.SavedStateHandle
-import androidx.lifecycle.ViewModel
 import androidx.lifecycle.viewmodel.compose.LocalViewModelStoreOwner
-import androidx.lifecycle.viewmodel.compose.viewModel
 import androidx.navigation.NavDestination
 import androidx.navigation.NavGraph
 import androidx.navigation.NavGraphBuilder
 import androidx.navigation.NavHostController
+import androidx.navigation.createGraph
 import androidx.navigation.Navigator
 import androidx.navigation.get
-import java.util.UUID
 
 /**
  * Provides in place in the Compose hierarchy for self contained navigation to occur.
@@ -136,41 +130,9 @@
         // while in the scope of the composable, we provide the navBackStackEntry as the
         // ViewModelStoreOwner and LifecycleOwner
         Box(modifier, propagateMinConstraints = true) {
-            CompositionLocalProvider(
-                LocalViewModelStoreOwner provides backStackEntry,
-                LocalLifecycleOwner provides backStackEntry,
-                LocalSavedStateRegistryOwner provides backStackEntry
-            ) {
-                saveableStateHolder.SaveableStateProvider {
-                    destination.content(backStackEntry)
-                }
+            backStackEntry.LocalOwnersProvider(saveableStateHolder) {
+                destination.content(backStackEntry)
             }
         }
     }
 }
-
-@Composable
-private fun SaveableStateHolder.SaveableStateProvider(content: @Composable () -> Unit) {
-    val viewModel = viewModel<BackStackEntryIdViewModel>()
-    viewModel.saveableStateHolder = this
-    SaveableStateProvider(viewModel.id, content)
-}
-
-internal class BackStackEntryIdViewModel(handle: SavedStateHandle) : ViewModel() {
-
-    private val IdKey = "SaveableStateHolder_BackStackEntryKey"
-
-    // we create our own id for each back stack entry to support multiple entries of the same
-    // destination. this id will be restored by SavedStateHandle
-    val id: UUID = handle.get<UUID>(IdKey) ?: UUID.randomUUID().also { handle.set(IdKey, it) }
-
-    var saveableStateHolder: SaveableStateHolder? = null
-
-    // onCleared will be called on the entries removed from the back stack. here we notify
-    // RestorableStateHolder that we shouldn't save the state for this id, so when we open this
-    // destination again the state will not be restored.
-    override fun onCleared() {
-        super.onCleared()
-        saveableStateHolder?.removeState(id)
-    }
-}
diff --git a/navigation/navigation-compose/src/main/java/androidx/navigation/compose/NavHostController.kt b/navigation/navigation-compose/src/main/java/androidx/navigation/compose/NavHostController.kt
index 08c66a0..b564e4c 100644
--- a/navigation/navigation-compose/src/main/java/androidx/navigation/compose/NavHostController.kt
+++ b/navigation/navigation-compose/src/main/java/androidx/navigation/compose/NavHostController.kt
@@ -27,22 +27,9 @@
 import androidx.compose.runtime.saveable.Saver
 import androidx.compose.runtime.saveable.rememberSaveable
 import androidx.compose.ui.platform.LocalContext
-import androidx.core.net.toUri
 import androidx.navigation.NavBackStackEntry
 import androidx.navigation.NavController
-import androidx.navigation.NavDeepLinkRequest
-import androidx.navigation.NavGraph
-import androidx.navigation.NavGraphBuilder
 import androidx.navigation.NavHostController
-import androidx.navigation.NavOptions
-import androidx.navigation.NavOptionsBuilder
-import androidx.navigation.navOptions
-import androidx.navigation.navigation
-
-/**
- * The route linked to the current destination.
- */
-const val KEY_ROUTE = "android-support-nav:controller:route"
 
 /**
  * Gets the current navigation back stack entry as a [MutableState]. When the given navController
@@ -96,56 +83,3 @@
     save = { it.saveState() },
     restore = { createNavController(context).apply { restoreState(it) } }
 )
-
-/**
- * Navigate to a route in the current NavGraph.
- *
- * @param route route for the destination
- * @param builder DSL for constructing a new [NavOptions]
- */
-public fun NavController.navigate(route: String, builder: NavOptionsBuilder.() -> Unit = {}) {
-    navigate(
-        NavDeepLinkRequest.Builder.fromUri(createRoute(route).toUri()).build(),
-        navOptions(builder)
-    )
-}
-
-/**
- * Construct a new [NavGraph]
- *
- * @param route the route for the graph
- * @param startDestination the route for the start destination
- * @param builder the builder used to construct the graph
- */
-fun NavController.createGraph(
-    startDestination: String,
-    route: String? = null,
-    builder: NavGraphBuilder.() -> Unit
-): NavGraph = navigatorProvider.navigation(
-    if (route != null) createRoute(route).hashCode() else 0,
-    createRoute(startDestination).hashCode(),
-    builder
-)
-
-/**
- * Gets the topmost {@link NavBackStackEntry} for a route.
- * <p>
- * This is always safe to use with {@link #getCurrentDestination() the current destination} or
- * {@link NavDestination#getParent() its parent} or grandparent navigation graphs as these
- * destinations are guaranteed to be on the back stack.
- *
- * @param route route of a destination that exists on the back stack
- * @throws IllegalArgumentException if the destination is not on the back stack
- */
-public fun NavController.getBackStackEntry(route: String): NavBackStackEntry {
-    try {
-        return getBackStackEntry(createRoute(route).hashCode())
-    } catch (e: IllegalArgumentException) {
-        throw IllegalArgumentException(
-            "No destination with route $route is on the NavController's back stack. The current " +
-                "destination is $currentDestination"
-        )
-    }
-}
-
-internal fun createRoute(route: String) = "android-app://androidx.navigation.compose/$route"
diff --git a/navigation/navigation-compose/src/main/java/androidx/navigation/compose/NavOptionsBuilder.kt b/navigation/navigation-compose/src/main/java/androidx/navigation/compose/NavOptionsBuilder.kt
deleted file mode 100644
index 7701f08..0000000
--- a/navigation/navigation-compose/src/main/java/androidx/navigation/compose/NavOptionsBuilder.kt
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright 2020 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.navigation.compose
-
-import androidx.navigation.NavOptionsBuilder
-import androidx.navigation.PopUpToBuilder
-
-/**
- * Pop up to a given destination before navigating. This pops all non-matching destination routes
- * from the back stack until the destination with a matching route is found.
- *
- * @param route route for the destination
- * @param popUpToBuilder builder used to construct a popUpTo operation
- */
-fun NavOptionsBuilder.popUpTo(route: String, popUpToBuilder: PopUpToBuilder.() -> Unit) {
-    popUpTo(createRoute(route).hashCode(), popUpToBuilder)
-}
diff --git a/navigation/navigation-dynamic-features-fragment/lint-baseline.xml b/navigation/navigation-dynamic-features-fragment/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/navigation/navigation-dynamic-features-fragment/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/navigation/navigation-dynamic-features-fragment/src/main/java/androidx/navigation/dynamicfeatures/fragment/DynamicFragmentNavigator.kt b/navigation/navigation-dynamic-features-fragment/src/main/java/androidx/navigation/dynamicfeatures/fragment/DynamicFragmentNavigator.kt
index 574c877..a3ac6d7 100644
--- a/navigation/navigation-dynamic-features-fragment/src/main/java/androidx/navigation/dynamicfeatures/fragment/DynamicFragmentNavigator.kt
+++ b/navigation/navigation-dynamic-features-fragment/src/main/java/androidx/navigation/dynamicfeatures/fragment/DynamicFragmentNavigator.kt
@@ -17,11 +17,10 @@
 package androidx.navigation.dynamicfeatures.fragment
 
 import android.content.Context
-import android.os.Bundle
 import android.util.AttributeSet
 import androidx.core.content.withStyledAttributes
 import androidx.fragment.app.FragmentManager
-import androidx.navigation.NavDestination
+import androidx.navigation.NavBackStackEntry
 import androidx.navigation.NavOptions
 import androidx.navigation.Navigator
 import androidx.navigation.NavigatorProvider
@@ -43,21 +42,32 @@
     override fun createDestination(): Destination = Destination(this)
 
     override fun navigate(
-        destination: FragmentNavigator.Destination,
-        args: Bundle?,
+        entries: List<NavBackStackEntry>,
         navOptions: NavOptions?,
         navigatorExtras: Navigator.Extras?
-    ): NavDestination? {
+    ) {
+        for (entry in entries) {
+            navigate(entry, navOptions, navigatorExtras)
+        }
+    }
+
+    private fun navigate(
+        entry: NavBackStackEntry,
+        navOptions: NavOptions?,
+        navigatorExtras: Navigator.Extras?
+    ) {
+        val destination = entry.destination
+        val args = entry.arguments
         val extras = navigatorExtras as? DynamicExtras
         if (destination is Destination) {
             val moduleName = destination.moduleName
             if (moduleName != null && installManager.needsInstall(moduleName)) {
-                return installManager.performInstall(destination, args, extras, moduleName)
+                installManager.performInstall(destination, args, extras, moduleName)
+                return
             }
         }
-        return super.navigate(
-            destination,
-            args,
+        super.navigate(
+            listOf(entry),
             navOptions,
             if (extras != null) extras.destinationExtras else navigatorExtras
         )
diff --git a/navigation/navigation-dynamic-features-runtime/build.gradle b/navigation/navigation-dynamic-features-runtime/build.gradle
index 2b45ff2..09812400 100644
--- a/navigation/navigation-dynamic-features-runtime/build.gradle
+++ b/navigation/navigation-dynamic-features-runtime/build.gradle
@@ -37,6 +37,8 @@
     api(PLAY_CORE)
     api(KOTLIN_STDLIB)
 
+    testImplementation(project(":navigation:navigation-testing"))
+    testImplementation("androidx.arch.core:core-testing:2.1.0")
     testImplementation(ANDROIDX_TEST_CORE)
     testImplementation(ANDROIDX_TEST_EXT_JUNIT)
     testImplementation(ANDROIDX_TEST_RUNNER)
diff --git a/navigation/navigation-dynamic-features-runtime/lint-baseline.xml b/navigation/navigation-dynamic-features-runtime/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/navigation/navigation-dynamic-features-runtime/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/navigation/navigation-dynamic-features-runtime/src/main/java/androidx/navigation/dynamicfeatures/DynamicGraphNavigator.kt b/navigation/navigation-dynamic-features-runtime/src/main/java/androidx/navigation/dynamicfeatures/DynamicGraphNavigator.kt
index 04ec5fa..1aa62d6 100644
--- a/navigation/navigation-dynamic-features-runtime/src/main/java/androidx/navigation/dynamicfeatures/DynamicGraphNavigator.kt
+++ b/navigation/navigation-dynamic-features-runtime/src/main/java/androidx/navigation/dynamicfeatures/DynamicGraphNavigator.kt
@@ -21,6 +21,7 @@
 import android.util.AttributeSet
 import androidx.annotation.RestrictTo
 import androidx.core.content.withStyledAttributes
+import androidx.navigation.NavBackStackEntry
 import androidx.navigation.NavDestination
 import androidx.navigation.NavGraph
 import androidx.navigation.NavGraphNavigator
@@ -58,20 +59,32 @@
      * module has successfully been installed.
      */
     override fun navigate(
-        destination: NavGraph,
-        args: Bundle?,
+        entries: List<NavBackStackEntry>,
         navOptions: NavOptions?,
         navigatorExtras: Extras?
-    ): NavDestination? {
+    ) {
+        for (entry in entries) {
+            navigate(entry, navOptions, navigatorExtras)
+        }
+    }
+
+    private fun navigate(
+        entry: NavBackStackEntry,
+        navOptions: NavOptions?,
+        navigatorExtras: Extras?
+    ) {
+        val destination = entry.destination
+        val args = entry.arguments
         val extras = if (navigatorExtras is DynamicExtras) navigatorExtras else null
         if (destination is DynamicNavGraph) {
             val moduleName = destination.moduleName
             if (moduleName != null && installManager.needsInstall(moduleName)) {
-                return installManager.performInstall(destination, args, extras, moduleName)
+                installManager.performInstall(destination, args, extras, moduleName)
+                return
             }
         }
-        return super.navigate(
-            destination, args, navOptions,
+        super.navigate(
+            listOf(entry), navOptions,
             if (extras != null) extras.destinationExtras else navigatorExtras
         )
     }
@@ -110,7 +123,7 @@
     internal fun navigateToProgressDestination(
         dynamicNavGraph: DynamicNavGraph,
         progressArgs: Bundle?
-    ): NavDestination? {
+    ) {
         var progressDestinationId = dynamicNavGraph.progressDestination
         if (progressDestinationId == 0) {
             progressDestinationId = installDefaultProgressDestination(dynamicNavGraph)
@@ -124,7 +137,8 @@
         val navigator = navigatorProvider.getNavigator<Navigator<NavDestination>>(
             progressDestination.navigatorName
         )
-        return navigator.navigate(progressDestination, progressArgs, null, null)
+        val entry = state.createBackStackEntry(progressDestination, progressArgs)
+        navigator.navigate(listOf(entry), null, null)
     }
 
     /**
diff --git a/navigation/navigation-dynamic-features-runtime/src/main/java/androidx/navigation/dynamicfeatures/DynamicInstallManager.kt b/navigation/navigation-dynamic-features-runtime/src/main/java/androidx/navigation/dynamicfeatures/DynamicInstallManager.kt
index 0d14629e..eab3688 100644
--- a/navigation/navigation-dynamic-features-runtime/src/main/java/androidx/navigation/dynamicfeatures/DynamicInstallManager.kt
+++ b/navigation/navigation-dynamic-features-runtime/src/main/java/androidx/navigation/dynamicfeatures/DynamicInstallManager.kt
@@ -79,8 +79,9 @@
             val dynamicNavGraph = DynamicNavGraph.getOrThrow(destination)
             val navigator: Navigator<*> =
                 dynamicNavGraph.navigatorProvider[dynamicNavGraph.navigatorName]
-            return if (navigator is DynamicGraphNavigator) {
+            if (navigator is DynamicGraphNavigator) {
                 navigator.navigateToProgressDestination(dynamicNavGraph, progressArgs)
+                return null
             } else {
                 throw IllegalStateException(
                     "You must use a DynamicNavGraph to perform a module installation."
diff --git a/navigation/navigation-dynamic-features-runtime/src/test/java/androidx/navigation/dynamicfeatures/DynamicNavGraphTest.kt b/navigation/navigation-dynamic-features-runtime/src/test/java/androidx/navigation/dynamicfeatures/DynamicNavGraphTest.kt
index 73897f5..f48fdbb 100644
--- a/navigation/navigation-dynamic-features-runtime/src/test/java/androidx/navigation/dynamicfeatures/DynamicNavGraphTest.kt
+++ b/navigation/navigation-dynamic-features-runtime/src/test/java/androidx/navigation/dynamicfeatures/DynamicNavGraphTest.kt
@@ -16,14 +16,17 @@
 
 package androidx.navigation.dynamicfeatures
 
+import androidx.arch.core.executor.testing.InstantTaskExecutorRule
 import androidx.navigation.NavDestination
 import androidx.navigation.NavigatorProvider
 import androidx.navigation.NoOpNavigator
 import androidx.navigation.dynamicfeatures.DynamicGraphNavigator.DynamicNavGraph
 import androidx.navigation.dynamicfeatures.shared.TestDynamicInstallManager
+import androidx.navigation.testing.TestNavigatorState
 import org.junit.Assert.assertNotNull
 import org.junit.Assert.assertTrue
 import org.junit.Before
+import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.JUnit4
@@ -31,10 +34,15 @@
 @RunWith(JUnit4::class)
 class DynamicNavGraphTest {
 
+    @get:Rule
+    val instantTaskExecutorRule = InstantTaskExecutorRule()
+
     private val progressId = 1
     private lateinit var provider: NavigatorProvider
+    private lateinit var navigatorState: TestNavigatorState
     private lateinit var navigator: DynamicGraphNavigator
     private lateinit var dynamicNavGraph: DynamicNavGraph
+    private lateinit var noOpState: TestNavigatorState
     private lateinit var noOpNavigator: NoOpNavigator
 
     @Before
@@ -46,6 +54,8 @@
             TestDynamicInstallManager()
         )
         provider.addNavigator(noOpNavigator)
+        noOpState = TestNavigatorState()
+        noOpNavigator.onAttach(noOpState)
         dynamicNavGraph = navigator.createDestination()
     }
 
@@ -61,7 +71,8 @@
                 id = progressId
             }
         )
-        val progressDestination = navigator.navigateToProgressDestination(dynamicNavGraph, null)
+        navigator.navigateToProgressDestination(dynamicNavGraph, null)
+        val progressDestination = noOpState.backStack.value.lastOrNull()?.destination
         assertNotNull(progressDestination)
         progressDestination?.let {
             DynamicNavGraph.getOrThrow(progressDestination)
@@ -82,7 +93,8 @@
                 id = progressId
             }
         )
-        val destination = navigator.navigateToProgressDestination(dynamicNavGraph, null)
+        navigator.navigateToProgressDestination(dynamicNavGraph, null)
+        val destination = noOpState.backStack.value.lastOrNull()?.destination
         assertTrue(destination?.parent is DynamicNavGraph)
     }
 
@@ -91,6 +103,8 @@
             navigator.installDefaultProgressDestination { it }
         }
         provider.addNavigator(navigator)
+        navigatorState = TestNavigatorState()
+        navigator.onAttach(navigatorState)
         dynamicNavGraph = navigator.createDestination()
     }
 }
diff --git a/navigation/navigation-fragment-ktx/lint-baseline.xml b/navigation/navigation-fragment-ktx/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/navigation/navigation-fragment-ktx/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/navigation/navigation-fragment/api/current.ignore b/navigation/navigation-fragment/api/current.ignore
index e92246d..bed10fd 100644
--- a/navigation/navigation-fragment/api/current.ignore
+++ b/navigation/navigation-fragment/api/current.ignore
@@ -5,5 +5,7 @@
 
 RemovedMethod: androidx.navigation.fragment.DialogFragmentNavigator#popBackStack():
     Removed method androidx.navigation.fragment.DialogFragmentNavigator.popBackStack()
+RemovedMethod: androidx.navigation.fragment.FragmentNavigator#navigate(androidx.navigation.fragment.FragmentNavigator.Destination, android.os.Bundle, androidx.navigation.NavOptions, androidx.navigation.Navigator.Extras):
+    Removed method androidx.navigation.fragment.FragmentNavigator.navigate(androidx.navigation.fragment.FragmentNavigator.Destination,android.os.Bundle,androidx.navigation.NavOptions,androidx.navigation.Navigator.Extras)
 RemovedMethod: androidx.navigation.fragment.FragmentNavigator#popBackStack():
     Removed method androidx.navigation.fragment.FragmentNavigator.popBackStack()
diff --git a/navigation/navigation-fragment/api/current.txt b/navigation/navigation-fragment/api/current.txt
index ac16ffe..5f8b262 100644
--- a/navigation/navigation-fragment/api/current.txt
+++ b/navigation/navigation-fragment/api/current.txt
@@ -45,7 +45,6 @@
     ctor public FragmentNavigator(android.content.Context context, androidx.fragment.app.FragmentManager fragmentManager, int containerId);
     method public androidx.navigation.fragment.FragmentNavigator.Destination createDestination();
     method @Deprecated public androidx.fragment.app.Fragment instantiateFragment(android.content.Context context, androidx.fragment.app.FragmentManager fragmentManager, String className, android.os.Bundle? args);
-    method public androidx.navigation.NavDestination? navigate(androidx.navigation.fragment.FragmentNavigator.Destination destination, android.os.Bundle? args, androidx.navigation.NavOptions? navOptions, androidx.navigation.Navigator.Extras? navigatorExtras);
   }
 
   @androidx.navigation.NavDestination.ClassType(Fragment::class) public static class FragmentNavigator.Destination extends androidx.navigation.NavDestination {
diff --git a/navigation/navigation-fragment/api/public_plus_experimental_current.txt b/navigation/navigation-fragment/api/public_plus_experimental_current.txt
index ac16ffe..5f8b262 100644
--- a/navigation/navigation-fragment/api/public_plus_experimental_current.txt
+++ b/navigation/navigation-fragment/api/public_plus_experimental_current.txt
@@ -45,7 +45,6 @@
     ctor public FragmentNavigator(android.content.Context context, androidx.fragment.app.FragmentManager fragmentManager, int containerId);
     method public androidx.navigation.fragment.FragmentNavigator.Destination createDestination();
     method @Deprecated public androidx.fragment.app.Fragment instantiateFragment(android.content.Context context, androidx.fragment.app.FragmentManager fragmentManager, String className, android.os.Bundle? args);
-    method public androidx.navigation.NavDestination? navigate(androidx.navigation.fragment.FragmentNavigator.Destination destination, android.os.Bundle? args, androidx.navigation.NavOptions? navOptions, androidx.navigation.Navigator.Extras? navigatorExtras);
   }
 
   @androidx.navigation.NavDestination.ClassType(Fragment::class) public static class FragmentNavigator.Destination extends androidx.navigation.NavDestination {
diff --git a/navigation/navigation-fragment/api/restricted_current.ignore b/navigation/navigation-fragment/api/restricted_current.ignore
index e92246d..bed10fd 100644
--- a/navigation/navigation-fragment/api/restricted_current.ignore
+++ b/navigation/navigation-fragment/api/restricted_current.ignore
@@ -5,5 +5,7 @@
 
 RemovedMethod: androidx.navigation.fragment.DialogFragmentNavigator#popBackStack():
     Removed method androidx.navigation.fragment.DialogFragmentNavigator.popBackStack()
+RemovedMethod: androidx.navigation.fragment.FragmentNavigator#navigate(androidx.navigation.fragment.FragmentNavigator.Destination, android.os.Bundle, androidx.navigation.NavOptions, androidx.navigation.Navigator.Extras):
+    Removed method androidx.navigation.fragment.FragmentNavigator.navigate(androidx.navigation.fragment.FragmentNavigator.Destination,android.os.Bundle,androidx.navigation.NavOptions,androidx.navigation.Navigator.Extras)
 RemovedMethod: androidx.navigation.fragment.FragmentNavigator#popBackStack():
     Removed method androidx.navigation.fragment.FragmentNavigator.popBackStack()
diff --git a/navigation/navigation-fragment/api/restricted_current.txt b/navigation/navigation-fragment/api/restricted_current.txt
index ac16ffe..5f8b262 100644
--- a/navigation/navigation-fragment/api/restricted_current.txt
+++ b/navigation/navigation-fragment/api/restricted_current.txt
@@ -45,7 +45,6 @@
     ctor public FragmentNavigator(android.content.Context context, androidx.fragment.app.FragmentManager fragmentManager, int containerId);
     method public androidx.navigation.fragment.FragmentNavigator.Destination createDestination();
     method @Deprecated public androidx.fragment.app.Fragment instantiateFragment(android.content.Context context, androidx.fragment.app.FragmentManager fragmentManager, String className, android.os.Bundle? args);
-    method public androidx.navigation.NavDestination? navigate(androidx.navigation.fragment.FragmentNavigator.Destination destination, android.os.Bundle? args, androidx.navigation.NavOptions? navOptions, androidx.navigation.Navigator.Extras? navigatorExtras);
   }
 
   @androidx.navigation.NavDestination.ClassType(Fragment::class) public static class FragmentNavigator.Destination extends androidx.navigation.NavDestination {
diff --git a/navigation/navigation-fragment/build.gradle b/navigation/navigation-fragment/build.gradle
index fa1132c..5837d97 100644
--- a/navigation/navigation-fragment/build.gradle
+++ b/navigation/navigation-fragment/build.gradle
@@ -31,6 +31,7 @@
     api(project(":navigation:navigation-runtime"))
 
     api(KOTLIN_STDLIB)
+    androidTestImplementation(project(":navigation:navigation-testing"))
     androidTestImplementation(projectOrArtifact(":fragment:fragment-testing"))
     androidTestImplementation(ANDROIDX_TEST_EXT_JUNIT)
     androidTestImplementation(ANDROIDX_TEST_CORE)
diff --git a/navigation/navigation-fragment/lint-baseline.xml b/navigation/navigation-fragment/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/navigation/navigation-fragment/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/navigation/navigation-fragment/src/androidTest/java/androidx/navigation/fragment/FragmentNavigatorTest.kt b/navigation/navigation-fragment/src/androidTest/java/androidx/navigation/fragment/FragmentNavigatorTest.kt
index 8b7571e..2498514 100644
--- a/navigation/navigation-fragment/src/androidTest/java/androidx/navigation/fragment/FragmentNavigatorTest.kt
+++ b/navigation/navigation-fragment/src/androidTest/java/androidx/navigation/fragment/FragmentNavigatorTest.kt
@@ -22,9 +22,11 @@
 import androidx.fragment.app.FragmentFactory
 import androidx.fragment.app.FragmentManager
 import androidx.lifecycle.Lifecycle
+import androidx.navigation.NavBackStackEntry
 import androidx.navigation.NavOptions
 import androidx.navigation.fragment.test.EmptyFragment
 import androidx.navigation.fragment.test.R
+import androidx.navigation.testing.TestNavigatorState
 import androidx.test.annotation.UiThreadTest
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.LargeTest
@@ -38,6 +40,7 @@
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
+import kotlin.reflect.KClass
 
 @LargeTest
 @RunWith(AndroidJUnit4::class)
@@ -57,24 +60,26 @@
 
     private lateinit var emptyActivity: EmptyActivity
     private lateinit var fragmentManager: FragmentManager
+    private lateinit var navigatorState: TestNavigatorState
+    private lateinit var fragmentNavigator: FragmentNavigator
 
     @Before
     fun setup() {
         emptyActivity = activityRule.activity
         fragmentManager = emptyActivity.supportFragmentManager
+        navigatorState = TestNavigatorState()
+        fragmentNavigator = FragmentNavigator(emptyActivity, fragmentManager, R.id.container)
+        fragmentNavigator.onAttach(navigatorState)
     }
 
     @UiThreadTest
     @Test
     fun testNavigate() {
-        val fragmentNavigator = FragmentNavigator(emptyActivity, fragmentManager, R.id.container)
-        val destination = fragmentNavigator.createDestination().apply {
-            id = INITIAL_FRAGMENT
-            setClassName(EmptyFragment::class.java.name)
-        }
+        val entry = createBackStackEntry()
 
-        assertThat(fragmentNavigator.navigate(destination, null, null, null))
-            .isEqualTo(destination)
+        fragmentNavigator.navigate(listOf(entry), null, null)
+        assertThat(navigatorState.backStack.value)
+            .containsExactly(entry)
         fragmentManager.executePendingTransactions()
         val fragment = fragmentManager.findFragmentById(R.id.container)
         assertNotNull("Fragment should be added", fragment)
@@ -92,14 +97,11 @@
     @Test
     fun testNavigateWithFragmentFactory() {
         fragmentManager.fragmentFactory = NonEmptyFragmentFactory()
-        val fragmentNavigator = FragmentNavigator(emptyActivity, fragmentManager, R.id.container)
-        val destination = fragmentNavigator.createDestination().apply {
-            id = INITIAL_FRAGMENT
-            setClassName(NonEmptyConstructorFragment::class.java.name)
-        }
+        val entry = createBackStackEntry(clazz = NonEmptyConstructorFragment::class)
 
-        assertThat(fragmentNavigator.navigate(destination, null, null, null))
-            .isEqualTo(destination)
+        fragmentNavigator.navigate(listOf(entry), null, null)
+        assertThat(navigatorState.backStack.value)
+            .containsExactly(entry)
         fragmentManager.executePendingTransactions()
         val fragment = fragmentManager.findFragmentById(R.id.container)
         assertWithMessage("Fragment should be added")
@@ -116,14 +118,11 @@
     @UiThreadTest
     @Test
     fun testNavigateTwice() {
-        val fragmentNavigator = FragmentNavigator(emptyActivity, fragmentManager, R.id.container)
-        val destination = fragmentNavigator.createDestination().apply {
-            id = INITIAL_FRAGMENT
-            setClassName(EmptyFragment::class.java.name)
-        }
+        val entry = createBackStackEntry()
 
-        assertThat(fragmentNavigator.navigate(destination, null, null, null))
-            .isEqualTo(destination)
+        fragmentNavigator.navigate(listOf(entry), null, null)
+        assertThat(navigatorState.backStack.value)
+            .containsExactly(entry)
         fragmentManager.executePendingTransactions()
         val fragment = fragmentManager.findFragmentById(R.id.container)
         assertNotNull("Fragment should be added", fragment)
@@ -137,9 +136,10 @@
         )
 
         // Now push a second fragment
-        destination.id = SECOND_FRAGMENT
-        assertThat(fragmentNavigator.navigate(destination, null, null, null))
-            .isEqualTo(destination)
+        val replacementEntry = createBackStackEntry(SECOND_FRAGMENT)
+        fragmentNavigator.navigate(listOf(replacementEntry), null, null)
+        assertThat(navigatorState.backStack.value)
+            .containsExactly(entry, replacementEntry).inOrder()
         fragmentManager.executePendingTransactions()
         val replacementFragment = fragmentManager.findFragmentById(R.id.container)
         assertNotNull("Replacement Fragment should be added", replacementFragment)
@@ -156,60 +156,55 @@
     @UiThreadTest
     @Test
     fun testNavigateWithPopUpToThenPop() {
-        val fragmentNavigator = FragmentNavigator(emptyActivity, fragmentManager, R.id.container)
-        val destination = fragmentNavigator.createDestination()
-        destination.id = INITIAL_FRAGMENT
-        destination.setClassName(EmptyFragment::class.java.name)
+        val entry = createBackStackEntry()
 
         // Push initial fragment
-        assertThat(fragmentNavigator.navigate(destination, null, null, null))
-            .isEqualTo(destination)
+        fragmentNavigator.navigate(listOf(entry), null, null)
+        assertThat(navigatorState.backStack.value)
+            .containsExactly(entry)
         fragmentManager.executePendingTransactions()
 
         // Push a second fragment
-        destination.id = SECOND_FRAGMENT
-        assertThat(fragmentNavigator.navigate(destination, null, null, null))
-            .isEqualTo(destination)
+        val secondEntry = createBackStackEntry(SECOND_FRAGMENT)
+        fragmentNavigator.navigate(listOf(secondEntry), null, null)
+        assertThat(navigatorState.backStack.value)
+            .containsExactly(entry, secondEntry).inOrder()
         fragmentManager.executePendingTransactions()
 
         // Pop and then push third fragment, simulating popUpTo to initial.
-        val success = fragmentNavigator.popBackStack()
-        assertTrue("FragmentNavigator should return true when popping the third fragment", success)
-        destination.id = THIRD_FRAGMENT
-        assertThat(
-            fragmentNavigator.navigate(
-                destination, null,
-                NavOptions.Builder().setPopUpTo(INITIAL_FRAGMENT, false).build(), null
-            )
-        )
-            .isEqualTo(destination)
+        fragmentNavigator.popBackStack(secondEntry, false)
+        assertThat(navigatorState.backStack.value)
+            .containsExactly(entry)
+        val thirdEntry = createBackStackEntry(THIRD_FRAGMENT)
+        fragmentNavigator.navigate(listOf(thirdEntry), null, null)
+        assertThat(navigatorState.backStack.value)
+            .containsExactly(entry, thirdEntry).inOrder()
         fragmentManager.executePendingTransactions()
 
         // Now pop the Fragment
-        val popped = fragmentNavigator.popBackStack()
-        assertTrue("FragmentNavigator should return true when popping the third fragment", popped)
+        fragmentNavigator.popBackStack(thirdEntry, false)
+        assertThat(navigatorState.backStack.value)
+            .containsExactly(entry)
     }
 
     @UiThreadTest
     @Test
     fun testSingleTopInitial() {
-        val fragmentNavigator = FragmentNavigator(emptyActivity, fragmentManager, R.id.container)
-        val destination = fragmentNavigator.createDestination()
-        destination.setClassName(EmptyFragment::class.java.name)
+        val entry = createBackStackEntry()
 
-        fragmentNavigator.navigate(destination, null, null, null)
+        fragmentNavigator.navigate(listOf(entry), null, null)
         fragmentManager.executePendingTransactions()
         val fragment = fragmentManager.findFragmentById(R.id.container)
         assertNotNull("Fragment should be added", fragment)
         val lifecycle = fragment!!.lifecycle
 
-        assertThat(
-            fragmentNavigator.navigate(
-                destination, null,
-                NavOptions.Builder().setLaunchSingleTop(true).build(), null
-            )
+        fragmentNavigator.navigate(
+            listOf(entry),
+            NavOptions.Builder().setLaunchSingleTop(true).build(),
+            null
         )
-            .isNull()
+        assertThat(navigatorState.backStack.value)
+            .containsExactly(entry)
         fragmentManager.executePendingTransactions()
         val replacementFragment = fragmentManager.findFragmentById(R.id.container)
         assertNotNull("Replacement Fragment should be added", replacementFragment)
@@ -234,13 +229,12 @@
     @UiThreadTest
     @Test
     fun testSingleTop() {
-        val fragmentNavigator = FragmentNavigator(emptyActivity, fragmentManager, R.id.container)
-        val destination = fragmentNavigator.createDestination()
-        destination.setClassName(EmptyFragment::class.java.name)
+        val entry = createBackStackEntry()
 
         // First push an initial Fragment
-        assertThat(fragmentNavigator.navigate(destination, null, null, null))
-            .isEqualTo(destination)
+        fragmentNavigator.navigate(listOf(entry), null, null)
+        assertThat(navigatorState.backStack.value)
+            .containsExactly(entry)
         fragmentManager.executePendingTransactions()
         val initialFragment = fragmentManager.findFragmentById(R.id.container)
         assertWithMessage("Initial Fragment should be added")
@@ -248,9 +242,10 @@
             .isNotNull()
 
         // Now push the Fragment that we want to replace with a singleTop operation
-        destination.id = 1
-        assertThat(fragmentNavigator.navigate(destination, null, null, null))
-            .isEqualTo(destination)
+        val replacementEntry = createBackStackEntry(SECOND_FRAGMENT)
+        fragmentNavigator.navigate(listOf(replacementEntry), null, null)
+        assertThat(navigatorState.backStack.value)
+            .containsExactly(entry, replacementEntry).inOrder()
         fragmentManager.executePendingTransactions()
         val fragment = fragmentManager.findFragmentById(R.id.container)
         assertWithMessage("Fragment should be added")
@@ -258,13 +253,13 @@
             .isNotNull()
         val lifecycle = fragment!!.lifecycle
 
-        assertThat(
-            fragmentNavigator.navigate(
-                destination, null,
-                NavOptions.Builder().setLaunchSingleTop(true).build(), null
-            )
+        fragmentNavigator.navigate(
+            listOf(replacementEntry),
+            NavOptions.Builder().setLaunchSingleTop(true).build(),
+            null
         )
-            .isNull()
+        assertThat(navigatorState.backStack.value)
+            .containsExactly(entry, replacementEntry).inOrder()
         fragmentManager.executePendingTransactions()
         val replacementFragment = fragmentManager.findFragmentById(R.id.container)
         assertWithMessage("Replacement Fragment should be added")
@@ -283,8 +278,10 @@
             .that(lifecycle.currentState)
             .isEqualTo(Lifecycle.State.DESTROYED)
 
-        assertThat(fragmentNavigator.popBackStack())
-            .isTrue()
+        fragmentNavigator.popBackStack(replacementEntry, false)
+        assertThat(navigatorState.backStack.value)
+            .containsExactly(entry)
+
         fragmentManager.executePendingTransactions()
         assertWithMessage("Initial Fragment should be on top of back stack after pop")
             .that(fragmentManager.findFragmentById(R.id.container))
@@ -297,47 +294,37 @@
     @UiThreadTest
     @Test
     fun testPopInitial() {
-        val fragmentNavigator = FragmentNavigator(
-            emptyActivity,
-            fragmentManager, R.id.container
-        )
-        val destination = fragmentNavigator.createDestination()
-        destination.id = INITIAL_FRAGMENT
-        destination.setClassName(EmptyFragment::class.java.name)
+        val entry = createBackStackEntry()
 
         // First push an initial Fragment
-        assertThat(fragmentNavigator.navigate(destination, null, null, null))
-            .isEqualTo(destination)
+        fragmentNavigator.navigate(listOf(entry), null, null)
+        assertThat(navigatorState.backStack.value)
+            .containsExactly(entry)
 
         // Now pop the initial Fragment
-        val popped = fragmentNavigator.popBackStack()
-        assertWithMessage(
-            "FragmentNavigator should return false when popping " +
-                "the initial Fragment"
-        )
-            .that(popped)
-            .isTrue()
+        fragmentNavigator.popBackStack(entry, false)
+        assertThat(navigatorState.backStack.value)
+            .isEmpty()
     }
 
     @UiThreadTest
     @Test
     fun testPop() {
-        val fragmentNavigator = FragmentNavigator(emptyActivity, fragmentManager, R.id.container)
-        val destination = fragmentNavigator.createDestination()
-        destination.id = INITIAL_FRAGMENT
-        destination.setClassName(EmptyFragment::class.java.name)
+        val entry = createBackStackEntry()
 
         // First push an initial Fragment
-        assertThat(fragmentNavigator.navigate(destination, null, null, null))
-            .isEqualTo(destination)
+        fragmentNavigator.navigate(listOf(entry), null, null)
+        assertThat(navigatorState.backStack.value)
+            .containsExactly(entry)
         fragmentManager.executePendingTransactions()
         val fragment = fragmentManager.findFragmentById(R.id.container)
         assertNotNull("Fragment should be added", fragment)
 
         // Now push the Fragment that we want to pop
-        destination.id = SECOND_FRAGMENT
-        assertThat(fragmentNavigator.navigate(destination, null, null, null))
-            .isEqualTo(destination)
+        val replacementEntry = createBackStackEntry(SECOND_FRAGMENT)
+        fragmentNavigator.navigate(listOf(replacementEntry), null, null)
+        assertThat(navigatorState.backStack.value)
+            .containsExactly(entry, replacementEntry).inOrder()
         fragmentManager.executePendingTransactions()
         val replacementFragment = fragmentManager.findFragmentById(R.id.container)
         assertNotNull("Replacement Fragment should be added", replacementFragment)
@@ -351,9 +338,10 @@
         )
 
         // Now pop the Fragment
-        val popped = fragmentNavigator.popBackStack()
+        fragmentNavigator.popBackStack(replacementEntry, false)
         fragmentManager.executePendingTransactions()
-        assertTrue("FragmentNavigator should return true when popping a Fragment", popped)
+        assertThat(navigatorState.backStack.value)
+            .containsExactly(entry)
         assertEquals(
             "Fragment should be the primary navigation Fragment after pop",
             fragment, fragmentManager.primaryNavigationFragment
@@ -363,14 +351,12 @@
     @UiThreadTest
     @Test
     fun testPopWithSameDestinationTwice() {
-        val fragmentNavigator = FragmentNavigator(emptyActivity, fragmentManager, R.id.container)
-        val destination = fragmentNavigator.createDestination()
-        destination.id = INITIAL_FRAGMENT
-        destination.setClassName(EmptyFragment::class.java.name)
+        val entry = createBackStackEntry()
 
         // First push an initial Fragment
-        assertThat(fragmentNavigator.navigate(destination, null, null, null))
-            .isEqualTo(destination)
+        fragmentNavigator.navigate(listOf(entry), null, null)
+        assertThat(navigatorState.backStack.value)
+            .containsExactly(entry)
         fragmentManager.executePendingTransactions()
         val fragment = fragmentManager.findFragmentById(R.id.container)
         assertWithMessage("Fragment should be added")
@@ -378,9 +364,10 @@
             .isNotNull()
 
         // Now push a second Fragment
-        destination.id = SECOND_FRAGMENT
-        assertThat(fragmentNavigator.navigate(destination, null, null, null))
-            .isEqualTo(destination)
+        val secondEntry = createBackStackEntry(SECOND_FRAGMENT)
+        fragmentNavigator.navigate(listOf(secondEntry), null, null)
+        assertThat(navigatorState.backStack.value)
+            .containsExactly(entry, secondEntry).inOrder()
         fragmentManager.executePendingTransactions()
         val replacementFragment = fragmentManager.findFragmentById(R.id.container)
         assertWithMessage("Replacement Fragment should be added")
@@ -392,8 +379,10 @@
 
         // Push the same Fragment a second time, creating a stack of two
         // identical Fragments
-        assertThat(fragmentNavigator.navigate(destination, null, null, null))
-            .isEqualTo(destination)
+        val replacementEntry = createBackStackEntry(SECOND_FRAGMENT)
+        fragmentNavigator.navigate(listOf(replacementEntry), null, null)
+        assertThat(navigatorState.backStack.value)
+            .containsExactly(entry, secondEntry, replacementEntry).inOrder()
         fragmentManager.executePendingTransactions()
         val fragmentToPop = fragmentManager.findFragmentById(R.id.container)
         assertWithMessage("Fragment to pop should be added")
@@ -404,11 +393,10 @@
             .isSameInstanceAs(fragmentToPop)
 
         // Now pop the Fragment
-        val popped = fragmentNavigator.popBackStack()
+        fragmentNavigator.popBackStack(replacementEntry, false)
         fragmentManager.executePendingTransactions()
-        assertWithMessage("FragmentNavigator should return true when popping a Fragment")
-            .that(popped)
-            .isTrue()
+        assertThat(navigatorState.backStack.value)
+            .containsExactly(entry, secondEntry).inOrder()
         assertWithMessage(
             "Replacement Fragment should be the primary navigation Fragment " +
                 "after pop"
@@ -420,22 +408,21 @@
     @UiThreadTest
     @Test
     fun testPopWithChildFragmentBackStack() {
-        val fragmentNavigator = FragmentNavigator(emptyActivity, fragmentManager, R.id.container)
-        val destination = fragmentNavigator.createDestination()
-        destination.id = INITIAL_FRAGMENT
-        destination.setClassName(EmptyFragment::class.java.name)
+        val entry = createBackStackEntry()
 
         // First push an initial Fragment
-        assertThat(fragmentNavigator.navigate(destination, null, null, null))
-            .isEqualTo(destination)
+        fragmentNavigator.navigate(listOf(entry), null, null)
+        assertThat(navigatorState.backStack.value)
+            .containsExactly(entry)
         fragmentManager.executePendingTransactions()
         val fragment = fragmentManager.findFragmentById(R.id.container)
         assertNotNull("Fragment should be added", fragment)
 
         // Now push the Fragment that we want to pop
-        destination.id = SECOND_FRAGMENT
-        assertThat(fragmentNavigator.navigate(destination, null, null, null))
-            .isEqualTo(destination)
+        val replacementEntry = createBackStackEntry(SECOND_FRAGMENT)
+        fragmentNavigator.navigate(listOf(replacementEntry), null, null)
+        assertThat(navigatorState.backStack.value)
+            .containsExactly(entry, replacementEntry).inOrder()
         fragmentManager.executePendingTransactions()
         val replacementFragment = fragmentManager.findFragmentById(R.id.container)
         assertNotNull("Replacement Fragment should be added", replacementFragment)
@@ -458,9 +445,10 @@
         }
 
         // Now pop the Fragment
-        val popped = fragmentNavigator.popBackStack()
+        fragmentNavigator.popBackStack(replacementEntry, false)
         fragmentManager.executePendingTransactions()
-        assertTrue("FragmentNavigator should return true when popping a Fragment", popped)
+        assertThat(navigatorState.backStack.value)
+            .containsExactly(entry)
         assertEquals(
             "Fragment should be the primary navigation Fragment after pop",
             fragment, fragmentManager.primaryNavigationFragment
@@ -470,22 +458,19 @@
     @UiThreadTest
     @Test
     fun testDeepLinkPop() {
-        val fragmentNavigator = FragmentNavigator(emptyActivity, fragmentManager, R.id.container)
-        val destination = fragmentNavigator.createDestination()
-        destination.id = INITIAL_FRAGMENT
-        destination.setClassName(EmptyFragment::class.java.name)
+        val entry = createBackStackEntry()
+        val secondEntry = createBackStackEntry(SECOND_FRAGMENT)
 
         // First push two Fragments as our 'deep link'
-        assertThat(fragmentNavigator.navigate(destination, null, null, null))
-            .isEqualTo(destination)
-        destination.id = SECOND_FRAGMENT
-        assertThat(fragmentNavigator.navigate(destination, null, null, null))
-            .isEqualTo(destination)
+        fragmentNavigator.navigate(listOf(entry, secondEntry), null, null)
+        assertThat(navigatorState.backStack.value)
+            .containsExactly(entry, secondEntry)
 
         // Now push the Fragment that we want to pop
-        destination.id = THIRD_FRAGMENT
-        assertThat(fragmentNavigator.navigate(destination, null, null, null))
-            .isEqualTo(destination)
+        val thirdEntry = createBackStackEntry(THIRD_FRAGMENT)
+        fragmentNavigator.navigate(listOf(thirdEntry), null, null)
+        assertThat(navigatorState.backStack.value)
+            .containsExactly(entry, secondEntry, thirdEntry)
         fragmentManager.executePendingTransactions()
         val replacementFragment = fragmentManager.findFragmentById(R.id.container)
         assertNotNull("Replacement Fragment should be added", replacementFragment)
@@ -499,7 +484,7 @@
         )
 
         // Now pop the Fragment
-        fragmentNavigator.popBackStack()
+        fragmentNavigator.popBackStack(thirdEntry, false)
         fragmentManager.executePendingTransactions()
         val fragment = fragmentManager.findFragmentById(R.id.container)
         assertEquals(
@@ -510,156 +495,19 @@
 
     @UiThreadTest
     @Test
-    fun testDeepLinkPopWithSaveState() {
-        var fragmentNavigator = FragmentNavigator(
-            emptyActivity,
-            fragmentManager, R.id.container
-        )
-        val destination = fragmentNavigator.createDestination()
-        destination.id = INITIAL_FRAGMENT
-        destination.setClassName(EmptyFragment::class.java.name)
-
-        // First push two Fragments as our 'deep link'
-        assertThat(fragmentNavigator.navigate(destination, null, null, null))
-            .isEqualTo(destination)
-        destination.id = SECOND_FRAGMENT
-        assertThat(fragmentNavigator.navigate(destination, null, null, null))
-            .isEqualTo(destination)
-
-        // Now push the Fragment that we want to pop
-        destination.id = THIRD_FRAGMENT
-        assertThat(fragmentNavigator.navigate(destination, null, null, null))
-            .isEqualTo(destination)
-        fragmentManager.executePendingTransactions()
-        val replacementFragment = fragmentManager.findFragmentById(R.id.container)
-        assertNotNull("Replacement Fragment should be added", replacementFragment)
-        assertTrue(
-            "Replacement Fragment should be the correct type",
-            replacementFragment is EmptyFragment
-        )
-        assertEquals(
-            "Replacement Fragment should be the primary navigation Fragment",
-            replacementFragment, fragmentManager.primaryNavigationFragment
-        )
-
-        // Create a new FragmentNavigator, replacing the previous one
-        val savedState = fragmentNavigator.onSaveState() as Bundle
-        fragmentNavigator = FragmentNavigator(
-            emptyActivity,
-            fragmentManager, R.id.container
-        )
-        fragmentNavigator.onRestoreState(savedState)
-
-        // Now pop the Fragment
-        fragmentNavigator.popBackStack()
-        fragmentManager.executePendingTransactions()
-        val fragment = fragmentManager.findFragmentById(R.id.container)
-        assertEquals(
-            "Fragment should be the primary navigation Fragment after pop",
-            fragment, fragmentManager.primaryNavigationFragment
-        )
-    }
-
-    @UiThreadTest
-    @Test
-    fun testNavigateThenPopAfterSaveState() {
-        var fragmentNavigator = FragmentNavigator(
-            emptyActivity,
-            fragmentManager, R.id.container
-        )
-        val destination = fragmentNavigator.createDestination()
-        destination.id = INITIAL_FRAGMENT
-        destination.setClassName(EmptyFragment::class.java.name)
-
-        assertThat(fragmentNavigator.navigate(destination, null, null, null))
-            .isEqualTo(destination)
-        fragmentManager.executePendingTransactions()
-        var fragment = fragmentManager.findFragmentById(R.id.container)
-        assertNotNull("Fragment should be added", fragment)
-        assertEquals(
-            "Fragment should be the correct type",
-            EmptyFragment::class.java, fragment!!::class.java
-        )
-        assertEquals(
-            "Fragment should be the primary navigation Fragment",
-            fragment, fragmentManager.primaryNavigationFragment
-        )
-
-        // Now push a second fragment
-        destination.id = SECOND_FRAGMENT
-        assertThat(fragmentNavigator.navigate(destination, null, null, null))
-            .isEqualTo(destination)
-        fragmentManager.executePendingTransactions()
-        var replacementFragment = fragmentManager.findFragmentById(R.id.container)
-        assertNotNull("Replacement Fragment should be added", replacementFragment)
-        assertEquals(
-            "Replacement Fragment should be the correct type",
-            EmptyFragment::class.java, replacementFragment!!::class.java
-        )
-        assertEquals(
-            "Replacement Fragment should be the primary navigation Fragment",
-            replacementFragment, fragmentManager.primaryNavigationFragment
-        )
-
-        // Create a new FragmentNavigator, replacing the previous one
-        val savedState = fragmentNavigator.onSaveState() as Bundle
-        fragmentNavigator = FragmentNavigator(
-            emptyActivity,
-            fragmentManager, R.id.container
-        )
-        fragmentNavigator.onRestoreState(savedState)
-
-        // Now push a third fragment after the state save
-        destination.id = THIRD_FRAGMENT
-        assertThat(fragmentNavigator.navigate(destination, null, null, null))
-            .isEqualTo(destination)
-        fragmentManager.executePendingTransactions()
-        replacementFragment = fragmentManager.findFragmentById(R.id.container)
-        assertNotNull("Replacement Fragment should be added", replacementFragment)
-        assertTrue(
-            "Replacement Fragment should be the correct type",
-            replacementFragment is EmptyFragment
-        )
-        assertEquals(
-            "Replacement Fragment should be the primary navigation Fragment",
-            replacementFragment, fragmentManager.primaryNavigationFragment
-        )
-
-        // Now pop the Fragment
-        fragmentNavigator.popBackStack()
-        fragmentManager.executePendingTransactions()
-        fragment = fragmentManager.findFragmentById(R.id.container)
-        assertEquals(
-            "Fragment should be the primary navigation Fragment after pop",
-            fragment, fragmentManager.primaryNavigationFragment
-        )
-    }
-
-    @UiThreadTest
-    @Test
     fun testMultipleNavigateFragmentTransactionsThenPop() {
-        val fragmentNavigator = FragmentNavigator(
-            emptyActivity,
-            fragmentManager, R.id.container
-        )
-        val destination = fragmentNavigator.createDestination()
-        destination.setClassName(EmptyFragment::class.java.name)
-        val destination2 = fragmentNavigator.createDestination()
-        destination2.setClassName(Fragment::class.java.name)
+        val entry = createBackStackEntry()
+        val secondEntry = createBackStackEntry(SECOND_FRAGMENT, clazz = Fragment::class)
+        val thirdEntry = createBackStackEntry(THIRD_FRAGMENT)
 
         // Push 3 fragments without executing pending transactions.
-        destination.id = INITIAL_FRAGMENT
-        fragmentNavigator.navigate(destination, null, null, null)
-        destination2.id = SECOND_FRAGMENT
-        fragmentNavigator.navigate(destination2, null, null, null)
-        destination.id = THIRD_FRAGMENT
-        fragmentNavigator.navigate(destination, null, null, null)
+        fragmentNavigator.navigate(listOf(entry, secondEntry, thirdEntry), null, null)
 
         // Now pop the Fragment
-        val popped = fragmentNavigator.popBackStack()
+        fragmentNavigator.popBackStack(thirdEntry, false)
         fragmentManager.executePendingTransactions()
-        assertWithMessage("FragmentNavigator should return true when popping the third fragment")
-            .that(popped).isTrue()
+        assertThat(navigatorState.backStack.value)
+            .containsExactly(entry, secondEntry)
         // We should ensure the fragment manager is on the proper fragment at the end
         assertWithMessage("FragmentManager back stack should have only SECOND_FRAGMENT")
             .that(fragmentManager.backStackEntryCount)
@@ -672,36 +520,176 @@
     @UiThreadTest
     @Test
     fun testMultiplePopFragmentTransactionsThenPop() {
-        val fragmentNavigator = FragmentNavigator(
-            emptyActivity,
-            fragmentManager, R.id.container
-        )
-        val destination = fragmentNavigator.createDestination()
-        destination.setClassName(EmptyFragment::class.java.name)
+        val entry = createBackStackEntry()
+        val secondEntry = createBackStackEntry(SECOND_FRAGMENT)
+        val thirdEntry = createBackStackEntry(THIRD_FRAGMENT)
+        val fourthEntry = createBackStackEntry(FOURTH_FRAGMENT)
 
         // Push 4 fragments
-        destination.id = INITIAL_FRAGMENT
-        fragmentNavigator.navigate(destination, null, null, null)
-        destination.id = SECOND_FRAGMENT
-        fragmentNavigator.navigate(destination, null, null, null)
-        destination.id = THIRD_FRAGMENT
-        fragmentNavigator.navigate(destination, null, null, null)
-        destination.id = FOURTH_FRAGMENT
-        fragmentNavigator.navigate(destination, null, null, null)
+        fragmentNavigator.navigate(
+            listOf(entry, secondEntry, thirdEntry, fourthEntry),
+            null, null
+        )
         fragmentManager.executePendingTransactions()
 
         // Pop 2 fragments without executing pending transactions.
-        fragmentNavigator.popBackStack()
-        fragmentNavigator.popBackStack()
+        fragmentNavigator.popBackStack(thirdEntry, false)
 
-        val popped = fragmentNavigator.popBackStack()
+        fragmentNavigator.popBackStack(secondEntry, false)
         fragmentManager.executePendingTransactions()
-        assertTrue("FragmentNavigator should return true when popping the third fragment", popped)
+        assertThat(navigatorState.backStack.value)
+            .containsExactly(entry)
+    }
+
+    @UiThreadTest
+    @Test
+    fun testSaveRestoreState() {
+        val entry = createBackStackEntry()
+
+        // First push an initial Fragment
+        fragmentNavigator.navigate(listOf(entry), null, null)
+        assertThat(navigatorState.backStack.value)
+            .containsExactly(entry)
+        fragmentManager.executePendingTransactions()
+        val fragment = fragmentManager.findFragmentById(R.id.container)
+        assertWithMessage("Fragment should be added")
+            .that(fragment)
+            .isNotNull()
+
+        // Now push the Fragment that we want to save
+        val replacementEntry = createBackStackEntry(SECOND_FRAGMENT, SavedStateFragment::class)
+        fragmentNavigator.navigate(listOf(replacementEntry), null, null)
+        assertThat(navigatorState.backStack.value)
+            .containsExactly(entry, replacementEntry).inOrder()
+        fragmentManager.executePendingTransactions()
+        val replacementFragment = fragmentManager.findFragmentById(R.id.container)
+        assertWithMessage("Replacement Fragment should be added")
+            .that(replacementFragment)
+            .isNotNull()
+        assertWithMessage("Replacement Fragment should be the correct type")
+            .that(replacementFragment)
+            .isInstanceOf(SavedStateFragment::class.java)
+        assertWithMessage("Replacement Fragment should be the primary navigation Fragment")
+            .that(fragmentManager.primaryNavigationFragment)
+            .isSameInstanceAs(replacementFragment)
+
+        // Save some state into the replacement fragment
+        (replacementFragment as SavedStateFragment).savedState = "test"
+
+        // Now save the Fragment
+        fragmentNavigator.popBackStack(replacementEntry, true)
+        fragmentManager.executePendingTransactions()
+        assertThat(navigatorState.backStack.value)
+            .containsExactly(entry)
+        assertWithMessage("Fragment should be the primary navigation Fragment after pop")
+            .that(fragmentManager.primaryNavigationFragment)
+            .isSameInstanceAs(fragment)
+
+        // And now restore the fragment
+        val restoredEntry = navigatorState.restoreBackStackEntry(replacementEntry)
+        fragmentNavigator.navigate(
+            listOf(restoredEntry),
+            NavOptions.Builder().setRestoreState(true).build(), null
+        )
+        assertThat(navigatorState.backStack.value)
+            .containsExactly(entry, restoredEntry).inOrder()
+        fragmentManager.executePendingTransactions()
+        val restoredFragment = fragmentManager.findFragmentById(R.id.container)
+        assertWithMessage("Restored Fragment should be added")
+            .that(restoredFragment)
+            .isNotNull()
+        assertWithMessage("Restored Fragment should be the correct type")
+            .that(restoredFragment)
+            .isInstanceOf(SavedStateFragment::class.java)
+        assertWithMessage("Restored Fragment should be the primary navigation Fragment")
+            .that(fragmentManager.primaryNavigationFragment)
+            .isSameInstanceAs(restoredFragment)
+
+        assertWithMessage("Restored Fragment should have its state restored")
+            .that((restoredFragment as SavedStateFragment).savedState)
+            .isEqualTo("test")
+    }
+
+    @UiThreadTest
+    @Test
+    fun testSaveRestoreStateAfterSaveState() {
+        val entry = createBackStackEntry()
+
+        // First push an initial Fragment
+        fragmentNavigator.navigate(listOf(entry), null, null)
+        assertThat(navigatorState.backStack.value)
+            .containsExactly(entry)
+        fragmentManager.executePendingTransactions()
+        val fragment = fragmentManager.findFragmentById(R.id.container)
+        assertWithMessage("Fragment should be added")
+            .that(fragment)
+            .isNotNull()
+
+        // Now push the Fragment that we want to save
+        val replacementEntry = createBackStackEntry(SECOND_FRAGMENT, SavedStateFragment::class)
+        fragmentNavigator.navigate(listOf(replacementEntry), null, null)
+        assertThat(navigatorState.backStack.value)
+            .containsExactly(entry, replacementEntry).inOrder()
+        fragmentManager.executePendingTransactions()
+        val replacementFragment = fragmentManager.findFragmentById(R.id.container)
+        assertWithMessage("Replacement Fragment should be added")
+            .that(replacementFragment)
+            .isNotNull()
+        assertWithMessage("Replacement Fragment should be the correct type")
+            .that(replacementFragment)
+            .isInstanceOf(SavedStateFragment::class.java)
+        assertWithMessage("Replacement Fragment should be the primary navigation Fragment")
+            .that(fragmentManager.primaryNavigationFragment)
+            .isSameInstanceAs(replacementFragment)
+
+        // Save some state into the replacement fragment
+        (replacementFragment as SavedStateFragment).savedState = "test"
+
+        // Now save the Fragment
+        fragmentNavigator.popBackStack(replacementEntry, true)
+        fragmentManager.executePendingTransactions()
+        assertThat(navigatorState.backStack.value)
+            .containsExactly(entry)
+        assertWithMessage("Fragment should be the primary navigation Fragment after pop")
+            .that(fragmentManager.primaryNavigationFragment)
+            .isSameInstanceAs(fragment)
+
+        // Create a new FragmentNavigator, replacing the previous one
+        val savedState = fragmentNavigator.onSaveState() as Bundle
+        fragmentNavigator = FragmentNavigator(
+            emptyActivity,
+            fragmentManager, R.id.container
+        )
+        fragmentNavigator.onAttach(navigatorState)
+        fragmentNavigator.onRestoreState(savedState)
+
+        // And now restore the fragment
+        val restoredEntry = navigatorState.restoreBackStackEntry(replacementEntry)
+        fragmentNavigator.navigate(
+            listOf(restoredEntry),
+            NavOptions.Builder().setRestoreState(true).build(), null
+        )
+        assertThat(navigatorState.backStack.value)
+            .containsExactly(entry, restoredEntry).inOrder()
+        fragmentManager.executePendingTransactions()
+        val restoredFragment = fragmentManager.findFragmentById(R.id.container)
+        assertWithMessage("Restored Fragment should be added")
+            .that(restoredFragment)
+            .isNotNull()
+        assertWithMessage("Restored Fragment should be the correct type")
+            .that(restoredFragment)
+            .isInstanceOf(SavedStateFragment::class.java)
+        assertWithMessage("Restored Fragment should be the primary navigation Fragment")
+            .that(fragmentManager.primaryNavigationFragment)
+            .isSameInstanceAs(restoredFragment)
+
+        assertWithMessage("Restored Fragment should have its state restored")
+            .that((restoredFragment as SavedStateFragment).savedState)
+            .isEqualTo("test")
     }
 
     @Test
     fun testToString() {
-        val fragmentNavigator = FragmentNavigator(emptyActivity, fragmentManager, R.id.container)
         val destination = fragmentNavigator.createDestination().apply {
             id = INITIAL_FRAGMENT
             setClassName(EmptyFragment::class.java.name)
@@ -714,7 +702,6 @@
 
     @Test
     fun testToStringNoClassName() {
-        val fragmentNavigator = FragmentNavigator(emptyActivity, fragmentManager, R.id.container)
         val destination = fragmentNavigator.createDestination().apply {
             id = INITIAL_FRAGMENT
             label = TEST_LABEL
@@ -723,6 +710,17 @@
             "class=null"
         assertThat(destination.toString()).isEqualTo(expected)
     }
+
+    private fun createBackStackEntry(
+        destId: Int = INITIAL_FRAGMENT,
+        clazz: KClass<out Fragment> = EmptyFragment::class
+    ): NavBackStackEntry {
+        val destination = fragmentNavigator.createDestination().apply {
+            id = destId
+            setClassName(clazz.java.name)
+        }
+        return navigatorState.createBackStackEntry(destination, null)
+    }
 }
 
 class EmptyActivity : FragmentActivity() {
@@ -732,6 +730,20 @@
     }
 }
 
+class SavedStateFragment : Fragment() {
+    var savedState: String? = null
+
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+        savedState = savedInstanceState?.getString("savedState")
+    }
+
+    override fun onSaveInstanceState(outState: Bundle) {
+        super.onSaveInstanceState(outState)
+        outState.putString("savedState", savedState)
+    }
+}
+
 class NonEmptyConstructorFragment(val test: String) : Fragment()
 
 class NonEmptyFragmentFactory : FragmentFactory() {
diff --git a/navigation/navigation-fragment/src/main/java/androidx/navigation/fragment/FragmentNavigator.kt b/navigation/navigation-fragment/src/main/java/androidx/navigation/fragment/FragmentNavigator.kt
index 4dd7e37..7e575fc 100644
--- a/navigation/navigation-fragment/src/main/java/androidx/navigation/fragment/FragmentNavigator.kt
+++ b/navigation/navigation-fragment/src/main/java/androidx/navigation/fragment/FragmentNavigator.kt
@@ -23,8 +23,10 @@
 import androidx.annotation.CallSuper
 import androidx.annotation.IdRes
 import androidx.core.content.res.use
+import androidx.core.os.bundleOf
 import androidx.fragment.app.Fragment
 import androidx.fragment.app.FragmentManager
+import androidx.navigation.NavBackStackEntry
 import androidx.navigation.NavDestination
 import androidx.navigation.NavOptions
 import androidx.navigation.Navigator
@@ -50,7 +52,7 @@
     private val fragmentManager: FragmentManager,
     private val containerId: Int
 ) : Navigator<Destination>() {
-    private val backStack = ArrayDeque<Int>()
+    private val savedIds = mutableSetOf<String>()
 
     /**
      * {@inheritDoc}
@@ -64,22 +66,41 @@
      * asynchronously, so the newly visible Fragment from the back stack
      * is not instantly available after this call completes.
      */
-    public override fun popBackStack(): Boolean {
-        if (backStack.isEmpty()) {
-            return false
-        }
+    override fun popBackStack(popUpTo: NavBackStackEntry, savedState: Boolean) {
         if (fragmentManager.isStateSaved) {
             Log.i(
                 TAG, "Ignoring popBackStack() call: FragmentManager has already saved its state"
             )
-            return false
+            return
         }
-        fragmentManager.popBackStack(
-            generateBackStackName(backStack.size, backStack.last()),
-            FragmentManager.POP_BACK_STACK_INCLUSIVE
-        )
-        backStack.removeLast()
-        return true
+        if (savedState) {
+            val beforePopList = state.backStack.value
+            val initialEntry = beforePopList.first()
+            // Get the set of entries that are going to be popped
+            val poppedList = beforePopList.subList(
+                beforePopList.indexOf(popUpTo),
+                beforePopList.size
+            )
+            // Now go through the list in reversed order (i.e., started from the most added)
+            // and save the back stack state of each.
+            for (entry in poppedList.reversed()) {
+                if (entry == initialEntry) {
+                    Log.i(
+                        TAG,
+                        "FragmentManager cannot save the state of the initial destination $entry"
+                    )
+                } else {
+                    fragmentManager.saveBackStack(entry.id)
+                    savedIds += entry.id
+                }
+            }
+        } else {
+            fragmentManager.popBackStack(
+                popUpTo.id,
+                FragmentManager.POP_BACK_STACK_INCLUSIVE
+            )
+        }
+        state.pop(popUpTo, savedState)
     }
 
     public override fun createDestination(): Destination {
@@ -126,18 +147,42 @@
      * asynchronously, so the new Fragment is not instantly available
      * after this call completes.
      */
-    public override fun navigate(
-        destination: Destination,
-        args: Bundle?,
+    override fun navigate(
+        entries: List<NavBackStackEntry>,
         navOptions: NavOptions?,
         navigatorExtras: Navigator.Extras?
-    ): NavDestination? {
+    ) {
         if (fragmentManager.isStateSaved) {
             Log.i(
                 TAG, "Ignoring navigate() call: FragmentManager has already saved its state"
             )
-            return null
+            return
         }
+        for (entry in entries) {
+            navigate(entry, navOptions, navigatorExtras)
+        }
+    }
+
+    private fun navigate(
+        entry: NavBackStackEntry,
+        navOptions: NavOptions?,
+        navigatorExtras: Navigator.Extras?
+    ) {
+        val backStack = state.backStack.value
+        val initialNavigation = backStack.isEmpty()
+        val restoreState = (
+            navOptions != null && !initialNavigation &&
+                navOptions.shouldRestoreState() &&
+                savedIds.remove(entry.id)
+            )
+        if (restoreState) {
+            // Restore back stack does all the work to restore the entry
+            fragmentManager.restoreBackStack(entry.id)
+            state.add(entry)
+            return
+        }
+        val destination = entry.destination as Destination
+        val args = entry.arguments
         var className = destination.className
         if (className[0] == '.') {
             className = context.packageName + className
@@ -159,15 +204,13 @@
         ft.replace(containerId, frag)
         ft.setPrimaryNavigationFragment(frag)
         @IdRes val destId = destination.id
-        val initialNavigation = backStack.isEmpty()
         // TODO Build first class singleTop behavior for fragments
         val isSingleTopReplacement = (
             navOptions != null && !initialNavigation &&
                 navOptions.shouldLaunchSingleTop() &&
-                backStack.last() == destId
+                backStack.last().destination.id == destId
             )
-        val isAdded: Boolean
-        isAdded = when {
+        val isAdded = when {
             initialNavigation -> {
                 true
             }
@@ -179,15 +222,15 @@
                     // remove it from the back stack and put our replacement
                     // on the back stack in its place
                     fragmentManager.popBackStack(
-                        generateBackStackName(backStack.size, backStack.last()),
+                        entry.id,
                         FragmentManager.POP_BACK_STACK_INCLUSIVE
                     )
-                    ft.addToBackStack(generateBackStackName(backStack.size, destId))
+                    ft.addToBackStack(entry.id)
                 }
                 false
             }
             else -> {
-                ft.addToBackStack(generateBackStackName(backStack.size + 1, destId))
+                ft.addToBackStack(entry.id)
                 true
             }
         }
@@ -199,35 +242,26 @@
         ft.setReorderingAllowed(true)
         ft.commit()
         // The commit succeeded, update our view of the world
-        return if (isAdded) {
-            backStack.add(destId)
-            destination
-        } else {
-            null
+        if (isAdded) {
+            state.add(entry)
         }
     }
 
     public override fun onSaveState(): Bundle? {
-        val b = Bundle()
-        val backStack = backStack.toIntArray()
-        b.putIntArray(KEY_BACK_STACK_IDS, backStack)
-        return b
+        if (savedIds.isEmpty()) {
+            return null
+        }
+        return bundleOf(KEY_SAVED_IDS to ArrayList(savedIds))
     }
 
     public override fun onRestoreState(savedState: Bundle) {
-        val backStack = savedState.getIntArray(KEY_BACK_STACK_IDS)
-        if (backStack != null) {
-            this.backStack.clear()
-            for (destId in backStack) {
-                this.backStack.add(destId)
-            }
+        val savedIds = savedState.getStringArrayList(KEY_SAVED_IDS)
+        if (savedIds != null) {
+            this.savedIds.clear()
+            this.savedIds += savedIds
         }
     }
 
-    private fun generateBackStackName(backStackIndex: Int, destId: Int): String {
-        return "$backStackIndex-$destId"
-    }
-
     /**
      * NavDestination specific to [FragmentNavigator]
      */
@@ -368,6 +402,6 @@
 
     private companion object {
         private const val TAG = "FragmentNavigator"
-        private const val KEY_BACK_STACK_IDS = "androidx-nav-fragment:navigator:backStackIds"
+        private const val KEY_SAVED_IDS = "androidx-nav-fragment:navigator:savedIds"
     }
 }
diff --git a/navigation/navigation-runtime-ktx/lint-baseline.xml b/navigation/navigation-runtime-ktx/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/navigation/navigation-runtime-ktx/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/navigation/navigation-runtime-truth/lint-baseline.xml b/navigation/navigation-runtime-truth/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/navigation/navigation-runtime-truth/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/navigation/navigation-runtime/api/current.txt b/navigation/navigation-runtime/api/current.txt
index ebbad33..219326d 100644
--- a/navigation/navigation-runtime/api/current.txt
+++ b/navigation/navigation-runtime/api/current.txt
@@ -91,6 +91,7 @@
     method public void addOnDestinationChangedListener(androidx.navigation.NavController.OnDestinationChangedListener listener);
     method public androidx.navigation.NavDeepLinkBuilder createDeepLink();
     method public androidx.navigation.NavBackStackEntry getBackStackEntry(@IdRes int destinationId);
+    method public final androidx.navigation.NavBackStackEntry getBackStackEntry(String route);
     method public androidx.navigation.NavBackStackEntry? getCurrentBackStackEntry();
     method public final kotlinx.coroutines.flow.Flow<androidx.navigation.NavBackStackEntry> getCurrentBackStackEntryFlow();
     method public androidx.navigation.NavDestination? getCurrentDestination();
@@ -113,10 +114,16 @@
     method @MainThread public void navigate(androidx.navigation.NavDirections directions);
     method @MainThread public void navigate(androidx.navigation.NavDirections directions, androidx.navigation.NavOptions? navOptions);
     method @MainThread public void navigate(androidx.navigation.NavDirections directions, androidx.navigation.Navigator.Extras navigatorExtras);
+    method public final void navigate(String route, kotlin.jvm.functions.Function1<? super androidx.navigation.NavOptionsBuilder,kotlin.Unit> builder);
+    method public final void navigate(String route, optional androidx.navigation.NavOptions? navOptions, optional androidx.navigation.Navigator.Extras? navigatorExtras);
+    method public final void navigate(String route, optional androidx.navigation.NavOptions? navOptions);
+    method public final void navigate(String route);
     method @MainThread public boolean navigateUp();
     method @MainThread public boolean popBackStack();
     method @MainThread public boolean popBackStack(@IdRes int destinationId, boolean inclusive);
     method @MainThread public boolean popBackStack(@IdRes int destinationId, boolean inclusive, boolean saveState);
+    method @MainThread public final boolean popBackStack(String route, boolean inclusive, optional boolean saveState);
+    method @MainThread public final boolean popBackStack(String route, boolean inclusive);
     method public void removeOnDestinationChangedListener(androidx.navigation.NavController.OnDestinationChangedListener listener);
     method @CallSuper public void restoreState(android.os.Bundle? navState);
     method @CallSuper public android.os.Bundle? saveState();
@@ -144,12 +151,15 @@
 
   public final class NavControllerKt {
     method public static inline androidx.navigation.NavGraph createGraph(androidx.navigation.NavController, optional @IdRes int id, @IdRes int startDestination, kotlin.jvm.functions.Function1<? super androidx.navigation.NavGraphBuilder,kotlin.Unit> builder);
+    method public static androidx.navigation.NavGraph createGraph(androidx.navigation.NavController, String startDestination, optional String? route, kotlin.jvm.functions.Function1<? super androidx.navigation.NavGraphBuilder,kotlin.Unit> builder);
   }
 
   public final class NavDeepLinkBuilder {
     ctor public NavDeepLinkBuilder(android.content.Context context);
     method public androidx.navigation.NavDeepLinkBuilder addDestination(@IdRes int destId, optional android.os.Bundle? args);
     method public androidx.navigation.NavDeepLinkBuilder addDestination(@IdRes int destId);
+    method public androidx.navigation.NavDeepLinkBuilder addDestination(String route, optional android.os.Bundle? args);
+    method public androidx.navigation.NavDeepLinkBuilder addDestination(String route);
     method public android.app.PendingIntent createPendingIntent();
     method public androidx.core.app.TaskStackBuilder createTaskStackBuilder();
     method public androidx.navigation.NavDeepLinkBuilder setArguments(android.os.Bundle? args);
@@ -157,6 +167,8 @@
     method public androidx.navigation.NavDeepLinkBuilder setComponentName(android.content.ComponentName componentName);
     method public androidx.navigation.NavDeepLinkBuilder setDestination(@IdRes int destId, optional android.os.Bundle? args);
     method public androidx.navigation.NavDeepLinkBuilder setDestination(@IdRes int destId);
+    method public androidx.navigation.NavDeepLinkBuilder setDestination(String destRoute, optional android.os.Bundle? args);
+    method public androidx.navigation.NavDeepLinkBuilder setDestination(String destRoute);
     method public androidx.navigation.NavDeepLinkBuilder setGraph(@NavigationRes int navGraphId);
     method public androidx.navigation.NavDeepLinkBuilder setGraph(androidx.navigation.NavGraph navGraph);
   }
diff --git a/navigation/navigation-runtime/api/public_plus_experimental_current.txt b/navigation/navigation-runtime/api/public_plus_experimental_current.txt
index d627bde..308daa9 100644
--- a/navigation/navigation-runtime/api/public_plus_experimental_current.txt
+++ b/navigation/navigation-runtime/api/public_plus_experimental_current.txt
@@ -94,7 +94,9 @@
     method public androidx.navigation.NavDeepLinkBuilder createDeepLink();
     method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public void enableOnBackPressed(boolean enabled);
     method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public final androidx.navigation.NavDestination? findDestination(@IdRes int destinationId);
+    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public final androidx.navigation.NavDestination? findDestination(String destinationRoute);
     method public androidx.navigation.NavBackStackEntry getBackStackEntry(@IdRes int destinationId);
+    method public final androidx.navigation.NavBackStackEntry getBackStackEntry(String route);
     method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public final android.content.Context getContext();
     method public androidx.navigation.NavBackStackEntry? getCurrentBackStackEntry();
     method public final kotlinx.coroutines.flow.Flow<androidx.navigation.NavBackStackEntry> getCurrentBackStackEntryFlow();
@@ -118,10 +120,16 @@
     method @MainThread public void navigate(androidx.navigation.NavDirections directions);
     method @MainThread public void navigate(androidx.navigation.NavDirections directions, androidx.navigation.NavOptions? navOptions);
     method @MainThread public void navigate(androidx.navigation.NavDirections directions, androidx.navigation.Navigator.Extras navigatorExtras);
+    method public final void navigate(String route, kotlin.jvm.functions.Function1<? super androidx.navigation.NavOptionsBuilder,kotlin.Unit> builder);
+    method public final void navigate(String route, optional androidx.navigation.NavOptions? navOptions, optional androidx.navigation.Navigator.Extras? navigatorExtras);
+    method public final void navigate(String route, optional androidx.navigation.NavOptions? navOptions);
+    method public final void navigate(String route);
     method @MainThread public boolean navigateUp();
     method @MainThread public boolean popBackStack();
     method @MainThread public boolean popBackStack(@IdRes int destinationId, boolean inclusive);
     method @MainThread public boolean popBackStack(@IdRes int destinationId, boolean inclusive, boolean saveState);
+    method @MainThread public final boolean popBackStack(String route, boolean inclusive, optional boolean saveState);
+    method @MainThread public final boolean popBackStack(String route, boolean inclusive);
     method public void removeOnDestinationChangedListener(androidx.navigation.NavController.OnDestinationChangedListener listener);
     method @CallSuper public void restoreState(android.os.Bundle? navState);
     method @CallSuper public android.os.Bundle? saveState();
@@ -157,12 +165,15 @@
 
   public final class NavControllerKt {
     method public static inline androidx.navigation.NavGraph createGraph(androidx.navigation.NavController, optional @IdRes int id, @IdRes int startDestination, kotlin.jvm.functions.Function1<? super androidx.navigation.NavGraphBuilder,kotlin.Unit> builder);
+    method public static androidx.navigation.NavGraph createGraph(androidx.navigation.NavController, String startDestination, optional String? route, kotlin.jvm.functions.Function1<? super androidx.navigation.NavGraphBuilder,kotlin.Unit> builder);
   }
 
   public final class NavDeepLinkBuilder {
     ctor public NavDeepLinkBuilder(android.content.Context context);
     method public androidx.navigation.NavDeepLinkBuilder addDestination(@IdRes int destId, optional android.os.Bundle? args);
     method public androidx.navigation.NavDeepLinkBuilder addDestination(@IdRes int destId);
+    method public androidx.navigation.NavDeepLinkBuilder addDestination(String route, optional android.os.Bundle? args);
+    method public androidx.navigation.NavDeepLinkBuilder addDestination(String route);
     method public android.app.PendingIntent createPendingIntent();
     method public androidx.core.app.TaskStackBuilder createTaskStackBuilder();
     method public androidx.navigation.NavDeepLinkBuilder setArguments(android.os.Bundle? args);
@@ -170,6 +181,8 @@
     method public androidx.navigation.NavDeepLinkBuilder setComponentName(android.content.ComponentName componentName);
     method public androidx.navigation.NavDeepLinkBuilder setDestination(@IdRes int destId, optional android.os.Bundle? args);
     method public androidx.navigation.NavDeepLinkBuilder setDestination(@IdRes int destId);
+    method public androidx.navigation.NavDeepLinkBuilder setDestination(String destRoute, optional android.os.Bundle? args);
+    method public androidx.navigation.NavDeepLinkBuilder setDestination(String destRoute);
     method public androidx.navigation.NavDeepLinkBuilder setGraph(@NavigationRes int navGraphId);
     method public androidx.navigation.NavDeepLinkBuilder setGraph(androidx.navigation.NavGraph navGraph);
   }
diff --git a/navigation/navigation-runtime/api/restricted_current.txt b/navigation/navigation-runtime/api/restricted_current.txt
index ebbad33..219326d 100644
--- a/navigation/navigation-runtime/api/restricted_current.txt
+++ b/navigation/navigation-runtime/api/restricted_current.txt
@@ -91,6 +91,7 @@
     method public void addOnDestinationChangedListener(androidx.navigation.NavController.OnDestinationChangedListener listener);
     method public androidx.navigation.NavDeepLinkBuilder createDeepLink();
     method public androidx.navigation.NavBackStackEntry getBackStackEntry(@IdRes int destinationId);
+    method public final androidx.navigation.NavBackStackEntry getBackStackEntry(String route);
     method public androidx.navigation.NavBackStackEntry? getCurrentBackStackEntry();
     method public final kotlinx.coroutines.flow.Flow<androidx.navigation.NavBackStackEntry> getCurrentBackStackEntryFlow();
     method public androidx.navigation.NavDestination? getCurrentDestination();
@@ -113,10 +114,16 @@
     method @MainThread public void navigate(androidx.navigation.NavDirections directions);
     method @MainThread public void navigate(androidx.navigation.NavDirections directions, androidx.navigation.NavOptions? navOptions);
     method @MainThread public void navigate(androidx.navigation.NavDirections directions, androidx.navigation.Navigator.Extras navigatorExtras);
+    method public final void navigate(String route, kotlin.jvm.functions.Function1<? super androidx.navigation.NavOptionsBuilder,kotlin.Unit> builder);
+    method public final void navigate(String route, optional androidx.navigation.NavOptions? navOptions, optional androidx.navigation.Navigator.Extras? navigatorExtras);
+    method public final void navigate(String route, optional androidx.navigation.NavOptions? navOptions);
+    method public final void navigate(String route);
     method @MainThread public boolean navigateUp();
     method @MainThread public boolean popBackStack();
     method @MainThread public boolean popBackStack(@IdRes int destinationId, boolean inclusive);
     method @MainThread public boolean popBackStack(@IdRes int destinationId, boolean inclusive, boolean saveState);
+    method @MainThread public final boolean popBackStack(String route, boolean inclusive, optional boolean saveState);
+    method @MainThread public final boolean popBackStack(String route, boolean inclusive);
     method public void removeOnDestinationChangedListener(androidx.navigation.NavController.OnDestinationChangedListener listener);
     method @CallSuper public void restoreState(android.os.Bundle? navState);
     method @CallSuper public android.os.Bundle? saveState();
@@ -144,12 +151,15 @@
 
   public final class NavControllerKt {
     method public static inline androidx.navigation.NavGraph createGraph(androidx.navigation.NavController, optional @IdRes int id, @IdRes int startDestination, kotlin.jvm.functions.Function1<? super androidx.navigation.NavGraphBuilder,kotlin.Unit> builder);
+    method public static androidx.navigation.NavGraph createGraph(androidx.navigation.NavController, String startDestination, optional String? route, kotlin.jvm.functions.Function1<? super androidx.navigation.NavGraphBuilder,kotlin.Unit> builder);
   }
 
   public final class NavDeepLinkBuilder {
     ctor public NavDeepLinkBuilder(android.content.Context context);
     method public androidx.navigation.NavDeepLinkBuilder addDestination(@IdRes int destId, optional android.os.Bundle? args);
     method public androidx.navigation.NavDeepLinkBuilder addDestination(@IdRes int destId);
+    method public androidx.navigation.NavDeepLinkBuilder addDestination(String route, optional android.os.Bundle? args);
+    method public androidx.navigation.NavDeepLinkBuilder addDestination(String route);
     method public android.app.PendingIntent createPendingIntent();
     method public androidx.core.app.TaskStackBuilder createTaskStackBuilder();
     method public androidx.navigation.NavDeepLinkBuilder setArguments(android.os.Bundle? args);
@@ -157,6 +167,8 @@
     method public androidx.navigation.NavDeepLinkBuilder setComponentName(android.content.ComponentName componentName);
     method public androidx.navigation.NavDeepLinkBuilder setDestination(@IdRes int destId, optional android.os.Bundle? args);
     method public androidx.navigation.NavDeepLinkBuilder setDestination(@IdRes int destId);
+    method public androidx.navigation.NavDeepLinkBuilder setDestination(String destRoute, optional android.os.Bundle? args);
+    method public androidx.navigation.NavDeepLinkBuilder setDestination(String destRoute);
     method public androidx.navigation.NavDeepLinkBuilder setGraph(@NavigationRes int navGraphId);
     method public androidx.navigation.NavDeepLinkBuilder setGraph(androidx.navigation.NavGraph navGraph);
   }
diff --git a/navigation/navigation-runtime/lint-baseline.xml b/navigation/navigation-runtime/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/navigation/navigation-runtime/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/navigation/navigation-runtime/src/androidTest/java/androidx/navigation/NavControllerRouteTest.kt b/navigation/navigation-runtime/src/androidTest/java/androidx/navigation/NavControllerRouteTest.kt
new file mode 100644
index 0000000..654ffd9
--- /dev/null
+++ b/navigation/navigation-runtime/src/androidTest/java/androidx/navigation/NavControllerRouteTest.kt
@@ -0,0 +1,1338 @@
+/*
+ * 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.navigation
+
+import android.content.Context
+import android.content.Intent
+import android.net.Uri
+import android.os.Bundle
+import android.os.Parcel
+import androidx.activity.OnBackPressedDispatcher
+import androidx.activity.addCallback
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.ViewModelStore
+import androidx.lifecycle.testing.TestLifecycleOwner
+import androidx.navigation.NavDestination.Companion.createRoute
+import androidx.navigation.test.R
+import androidx.test.annotation.UiThreadTest
+import androidx.test.core.app.ActivityScenario
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.espresso.intent.Intents
+import androidx.test.espresso.intent.Intents.intended
+import androidx.test.espresso.intent.matcher.BundleMatchers
+import androidx.test.espresso.intent.matcher.IntentMatchers.hasAction
+import androidx.test.espresso.intent.matcher.IntentMatchers.hasComponent
+import androidx.test.espresso.intent.matcher.IntentMatchers.hasData
+import androidx.test.espresso.intent.matcher.IntentMatchers.hasExtra
+import androidx.test.espresso.intent.matcher.IntentMatchers.toPackage
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.ext.truth.os.BundleSubject.assertThat
+import androidx.test.filters.LargeTest
+import androidx.test.filters.MediumTest
+import androidx.test.filters.SdkSuppress
+import androidx.testutils.TestNavigator
+import androidx.testutils.test
+import com.google.common.truth.Truth.assertThat
+import com.google.common.truth.Truth.assertWithMessage
+import org.hamcrest.CoreMatchers.allOf
+import org.hamcrest.CoreMatchers.not
+import org.hamcrest.Matchers
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyString
+
+@MediumTest
+@RunWith(AndroidJUnit4::class)
+class NavControllerRouteTest {
+
+    val nav_simple_route_graph =
+        createNavController().createGraph(route = "nav_root", startDestination = "start_test") {
+            test("start_test")
+            test("start_test_with_default_arg") {
+                argument("defaultArg") { defaultValue = true }
+            }
+            test("second_test") {
+                argument("arg2") { type = NavType.StringType }
+                argument("defaultArg") {
+                    type = NavType.StringType
+                    defaultValue = "defaultValue"
+                }
+                deepLink {
+                    uriPattern = "android-app://androidx.navigation.test/test"
+                    action = "test.action"
+                    mimeType = "type/test"
+                }
+                deepLink {
+                    uriPattern = "android-app://androidx.navigation.test/test/{arg1}/{arg2}"
+                }
+            }
+        }
+
+    val nav_start_destination_route_graph =
+        createNavController().createGraph(route = "graph", startDestination = "start_test") {
+            test("start_test") {
+                argument("test") {
+                    type = NavType.StringType
+                    defaultValue = "@null"
+                }
+            }
+        }
+
+    val nav_nested_start_destination_route_graph =
+        createNavController().createGraph(route = "graph", startDestination = "nested") {
+            navigation(route = "nested", startDestination = "nested_test") {
+                test("nested_test")
+                test("nested_second_test")
+            }
+            test("second_test")
+        }
+
+    val nav_deeplink_route_graph =
+        createNavController().createGraph(route = "nav_root", startDestination = "first_test") {
+            test("first_test") {
+                deepLink {
+                    uriPattern = "android-app://androidx.navigation.test/test"
+                    action = "test.action"
+                    mimeType = "*/*"
+                }
+            }
+            test("second_test") {
+                deepLink {
+                    uriPattern = "android-app://androidx.navigation.test/test"
+                    action = "test.action"
+                    mimeType = "image/*"
+                }
+            }
+            test("third_test") {
+                deepLink {
+                    uriPattern = "android-app://androidx.navigation.test/test"
+                    action = "test.action"
+                    mimeType = "*/test"
+                }
+            }
+            test("forth_test") {
+                deepLink {
+                    uriPattern = "android-app://androidx.navigation.test/test"
+                    action = "test.action"
+                    mimeType = "type/test"
+                }
+            }
+        }
+
+    val nav_multiple_navigation_route_graph =
+        createNavController().createGraph(
+            route = "nav_multi_module_base", startDestination = "simple_child_start"
+        ) {
+            navigation(route = "simple_child_start", startDestination = "simple_child_start_test") {
+                test("simple_child_start_test")
+                test("simple_child_second_test")
+            }
+            navigation(
+                route = "deep_link_child_start", startDestination = "deep_link_child_start_test"
+            ) {
+                test("deep_link_child_start_test")
+                test("deep_link_child_second_test") {
+                    deepLink { uriPattern = "android-app://androidx.navigation.test/test" }
+                }
+                navigation(
+                    route = "deep_link_child_second",
+                    startDestination = "deep_link_grandchild_start_test"
+                ) {
+                    test("deep_link_grandchild_start_test") {
+                        deepLink {
+                            uriPattern = "android-app://androidx.navigation.test/grand_child_test"
+                        }
+                    }
+                    test("deep_link_child_second_test") {
+                        deepLink { uriPattern = "android-app://androidx.navigation.test/test" }
+                    }
+                }
+            }
+        }
+
+    companion object {
+        private const val UNKNOWN_DESTINATION_ID = -1
+        private const val TEST_ARG = "test"
+        private const val TEST_ARG_VALUE = "value"
+        private const val TEST_OVERRIDDEN_VALUE_ARG = "test_overridden_value"
+        private const val TEST_OVERRIDDEN_VALUE_ARG_VALUE = "override"
+    }
+
+    @UiThreadTest
+    @Test
+    fun testGetCurrentBackStackEntry() {
+        val navController = createNavController()
+        navController.graph = nav_start_destination_route_graph
+        assertThat(navController.currentBackStackEntry?.destination?.route).isEqualTo("start_test")
+    }
+
+    @UiThreadTest
+    @Test
+    fun testGetPreviousBackStackEntry() {
+        val navController = createNavController()
+        navController.graph = nav_simple_route_graph
+        navController.navigate("second_test")
+        assertThat(navController.previousBackStackEntry?.destination?.route).isEqualTo("start_test")
+    }
+
+    @UiThreadTest
+    @Test
+    fun testStartDestination() {
+        val navController = createNavController()
+        navController.graph = nav_start_destination_route_graph
+        assertThat(navController.currentDestination?.route).isEqualTo("start_test")
+    }
+
+    @UiThreadTest
+    @Test
+    fun testSetGraphTwice() {
+        val navController = createNavController()
+        navController.graph = nav_start_destination_route_graph
+        val navigator = navController.navigatorProvider[TestNavigator::class]
+        assertThat(navController.currentDestination?.route)
+            .isEqualTo("start_test")
+        assertThat(navigator.backStack.size)
+            .isEqualTo(1)
+
+        // Now set a new graph, overriding the first
+        navController.graph = nav_nested_start_destination_route_graph
+        assertThat(navController.currentDestination?.route)
+            .isEqualTo("nested_test")
+        assertThat(navigator.backStack.size)
+            .isEqualTo(1)
+    }
+
+    @UiThreadTest
+    @Test
+    fun testStartDestinationWithArgs() {
+        val navController = createNavController()
+        val args = Bundle().apply {
+            putString(TEST_ARG, TEST_ARG_VALUE)
+        }
+        navController.setGraph(nav_simple_route_graph, args)
+        val navigator = navController.navigatorProvider[TestNavigator::class]
+        assertThat(navController.currentDestination?.route).isEqualTo("start_test")
+        assertThat(navigator.backStack.size).isEqualTo(1)
+        val foundArgs = navigator.current.arguments
+        assertThat(foundArgs).isNotNull()
+        assertThat(foundArgs?.getString(TEST_ARG)).isEqualTo(TEST_ARG_VALUE)
+    }
+
+    @UiThreadTest
+    @Test
+    fun testStartDestinationWithArgsProgrammatic() {
+        val navController = createNavController()
+        val args = Bundle().apply {
+            putString(TEST_ARG, TEST_ARG_VALUE)
+        }
+
+        val navGraph = navController.navigatorProvider.navigation(
+            route = "graph", startDestination = "start"
+        ) {
+            test("start")
+        }
+        navController.setGraph(navGraph, args)
+        val navigator = navController.navigatorProvider[TestNavigator::class]
+        assertThat(navController.currentDestination?.route).isEqualTo("start")
+        assertThat(navigator.backStack.size).isEqualTo(1)
+        val foundArgs = navigator.current.arguments
+        assertThat(foundArgs).isNotNull()
+        assertThat(foundArgs?.getString(TEST_ARG)).isEqualTo(TEST_ARG_VALUE)
+    }
+
+    @UiThreadTest
+    @Test
+    fun testNestedStartDestination() {
+        val navController = createNavController()
+        navController.graph = nav_nested_start_destination_route_graph
+        assertThat(navController.currentDestination?.route).isEqualTo("nested_test")
+    }
+
+    @UiThreadTest
+    @Test
+    fun testSetGraph() {
+        val navController = createNavController()
+
+        navController.graph = nav_start_destination_route_graph
+        assertThat(navController.graph).isNotNull()
+        assertThat(navController.currentDestination?.route).isEqualTo("start_test")
+    }
+
+    @UiThreadTest
+    @Test
+    fun testSetViewModelStoreOwnerAfterGraphSet() {
+        val navController = createNavController()
+        navController.setViewModelStore(ViewModelStore())
+        val navGraph = navController.navigatorProvider.navigation(
+            route = "graph", startDestination = "start"
+        ) {
+            test("start")
+        }
+        navController.setGraph(navGraph, null)
+
+        try {
+            navController.setViewModelStore(ViewModelStore())
+        } catch (e: IllegalStateException) {
+            assertThat(e).hasMessageThat().contains(
+                "ViewModelStore should be set before setGraph call"
+            )
+        }
+    }
+
+    @UiThreadTest
+    @Test
+    fun testSetSameViewModelStoreOwnerAfterGraphSet() {
+        val navController = createNavController()
+        val viewModelStore = ViewModelStore()
+        navController.setViewModelStore(viewModelStore)
+        val navGraph = navController.navigatorProvider.navigation(
+            route = "graph",
+            startDestination = "start"
+        ) {
+            test("start")
+        }
+        navController.setGraph(navGraph, null)
+
+        navController.setViewModelStore(viewModelStore)
+    }
+
+    @UiThreadTest
+    @Test
+    fun testNavigate() {
+        val navController = createNavController()
+        navController.graph = nav_simple_route_graph
+        val navigator = navController.navigatorProvider.getNavigator(TestNavigator::class.java)
+        assertThat(navController.currentDestination?.route).isEqualTo("start_test")
+        assertThat(navigator.backStack.size).isEqualTo(1)
+
+        navController.navigate("second_test")
+        assertThat(navController.currentDestination?.route).isEqualTo("second_test")
+        assertThat(navigator.backStack.size).isEqualTo(2)
+    }
+
+    @UiThreadTest
+    @Test
+    fun testNavigateViaDeepLink() {
+        val navController = createNavController()
+        navController.graph = nav_simple_route_graph
+        val navigator = navController.navigatorProvider.getNavigator(TestNavigator::class.java)
+        val deepLink = Uri.parse("android-app://androidx.navigation.test/test")
+
+        navController.navigate(deepLink)
+        assertThat(navController.currentDestination?.route).isEqualTo("second_test")
+        assertThat(navigator.backStack.size).isEqualTo(2)
+        val intent = navigator.current.arguments?.getParcelable<Intent>(
+            NavController.KEY_DEEP_LINK_INTENT
+        )
+        assertThat(intent?.data).isEqualTo(deepLink)
+    }
+
+    @UiThreadTest
+    @Test
+    fun testNavigateViaDeepLinkDefaultArgs() {
+        val navController = createNavController()
+        navController.graph = nav_simple_route_graph
+        val navigator = navController.navigatorProvider.getNavigator(TestNavigator::class.java)
+        val deepLink = Uri.parse("android-app://androidx.navigation.test/test")
+
+        navController.navigate(deepLink)
+
+        val destination = navController.currentDestination
+        assertThat(destination?.route).isEqualTo("second_test")
+        assertThat(navigator.backStack.size).isEqualTo(2)
+        assertThat(destination?.arguments?.get("defaultArg")?.defaultValue.toString())
+            .isEqualTo("defaultValue")
+    }
+
+    @UiThreadTest
+    @Test
+    fun testNavigateViaDeepLinkActionDifferentURI() {
+        val navController = createNavController()
+        navController.graph = nav_simple_route_graph
+        val navigator = navController.navigatorProvider.getNavigator(TestNavigator::class.java)
+        val deepLink = NavDeepLinkRequest(Uri.parse("invalidDeepLink.com"), "test.action", null)
+
+        navController.navigate(deepLink)
+        assertThat(navController.currentDestination?.route).isEqualTo("second_test")
+        assertThat(navigator.backStack.size).isEqualTo(2)
+    }
+
+    @UiThreadTest
+    @Test
+    fun testNavigateViaDeepLinkMimeTypeDifferentUri() {
+        val navController = createNavController()
+        navController.graph = nav_simple_route_graph
+        val navigator = navController.navigatorProvider.getNavigator(TestNavigator::class.java)
+        val deepLink = NavDeepLinkRequest(Uri.parse("invalidDeepLink.com"), null, "type/test")
+
+        navController.navigate(deepLink)
+        assertThat(navController.currentDestination?.route).isEqualTo("second_test")
+        assertThat(navigator.backStack.size).isEqualTo(2)
+    }
+
+    @UiThreadTest
+    @Test
+    fun testNavigateViaDeepLinkMimeType() {
+        val navController = createNavController()
+        navController.graph = nav_deeplink_route_graph
+        val navigator = navController.navigatorProvider.getNavigator(TestNavigator::class.java)
+        val mimeType = "type/test"
+        val deepLink = NavDeepLinkRequest(null, null, mimeType)
+
+        navController.navigate(deepLink)
+        assertThat(navController.currentDestination?.route).isEqualTo("forth_test")
+        assertThat(navigator.backStack.size).isEqualTo(2)
+        val intent = navigator.current.arguments?.getParcelable<Intent>(
+            NavController.KEY_DEEP_LINK_INTENT
+        )
+        assertThat(intent?.type).isEqualTo(mimeType)
+    }
+
+    @UiThreadTest
+    @Test
+    fun testNavigateViaDeepLinkMimeTypeWildCard() {
+        val navController = createNavController()
+        navController.graph = nav_deeplink_route_graph
+        val navigator = navController.navigatorProvider.getNavigator(TestNavigator::class.java)
+        val deepLink = NavDeepLinkRequest(null, null, "any/thing")
+
+        navController.navigate(deepLink)
+        assertThat(navController.currentDestination?.route).isEqualTo("first_test")
+        assertThat(navigator.backStack.size).isEqualTo(2)
+    }
+
+    @UiThreadTest
+    @Test
+    fun testNavigateViaDeepLinkMimeTypeWildCardSubtype() {
+        val navController = createNavController()
+        navController.graph = nav_deeplink_route_graph
+        val navigator = navController.navigatorProvider.getNavigator(TestNavigator::class.java)
+        val deepLink = NavDeepLinkRequest(null, null, "image/jpg")
+
+        navController.navigate(deepLink)
+        assertThat(navController.currentDestination?.route).isEqualTo("second_test")
+        assertThat(navigator.backStack.size).isEqualTo(2)
+    }
+
+    @UiThreadTest
+    @Test
+    fun testNavigateViaDeepLinkMimeTypeWildCardType() {
+        val navController = createNavController()
+        navController.graph = nav_deeplink_route_graph
+        val navigator = navController.navigatorProvider.getNavigator(TestNavigator::class.java)
+        val deepLink = NavDeepLinkRequest(null, null, "doesNotEvenMatter/test")
+
+        navController.navigate(deepLink)
+        assertThat(navController.currentDestination?.route).isEqualTo("third_test")
+        assertThat(navigator.backStack.size).isEqualTo(2)
+    }
+
+    @UiThreadTest
+    @Test
+    fun testNavigationViaDeepLinkPopUpTo() {
+        val navController = createNavController()
+        navController.graph = nav_simple_route_graph
+        val navigator = navController.navigatorProvider.getNavigator(TestNavigator::class.java)
+        val deepLink = Uri.parse("android-app://androidx.navigation.test/test")
+
+        navController.navigate(
+            deepLink,
+            navOptions {
+                popUpTo("nav_root") { inclusive = true }
+            }
+        )
+        assertThat(navController.currentDestination?.route).isEqualTo("second_test")
+        assertThat(navigator.backStack.size).isEqualTo(1)
+    }
+
+    @UiThreadTest
+    @Test
+    fun testNavigateToDifferentGraphViaDeepLink() {
+        val navController = createNavController()
+        navController.graph = nav_multiple_navigation_route_graph
+        val navigator = navController.navigatorProvider.getNavigator(TestNavigator::class.java)
+        assertThat(navController.currentDestination?.route).isEqualTo("simple_child_start_test")
+        assertThat(navigator.backStack.size).isEqualTo(1)
+
+        val deepLink = Uri.parse("android-app://androidx.navigation.test/test")
+
+        navController.navigate(deepLink)
+        assertThat(navController.currentDestination?.route).isEqualTo("deep_link_child_second_test")
+        assertThat(navigator.backStack.size).isEqualTo(2)
+
+        val popped = navController.popBackStack()
+        assertWithMessage("NavController should return true when popping a non-root destination")
+            .that(popped)
+            .isTrue()
+        assertThat(navController.currentDestination?.route).isEqualTo("simple_child_start_test")
+        assertThat(navigator.backStack.size).isEqualTo(1)
+    }
+
+    @UiThreadTest
+    @Test
+    fun testNavigateToDifferentGraphViaDeepLink3x() {
+        val navController = createNavController()
+        navController.graph = nav_multiple_navigation_route_graph
+        val navigator = navController.navigatorProvider.getNavigator(TestNavigator::class.java)
+        assertThat(navController.currentDestination?.route).isEqualTo("simple_child_start_test")
+        assertThat(navigator.backStack.size).isEqualTo(1)
+
+        val deepLink = Uri.parse("android-app://androidx.navigation.test/test")
+
+        navController.navigate(deepLink)
+        assertThat(navController.currentDestination?.route).isEqualTo("deep_link_child_second_test")
+        assertThat(navigator.backStack.size).isEqualTo(2)
+
+        navController.popBackStack()
+        assertThat(navController.currentDestination?.route).isEqualTo("simple_child_start_test")
+        assertThat(navigator.backStack.size).isEqualTo(1)
+
+        // repeat nav and pop 2 more times.
+        navController.navigate(deepLink)
+        navController.popBackStack()
+        navController.navigate(deepLink)
+
+        val popped = navController.popBackStack()
+        assertWithMessage("NavController should return true when popping a non-root destination")
+            .that(popped)
+            .isTrue()
+        assertThat(navController.currentDestination?.route).isEqualTo("simple_child_start_test")
+        assertThat(navigator.backStack.size).isEqualTo(1)
+    }
+
+    @UiThreadTest
+    @Test
+    fun testNavigateToDifferentGraphViaDeepLinkToGrandchild3x() {
+        val navController = createNavController()
+        navController.graph = nav_multiple_navigation_route_graph
+        val navigator = navController.navigatorProvider.getNavigator(TestNavigator::class.java)
+        assertThat(navController.currentDestination?.route).isEqualTo("simple_child_start_test")
+        assertThat(navigator.backStack.size).isEqualTo(1)
+
+        val deepLink = Uri.parse("android-app://androidx.navigation.test/grand_child_test")
+
+        navController.navigate(deepLink)
+        assertThat(navController.currentDestination?.route)
+            .isEqualTo("deep_link_grandchild_start_test")
+        assertThat(navigator.backStack.size).isEqualTo(2)
+
+        navController.popBackStack()
+        assertThat(navController.currentDestination?.route).isEqualTo("simple_child_start_test")
+        assertThat(navigator.backStack.size).isEqualTo(1)
+
+        // repeat nav and pop 2 more times.
+        navController.navigate(deepLink)
+        navController.popBackStack()
+        navController.navigate(deepLink)
+
+        val popped = navController.popBackStack()
+        assertWithMessage("NavController should return true when popping a non-root destination")
+            .that(popped)
+            .isTrue()
+        assertThat(navController.currentDestination?.route).isEqualTo("simple_child_start_test")
+        assertThat(navigator.backStack.size).isEqualTo(1)
+    }
+
+    @LargeTest
+    @Test
+    @SdkSuppress(minSdkVersion = 17)
+    fun testNavigateViaImplicitDeepLink() {
+        val intent = Intent(
+            Intent.ACTION_VIEW,
+            Uri.parse("android-app://androidx.navigation.test/test/argument1/argument2"),
+            ApplicationProvider.getApplicationContext() as Context,
+            TestActivity::class.java
+        )
+
+        Intents.init()
+
+        with(ActivityScenario.launch<TestActivity>(intent)) {
+            moveToState(Lifecycle.State.CREATED)
+            onActivity { activity ->
+                run {
+                    val navController = activity.navController
+                    navController.graph = nav_simple_route_graph
+
+                    val navigator =
+                        navController.navigatorProvider.getNavigator(TestNavigator::class.java)
+
+                    assertThat(navController.currentDestination?.route).isEqualTo("second_test")
+
+                    // Only the leaf destination should be on the stack.
+                    assertThat(navigator.backStack.size).isEqualTo(1)
+                    // The parent will be constructed in a new Activity after navigateUp()
+                    navController.navigateUp()
+                }
+            }
+
+            assertThat(this.state).isEqualTo(Lifecycle.State.DESTROYED)
+        }
+
+        // this relies on MonitoringInstrumentation.execStartActivity() which was added in API 17
+        intended(
+            allOf(
+                toPackage((ApplicationProvider.getApplicationContext() as Context).packageName),
+                not(hasData(anyString())), // The rethrow should not use the URI as primary target.
+                hasExtra(
+                    NavController.KEY_DEEP_LINK_IDS, intArrayOf(createRoute("nav_root").hashCode())
+                ),
+                hasExtra(
+                    Matchers.`is`(NavController.KEY_DEEP_LINK_EXTRAS),
+                    allOf(
+                        BundleMatchers.hasEntry("arg1", "argument1"),
+                        BundleMatchers.hasEntry("arg2", "argument2"),
+                        BundleMatchers.hasEntry(
+                            NavController.KEY_DEEP_LINK_INTENT,
+                            allOf(
+                                hasAction(intent.action),
+                                hasData(intent.data),
+                                hasComponent(intent.component)
+                            )
+                        )
+                    )
+                )
+            )
+        )
+
+        Intents.release()
+    }
+
+    @UiThreadTest
+    @Test
+    fun testSaveRestoreStateXml() {
+        val context = ApplicationProvider.getApplicationContext() as Context
+        var navController = NavController(context)
+        var navigator = SaveStateTestNavigator()
+        navController.navigatorProvider.addNavigator(navigator)
+        navController.graph = nav_simple_route_graph
+        navController.navigate("second_test")
+
+        val savedState = navController.saveState()
+        navController = NavController(context)
+        navigator = SaveStateTestNavigator()
+        navController.navigatorProvider.addNavigator(navigator)
+
+        // Restore state doesn't recreate any graph
+        navController.restoreState(savedState)
+        assertThat(navController.currentDestination).isNull()
+
+        // Explicitly setting a graph then restores the state
+        navController.graph = nav_simple_route_graph
+        assertThat(navController.currentDestination?.route).isEqualTo("second_test")
+        assertThat(navigator.backStack.size).isEqualTo(2)
+        // Save state should be called on the navigator exactly once
+        assertThat(navigator.saveStateCount).isEqualTo(1)
+    }
+
+    @UiThreadTest
+    @Test
+    fun testSaveRestoreStateDestinationChanged() {
+        val context = ApplicationProvider.getApplicationContext() as Context
+        var navController = NavController(context)
+        var navigator = SaveStateTestNavigator()
+        navController.navigatorProvider.addNavigator(navigator)
+
+        navController.graph = nav_simple_route_graph
+
+        val savedState = navController.saveState()
+        navController = NavController(context)
+        navigator = SaveStateTestNavigator()
+        navController.navigatorProvider.addNavigator(navigator)
+
+        // Restore state doesn't recreate any graph
+        navController.restoreState(savedState)
+        assertThat(navController.currentDestination).isNull()
+
+        var destinationChangedCount = 0
+
+        navController.addOnDestinationChangedListener { _, _, _ ->
+            destinationChangedCount++
+        }
+
+        // Explicitly setting a graph then restores the state
+        navController.graph = nav_simple_route_graph
+        // Save state should be called on the navigator exactly once
+        assertThat(navigator.saveStateCount).isEqualTo(1)
+        // listener should have been fired again when state restored
+        assertThat(destinationChangedCount).isEqualTo(1)
+    }
+
+    @UiThreadTest
+    @Test
+    fun testSaveRestoreStateProgrammatic() {
+        val context = ApplicationProvider.getApplicationContext() as Context
+        var navController = NavController(context)
+        var navigator = TestNavigator()
+        navController.navigatorProvider.addNavigator(navigator)
+        navController.graph = nav_simple_route_graph
+        navController.navigate("second_test")
+
+        val savedState = navController.saveState()
+        navController = NavController(context)
+        navigator = TestNavigator()
+        navController.navigatorProvider.addNavigator(navigator)
+
+        // Restore state doesn't recreate any graph
+        navController.restoreState(savedState)
+        assertThat(navController.currentDestination).isNull()
+
+        // Explicitly setting a graph then restores the state
+        navController.graph = nav_simple_route_graph
+        assertThat(navController.currentDestination?.route).isEqualTo("second_test")
+        assertThat(navigator.backStack.size).isEqualTo(2)
+    }
+
+    @UiThreadTest
+    @Test
+    fun testSaveRestoreStateBundleParceled() {
+        val context = ApplicationProvider.getApplicationContext() as Context
+        var navController = NavController(context)
+        var navigator = SaveStateTestNavigator()
+        navController.navigatorProvider.addNavigator(navigator)
+        navController.graph = nav_simple_route_graph
+
+        navigator.customParcel = CustomTestParcelable(TEST_ARG_VALUE)
+
+        val savedState = navController.saveState()
+
+        val parcel = Parcel.obtain()
+        savedState?.writeToParcel(parcel, 0)
+        parcel.setDataPosition(0)
+
+        val restoredState = Bundle.CREATOR.createFromParcel(parcel)
+
+        navController = NavController(context)
+        navigator = SaveStateTestNavigator()
+        navController.navigatorProvider.addNavigator(navigator)
+
+        navController.restoreState(restoredState)
+        navController.graph = nav_simple_route_graph
+
+        // Ensure custom parcelable is present and can be read
+        assertThat(navigator.customParcel?.name).isEqualTo(TEST_ARG_VALUE)
+    }
+
+    @UiThreadTest
+    @Test
+    fun testSaveRestoreAfterNavigateToDifferentNavGraph() {
+        val context = ApplicationProvider.getApplicationContext() as Context
+        var navController = NavController(context)
+        var navigator = SaveStateTestNavigator()
+        navController.navigatorProvider.addNavigator(navigator)
+        navController.graph = nav_multiple_navigation_route_graph
+        assertThat(navController.currentDestination?.route)
+            .isEqualTo("simple_child_start_test")
+        assertThat(navigator.backStack.size).isEqualTo(1)
+
+        val deepLink = Uri.parse("android-app://androidx.navigation.test/test")
+
+        navController.navigate(deepLink)
+        assertThat(navController.currentDestination?.route)
+            .isEqualTo("deep_link_child_second_test")
+        assertThat(navigator.backStack.size).isEqualTo(2)
+
+        navController.navigate("simple_child_start")
+        assertThat(navController.currentDestination?.route)
+            .isEqualTo("simple_child_start_test")
+        assertThat(navigator.backStack.size).isEqualTo(3)
+
+        val savedState = navController.saveState()
+        navController = NavController(context)
+        navigator = SaveStateTestNavigator()
+        navController.navigatorProvider.addNavigator(navigator)
+
+        // Restore state doesn't recreate any graph
+        navController.restoreState(savedState)
+        assertThat(navController.currentDestination).isNull()
+
+        // Explicitly setting a graph then restores the state
+        navController.graph = nav_multiple_navigation_route_graph
+        assertThat(navController.currentDestination?.route)
+            .isEqualTo("simple_child_start_test")
+        assertThat(navigator.backStack.size).isEqualTo(3)
+        // Save state should be called on the navigator exactly once
+        assertThat(navigator.saveStateCount).isEqualTo(1)
+    }
+
+    @UiThreadTest
+    @Test
+    fun testBackstackArgsBundleParceled() {
+        val context = ApplicationProvider.getApplicationContext() as Context
+        var navController = NavController(context)
+        var navigator = SaveStateTestNavigator()
+        navController.navigatorProvider.addNavigator(navigator)
+
+        val backStackArg1 = Bundle()
+        backStackArg1.putParcelable(TEST_ARG, CustomTestParcelable(TEST_ARG_VALUE))
+        navController.setGraph(R.navigation.nav_arguments)
+        navController.navigate(R.id.second_test, backStackArg1)
+
+        val savedState = navController.saveState()
+
+        val parcel = Parcel.obtain()
+        savedState?.writeToParcel(parcel, 0)
+        parcel.setDataPosition(0)
+
+        val restoredState = Bundle.CREATOR.createFromParcel(parcel)
+
+        navController = NavController(context)
+        navigator = SaveStateTestNavigator()
+        navController.navigatorProvider.addNavigator(navigator)
+
+        navController.restoreState(restoredState)
+        navController.setGraph(R.navigation.nav_arguments)
+
+        navController.addOnDestinationChangedListener { _, _, arguments ->
+            assertThat(arguments?.getParcelable<CustomTestParcelable>(TEST_ARG)?.name)
+                .isEqualTo(TEST_ARG_VALUE)
+        }
+    }
+
+    @UiThreadTest
+    @Test
+    fun testNavigateArgs() {
+        val navController = createNavController()
+        navController.setGraph(R.navigation.nav_arguments)
+
+        val navigator = navController.navigatorProvider.getNavigator(TestNavigator::class.java)
+        val returnedArgs = navigator.current.arguments
+        assertThat(returnedArgs).isNotNull()
+        assertThat(returnedArgs!!["test_start_default"])
+            .isEqualTo("default")
+
+        navController.addOnDestinationChangedListener { _, _, arguments ->
+            assertThat(arguments).isNotNull()
+            assertThat(arguments!!["test_start_default"])
+                .isEqualTo("default")
+        }
+    }
+
+    @UiThreadTest
+    @Test
+    fun testNavigateWithNoDefaultValue() {
+        val returnedArgs = navigateWithArgs(null)
+
+        // Test that arguments without a default value aren't passed through at all
+        assertThat(returnedArgs.containsKey("test_no_default_value")).isFalse()
+    }
+
+    @UiThreadTest
+    @Test
+    fun testNavigateWithDefaultArgs() {
+        val returnedArgs = navigateWithArgs(null)
+
+        // Test that default values are passed through
+        assertThat(returnedArgs.getString("test_default_value")).isEqualTo("default")
+    }
+
+    @UiThreadTest
+    @Test
+    fun testNavigateWithArgs() {
+        val args = Bundle()
+        args.putString(TEST_ARG, TEST_ARG_VALUE)
+        val returnedArgs = navigateWithArgs(args)
+
+        // Test that programmatically constructed arguments are passed through
+        assertThat(returnedArgs.getString(TEST_ARG)).isEqualTo(TEST_ARG_VALUE)
+    }
+
+    @UiThreadTest
+    @Test
+    fun testNavigateWithOverriddenDefaultArgs() {
+        val args = Bundle()
+        args.putString(TEST_OVERRIDDEN_VALUE_ARG, TEST_OVERRIDDEN_VALUE_ARG_VALUE)
+        val returnedArgs = navigateWithArgs(args)
+
+        // Test that default values can be overridden by programmatic values
+        assertThat(returnedArgs.getString(TEST_OVERRIDDEN_VALUE_ARG))
+            .isEqualTo(TEST_OVERRIDDEN_VALUE_ARG_VALUE)
+    }
+
+    private fun navigateWithArgs(args: Bundle?): Bundle {
+        val navController = createNavController()
+        navController.setGraph(R.navigation.nav_arguments)
+
+        navController.navigate(R.id.second_test, args)
+
+        val navigator = navController.navigatorProvider.getNavigator(TestNavigator::class.java)
+        val returnedArgs = navigator.current.arguments
+        assertThat(returnedArgs).isNotNull()
+
+        return returnedArgs!!
+    }
+
+    @UiThreadTest
+    @Test
+    fun testPopRoot() {
+        val navController = createNavController()
+        navController.graph = nav_simple_route_graph
+        val navigator = navController.navigatorProvider.getNavigator(TestNavigator::class.java)
+        assertThat(navController.currentDestination?.route).isEqualTo("start_test")
+        assertThat(navigator.backStack.size).isEqualTo(1)
+
+        val success = navController.popBackStack()
+        assertWithMessage("NavController should return false when popping the root")
+            .that(success)
+            .isFalse()
+        assertThat(navController.currentDestination).isNull()
+        assertThat(navigator.backStack.size).isEqualTo(0)
+    }
+
+    @UiThreadTest
+    @Test
+    fun testPopOnEmptyStack() {
+        val navController = createNavController()
+        navController.graph = nav_simple_route_graph
+        val navigator = navController.navigatorProvider.getNavigator(TestNavigator::class.java)
+        assertThat(navController.currentDestination?.route).isEqualTo("start_test")
+        assertThat(navigator.backStack.size).isEqualTo(1)
+
+        val success = navController.popBackStack()
+        assertWithMessage("NavController should return false when popping the root")
+            .that(success)
+            .isFalse()
+        assertThat(navController.currentDestination).isNull()
+        assertThat(navigator.backStack.size).isEqualTo(0)
+
+        val popped = navController.popBackStack()
+        assertWithMessage(
+            "popBackStack should return false when there's nothing on the " +
+                "back stack"
+        )
+            .that(popped)
+            .isFalse()
+    }
+
+    @UiThreadTest
+    @Test
+    fun testNavigateThenPop() {
+        val navController = createNavController()
+        navController.graph = nav_simple_route_graph
+        val navigator = navController.navigatorProvider.getNavigator(TestNavigator::class.java)
+        assertThat(navController.currentDestination?.route).isEqualTo("start_test")
+        assertThat(navigator.backStack.size).isEqualTo(1)
+
+        navController.navigate("second_test")
+        assertThat(navController.currentDestination?.route).isEqualTo("second_test")
+        assertThat(navigator.backStack.size).isEqualTo(2)
+
+        val popped = navController.popBackStack()
+        assertWithMessage("NavController should return true when popping a non-root destination")
+            .that(popped)
+            .isTrue()
+        assertThat(navController.currentDestination?.route).isEqualTo("start_test")
+        assertThat(navigator.backStack.size).isEqualTo(1)
+    }
+
+    @UiThreadTest
+    @Test
+    fun testNavigateThenPopToUnknownDestination() {
+        val navController = createNavController()
+        navController.graph = nav_simple_route_graph
+        val navigator = navController.navigatorProvider.getNavigator(TestNavigator::class.java)
+        assertThat(navController.currentDestination?.route).isEqualTo("start_test")
+        assertThat(navigator.backStack.size).isEqualTo(1)
+
+        navController.navigate("second_test")
+        assertThat(navController.currentDestination?.route).isEqualTo("second_test")
+        assertThat(navigator.backStack.size).isEqualTo(2)
+
+        val popped = navController.popBackStack(UNKNOWN_DESTINATION_ID, false)
+        assertWithMessage("Popping to an invalid destination should return false")
+            .that(popped)
+            .isFalse()
+        assertThat(navController.currentDestination?.route).isEqualTo("second_test")
+        assertThat(navigator.backStack.size).isEqualTo(2)
+    }
+
+    @UiThreadTest
+    @Test
+    fun testNavigateThenNavigateWithPop() {
+        val navController = createNavController()
+        navController.graph = nav_simple_route_graph
+        val navigator = navController.navigatorProvider.getNavigator(TestNavigator::class.java)
+        assertThat(navController.currentDestination?.route).isEqualTo("start_test")
+        assertThat(navigator.backStack.size).isEqualTo(1)
+
+        navController.navigate("second_test") {
+            popUpTo("start_test") { inclusive = true }
+        }
+        assertThat(navController.currentDestination?.route).isEqualTo("second_test")
+        assertThat(navigator.backStack.size).isEqualTo(1)
+    }
+
+    @UiThreadTest
+    @Test
+    fun testNavigateThenNavigateWithPopRoot() {
+        val navController = createNavController()
+        navController.graph = nav_simple_route_graph
+        val navigator = navController.navigatorProvider.getNavigator(TestNavigator::class.java)
+        assertThat(navController.currentDestination?.route).isEqualTo("start_test")
+        assertThat(navigator.backStack.size).isEqualTo(1)
+
+        navController.navigate("second_test") {
+            popUpTo("nav_root") { inclusive = true }
+        }
+        assertThat(navController.currentDestination?.route).isEqualTo("second_test")
+        assertThat(navigator.backStack.size).isEqualTo(1)
+    }
+
+    @UiThreadTest
+    @Test
+    fun testNavigateThenNavigateUp() {
+        val navController = createNavController()
+        navController.graph = nav_simple_route_graph
+        val navigator = navController.navigatorProvider.getNavigator(TestNavigator::class.java)
+        assertThat(navController.currentDestination?.route).isEqualTo("start_test")
+        assertThat(navigator.backStack.size).isEqualTo(1)
+
+        navController.navigate("second_test")
+        assertThat(navController.currentDestination?.route).isEqualTo("second_test")
+        assertThat(navigator.backStack.size).isEqualTo(2)
+
+        // This should function identically to popBackStack()
+        val success = navController.navigateUp()
+        assertThat(success)
+            .isTrue()
+        assertThat(navController.currentDestination?.route).isEqualTo("start_test")
+        assertThat(navigator.backStack.size).isEqualTo(1)
+    }
+
+    @UiThreadTest
+    @Test
+    fun testNavigateThenNavigateUpWithDefaultArgs() {
+        val navController = createNavController()
+        navController.graph = nav_simple_route_graph
+        val navigator = navController.navigatorProvider.getNavigator(TestNavigator::class.java)
+        assertThat(navController.currentDestination?.route).isEqualTo("start_test")
+        assertThat(navigator.backStack.size).isEqualTo(1)
+
+        navController.navigate("second_test")
+        assertThat(navController.currentDestination?.route).isEqualTo("second_test")
+        assertThat(navigator.backStack.size).isEqualTo(2)
+
+        navController.navigate("start_test_with_default_arg")
+        assertThat(navController.currentDestination?.route).isEqualTo("start_test_with_default_arg")
+        assertThat(navigator.backStack.size).isEqualTo(3)
+
+        // This should function identically to popBackStack()
+        val success = navController.navigateUp()
+        assertThat(success).isTrue()
+        val destination = navController.currentDestination
+        assertThat(destination?.route).isEqualTo("second_test")
+        assertThat(navigator.backStack.size).isEqualTo(2)
+        assertThat(destination?.arguments?.get("defaultArg")?.defaultValue.toString())
+            .isEqualTo("defaultValue")
+    }
+
+    @UiThreadTest
+    @Test
+    fun testDeepLinkFromNavGraph() {
+        val navController = createNavController()
+        navController.graph = nav_simple_route_graph
+
+        val taskStackBuilder = navController.createDeepLink()
+            .setDestination("second_test")
+            .createTaskStackBuilder()
+        assertThat(taskStackBuilder).isNotNull()
+        assertThat(taskStackBuilder.intentCount).isEqualTo(1)
+    }
+
+    @UiThreadTest
+    @Test
+    fun testDeepLinkIntent() {
+        val navController = createNavController()
+        navController.graph = nav_simple_route_graph
+
+        val args = Bundle()
+        args.putString("test", "test")
+        val taskStackBuilder = navController.createDeepLink()
+            .setDestination("second_test")
+            .setArguments(args)
+            .createTaskStackBuilder()
+
+        val intent = taskStackBuilder.editIntentAt(0)
+        assertThat(intent).isNotNull()
+        navController.handleDeepLink(intent)
+
+        // The original Intent should be untouched and safely writable to a Parcel
+        val p = Parcel.obtain()
+        intent!!.writeToParcel(p, 0)
+    }
+
+    @UiThreadTest
+    @Test
+    fun testDeepLinkIntentWithDefaultArgs() {
+        val navController = createNavController()
+        navController.graph = nav_simple_route_graph
+
+        val taskStackBuilder = navController.createDeepLink()
+            .setDestination("second_test")
+            .createTaskStackBuilder()
+
+        val intent = taskStackBuilder.editIntentAt(0)
+        assertThat(intent).isNotNull()
+        navController.handleDeepLink(intent)
+
+        // The original Intent should be untouched and safely writable to a Parcel
+        val p = Parcel.obtain()
+        intent!!.writeToParcel(p, 0)
+
+        val destination = navController.currentDestination
+        assertThat(destination?.route).isEqualTo("second_test")
+        assertThat(destination?.arguments?.get("defaultArg")?.defaultValue.toString())
+            .isEqualTo("defaultValue")
+    }
+
+    @UiThreadTest
+    @Test
+    fun testHandleDeepLinkValid() {
+        val navController = createNavController()
+        navController.graph = nav_simple_route_graph
+        val collectedDestinationIds = mutableListOf<String?>()
+        navController.addOnDestinationChangedListener { _, destination, _ ->
+            collectedDestinationIds.add(destination.route)
+        }
+
+        val taskStackBuilder = navController.createDeepLink()
+            .setDestination("second_test")
+            .createTaskStackBuilder()
+
+        val intent = taskStackBuilder.editIntentAt(0)
+        assertThat(intent).isNotNull()
+        assertWithMessage("NavController should handle deep links to its own graph")
+            .that(navController.handleDeepLink(intent))
+            .isTrue()
+        // Verify that we navigated down to the deep link
+        assertThat(collectedDestinationIds)
+            .containsExactly("start_test", "start_test", "second_test")
+            .inOrder()
+    }
+
+    @UiThreadTest
+    @Test
+    fun testHandleDeepLinkNestedStartDestination() {
+        val navController = createNavController()
+        navController.graph = nav_nested_start_destination_route_graph
+        val collectedDestinationIds = mutableListOf<String?>()
+        navController.addOnDestinationChangedListener { _, destination, _ ->
+            collectedDestinationIds.add(destination.route)
+        }
+
+        val taskStackBuilder = navController.createDeepLink()
+            .setDestination("second_test")
+            .createTaskStackBuilder()
+
+        val intent = taskStackBuilder.editIntentAt(0)
+        assertThat(intent).isNotNull()
+        assertWithMessage("NavController should handle deep links to its own graph")
+            .that(navController.handleDeepLink(intent))
+            .isTrue()
+
+        // Verify that we navigated down to the deep link
+        assertThat(collectedDestinationIds)
+            .containsExactly("nested_test", "nested_test", "second_test")
+            .inOrder()
+    }
+
+    @UiThreadTest
+    @Test
+    fun testHandleDeepLinkMultipleDestinations() {
+        val navController = createNavController()
+        navController.graph = nav_multiple_navigation_route_graph
+        val collectedDestinationRoutes = mutableListOf<String?>()
+        navController.addOnDestinationChangedListener { _, destination, _ ->
+            collectedDestinationRoutes.add(destination.route)
+        }
+
+        val taskStackBuilder = navController.createDeepLink()
+            .setDestination("simple_child_second_test")
+            .addDestination("deep_link_child_second_test")
+            .createTaskStackBuilder()
+
+        val intent = taskStackBuilder.editIntentAt(0)
+        assertThat(intent).isNotNull()
+        assertWithMessage("NavController should handle deep links to its own graph")
+            .that(navController.handleDeepLink(intent))
+            .isTrue()
+
+        // Verify that we navigated down to the deep link
+        assertThat(collectedDestinationRoutes)
+            .containsExactly(
+                // First to the destination added via setDestination()
+                "simple_child_start_test", "simple_child_start_test",
+                "simple_child_second_test",
+                // Then to the second destination added via addDestination()
+                "deep_link_child_start_test", "deep_link_child_second_test"
+            )
+            .inOrder()
+    }
+
+    @UiThreadTest
+    @Test
+    fun testHandleDeepLinkMultipleDestinationsWithArgs() {
+        val navController = createNavController()
+        navController.graph = nav_multiple_navigation_route_graph
+        val collectedDestinations = mutableListOf<Pair<String?, Bundle?>>()
+        navController.addOnDestinationChangedListener { _, destination, arguments ->
+            collectedDestinations.add(destination.route to arguments)
+        }
+
+        val globalBundle = Bundle().apply {
+            putString("global", "global")
+        }
+        val firstBundle = Bundle().apply {
+            putString("test", "first")
+        }
+        val secondBundle = Bundle().apply {
+            putString("global", "overridden")
+            putString("test", "second")
+        }
+        val taskStackBuilder = navController.createDeepLink()
+            .setDestination(createRoute("simple_child_second_test").hashCode(), firstBundle)
+            .addDestination(createRoute("deep_link_child_second_test").hashCode(), secondBundle)
+            .setArguments(globalBundle)
+            .createTaskStackBuilder()
+
+        val intent = taskStackBuilder.editIntentAt(0)
+        assertThat(intent).isNotNull()
+        assertWithMessage("NavController should handle deep links to its own graph")
+            .that(navController.handleDeepLink(intent))
+            .isTrue()
+
+        // Verify that we navigated down to the deep link
+        // First to the destination added via setDestination()
+        val (destinationRoutes, bundle) = collectedDestinations[0]
+        assertThat(destinationRoutes).isEqualTo("simple_child_start_test")
+        assertThat(bundle).isEqualTo(null)
+
+        val (destinationId1, bundle1) = collectedDestinations[1]
+        assertThat(destinationId1).isEqualTo("simple_child_start_test")
+        assertThat(bundle1).string("global").isEqualTo("global")
+        assertThat(bundle1).string("test").isEqualTo("first")
+
+        val (destinationId2, bundle2) = collectedDestinations[2]
+        assertThat(destinationId2).isEqualTo("simple_child_second_test")
+        assertThat(bundle2).string("global").isEqualTo("global")
+        assertThat(bundle2).string("test").isEqualTo("first")
+
+        // Then to the second destination added via addDestination()
+        val (destinationId3, bundle3) = collectedDestinations[3]
+        assertThat(destinationId3).isEqualTo("deep_link_child_start_test")
+        assertThat(bundle3).string("global").isEqualTo("overridden")
+        assertThat(bundle3).string("test").isEqualTo("second")
+
+        val (destinationId4, bundle4) = collectedDestinations[4]
+        assertThat(destinationId4).isEqualTo("deep_link_child_second_test")
+        assertThat(bundle4).string("global").isEqualTo("overridden")
+        assertThat(bundle4).string("test").isEqualTo("second")
+
+        assertWithMessage("$collectedDestinations should have 5 destinations")
+            .that(collectedDestinations).hasSize(5)
+    }
+
+    @UiThreadTest
+    @Test
+    fun testHandleDeepLinkInvalid() {
+        val navController = createNavController()
+        navController.graph = nav_simple_route_graph
+        val collectedDestinationRoutes = mutableListOf<String?>()
+        navController.addOnDestinationChangedListener { _, destination, _ ->
+            collectedDestinationRoutes.add(destination.route)
+        }
+
+        assertThat(collectedDestinationRoutes).containsExactly("start_test")
+
+        val taskStackBuilder = navController.createDeepLink()
+            .setGraph(nav_nested_start_destination_route_graph)
+            .setDestination("nested_second_test")
+            .createTaskStackBuilder()
+
+        val intent = taskStackBuilder.editIntentAt(0)
+        assertThat(intent).isNotNull()
+        assertWithMessage("handleDeepLink should return false when passed an invalid deep link")
+            .that(navController.handleDeepLink(intent))
+            .isFalse()
+
+        assertWithMessage("$collectedDestinationRoutes should have 1 destination id")
+            .that(collectedDestinationRoutes).hasSize(1)
+    }
+
+    @UiThreadTest
+    @Test
+    fun testHandleDeepLinkToRootInvalid() {
+        val navController = createNavController()
+        navController.graph = nav_simple_route_graph
+        val collectedDestinationRoutes = mutableListOf<String?>()
+        navController.addOnDestinationChangedListener { _, destination, _ ->
+            collectedDestinationRoutes.add(destination.route)
+        }
+
+        assertThat(collectedDestinationRoutes).containsExactly("start_test")
+
+        val taskStackBuilder = navController.createDeepLink()
+            .setGraph(nav_nested_start_destination_route_graph)
+            .setDestination("nested_test")
+            .createTaskStackBuilder()
+
+        val intent = taskStackBuilder.editIntentAt(0)
+        assertThat(intent).isNotNull()
+        assertWithMessage("handleDeepLink should return false when passed an invalid deep link")
+            .that(navController.handleDeepLink(intent))
+            .isFalse()
+
+        assertWithMessage("$collectedDestinationRoutes should have 1 destination id")
+            .that(collectedDestinationRoutes).hasSize(1)
+    }
+
+    @UiThreadTest
+    @Test
+    fun testSetOnBackPressedDispatcherOnNavBackStackEntry() {
+        var backPressedIntercepted = false
+        val navController = createNavController()
+        val lifecycleOwner = TestLifecycleOwner()
+        val dispatcher = OnBackPressedDispatcher()
+
+        navController.setLifecycleOwner(lifecycleOwner)
+        navController.setOnBackPressedDispatcher(dispatcher)
+
+        navController.graph = nav_simple_route_graph
+        navController.navigate("second_test")
+        assertThat(navController.previousBackStackEntry?.destination?.route)
+            .isEqualTo("start_test")
+
+        dispatcher.addCallback(navController.currentBackStackEntry!!) {
+            backPressedIntercepted = true
+        }
+
+        // Move to STOPPED
+        lifecycleOwner.currentState = Lifecycle.State.CREATED
+        // Move back up to RESUMED
+        lifecycleOwner.currentState = Lifecycle.State.RESUMED
+
+        dispatcher.onBackPressed()
+
+        assertThat(backPressedIntercepted).isTrue()
+    }
+
+    private fun createNavController(): NavController {
+        val navController = NavController(ApplicationProvider.getApplicationContext())
+        val navigator = TestNavigator()
+        navController.navigatorProvider.addNavigator(navigator)
+        return navController
+    }
+}
diff --git a/navigation/navigation-runtime/src/androidTest/java/androidx/navigation/NavControllerTest.kt b/navigation/navigation-runtime/src/androidTest/java/androidx/navigation/NavControllerTest.kt
index ff2ff9d..5dd486d 100644
--- a/navigation/navigation-runtime/src/androidTest/java/androidx/navigation/NavControllerTest.kt
+++ b/navigation/navigation-runtime/src/androidTest/java/androidx/navigation/NavControllerTest.kt
@@ -1581,7 +1581,7 @@
         assertThat(navigator.backStack.size).isEqualTo(2)
 
         val navOptions = navOptions {
-            popUpTo = R.id.start_test
+            popUpTo(R.id.start_test)
         }
         // the same as to call .navigate(R.id.finish)
         navController.navigate(0, null, navOptions)
diff --git a/navigation/navigation-runtime/src/androidTest/java/androidx/navigation/NavControllerViewModelTest.kt b/navigation/navigation-runtime/src/androidTest/java/androidx/navigation/NavControllerViewModelTest.kt
index ca5ed11..d786d7a 100644
--- a/navigation/navigation-runtime/src/androidTest/java/androidx/navigation/NavControllerViewModelTest.kt
+++ b/navigation/navigation-runtime/src/androidTest/java/androidx/navigation/NavControllerViewModelTest.kt
@@ -30,7 +30,7 @@
 
     @Test
     fun testGetViewModelStore() {
-        val navGraphId = UUID.randomUUID()
+        val navGraphId = UUID.randomUUID().toString()
         val viewModel = NavControllerViewModel()
         val viewModelStore = viewModel.getViewModelStore(navGraphId)
         assertThat(viewModel.getViewModelStore(navGraphId)).isSameInstanceAs(viewModelStore)
@@ -46,7 +46,7 @@
     @Test
     fun testClear() {
         val viewModel = NavControllerViewModel.getInstance(ViewModelStore())
-        val navGraphId = UUID.randomUUID()
+        val navGraphId = UUID.randomUUID().toString()
         val viewModelStore = viewModel.getViewModelStore(navGraphId)
         assertThat(viewModelStore).isNotNull()
 
@@ -58,10 +58,10 @@
     fun testOnCleared() {
         val baseViewModelStore = ViewModelStore()
         val viewModel = NavControllerViewModel.getInstance(baseViewModelStore)
-        val navGraphId = UUID.randomUUID()
+        val navGraphId = UUID.randomUUID().toString()
         val navGraphViewModelStore = viewModel.getViewModelStore(navGraphId)
         // test clearing two viewmodel stores.
-        viewModel.getViewModelStore(UUID.randomUUID())
+        viewModel.getViewModelStore(UUID.randomUUID().toString())
 
         assertThat(navGraphViewModelStore).isNotNull()
 
diff --git a/navigation/navigation-runtime/src/androidTest/java/androidx/navigation/NavDeepLinkBuilderTest.kt b/navigation/navigation-runtime/src/androidTest/java/androidx/navigation/NavDeepLinkBuilderTest.kt
index afdccd1..951a236 100644
--- a/navigation/navigation-runtime/src/androidTest/java/androidx/navigation/NavDeepLinkBuilderTest.kt
+++ b/navigation/navigation-runtime/src/androidTest/java/androidx/navigation/NavDeepLinkBuilderTest.kt
@@ -36,6 +36,29 @@
 
     private val targetContext get() = ApplicationProvider.getApplicationContext() as Context
 
+    val nav_simple_route_graph =
+        createNavController().createGraph(route = "nav_root", startDestination = "start_test") {
+            test("start_test")
+            test("start_test_with_default_arg") {
+                argument("defaultArg") { defaultValue = true }
+            }
+            test("second_test") {
+                argument("arg2") { type = NavType.StringType }
+                argument("defaultArg") {
+                    type = NavType.StringType
+                    defaultValue = "defaultValue"
+                }
+                deepLink {
+                    uriPattern = "android-app://androidx.navigation.test/test"
+                    action = "test.action"
+                    mimeType = "type/test"
+                }
+                deepLink {
+                    uriPattern = "android-app://androidx.navigation.test/test/{arg1}/{arg2}"
+                }
+            }
+        }
+
     @Test
     fun fromContextSetGraphXml() {
         val deepLinkBuilder = NavDeepLinkBuilder(targetContext)
@@ -47,6 +70,16 @@
     }
 
     @Test
+    fun fromContextSetGraphXmlRoute() {
+        val deepLinkBuilder = NavDeepLinkBuilder(targetContext)
+
+        deepLinkBuilder.setGraph(nav_simple_route_graph)
+        deepLinkBuilder.setDestination("second_test")
+        val taskStackBuilder = deepLinkBuilder.createTaskStackBuilder()
+        assertEquals("Expected one Intent", 1, taskStackBuilder.intentCount)
+    }
+
+    @Test
     fun fromContextSetGraphNavInflater() {
         val deepLinkBuilder = NavDeepLinkBuilder(targetContext)
 
@@ -63,6 +96,16 @@
     }
 
     @Test
+    fun fromContextSetGraphNavInflaterRoute() {
+        val deepLinkBuilder = NavDeepLinkBuilder(targetContext)
+
+        deepLinkBuilder.setGraph(nav_simple_route_graph)
+        deepLinkBuilder.setDestination("second_test")
+        val taskStackBuilder = deepLinkBuilder.createTaskStackBuilder()
+        assertEquals("Expected one Intent", 1, taskStackBuilder.intentCount)
+    }
+
+    @Test
     fun fromContextSetGraphProgrammatic() {
         val deepLinkBuilder = NavDeepLinkBuilder(targetContext)
 
@@ -79,6 +122,25 @@
         assertEquals("Expected one Intent", 1, taskStackBuilder.intentCount)
     }
 
+    @Test
+    fun fromContextSetGraphProgrammaticRoute() {
+        val deepLinkBuilder = NavDeepLinkBuilder(targetContext)
+
+        val navigatorProvider = NavigatorProvider().apply {
+            addNavigator(NavGraphNavigator(this))
+            addNavigator(TestNavigator())
+        }
+        val navGraph = navigatorProvider.navigation(
+            route = "graph", startDestination = "test"
+        ) {
+            test("test")
+        }
+        deepLinkBuilder.setGraph(navGraph)
+        deepLinkBuilder.setDestination("test")
+        val taskStackBuilder = deepLinkBuilder.createTaskStackBuilder()
+        assertEquals("Expected one Intent", 1, taskStackBuilder.intentCount)
+    }
+
     @UiThreadTest
     @Test
     fun fromNavController() {
@@ -93,6 +155,20 @@
         assertEquals("Expected one Intent", 1, taskStackBuilder.intentCount)
     }
 
+    @UiThreadTest
+    @Test
+    fun fromNavControllerRoute() {
+        val navController = NavController(targetContext).apply {
+            navigatorProvider.addNavigator(TestNavigator())
+            graph = nav_simple_route_graph
+        }
+        val deepLinkBuilder = NavDeepLinkBuilder(navController)
+
+        deepLinkBuilder.setDestination("second_test")
+        val taskStackBuilder = deepLinkBuilder.createTaskStackBuilder()
+        assertEquals("Expected one Intent", 1, taskStackBuilder.intentCount)
+    }
+
     @Test
     fun pendingIntentEqualsWithSameArgs() {
         val deepLinkBuilder = NavDeepLinkBuilder(targetContext)
@@ -113,6 +189,25 @@
     }
 
     @Test
+    fun pendingIntentEqualsWithSameArgsRoute() {
+        val deepLinkBuilder = NavDeepLinkBuilder(targetContext)
+
+        deepLinkBuilder.setGraph(nav_simple_route_graph)
+        deepLinkBuilder.setDestination("second_test")
+        val args = Bundle().apply {
+            putString("test", "test")
+        }
+        deepLinkBuilder.setArguments(args)
+        val firstPendingIntent = deepLinkBuilder.createPendingIntent()
+
+        // Don't change anything and generate a new PendingIntent
+        val secondPendingIntent = deepLinkBuilder.createPendingIntent()
+        assertWithMessage("PendingIntents with the same destination and args should be the same")
+            .that(firstPendingIntent)
+            .isEqualTo(secondPendingIntent)
+    }
+
+    @Test
     fun pendingIntentNotEqualsWithDifferentDestination() {
         val deepLinkBuilder = NavDeepLinkBuilder(targetContext)
 
@@ -133,6 +228,26 @@
     }
 
     @Test
+    fun pendingIntentNotEqualsWithDifferentDestinationRoute() {
+        val deepLinkBuilder = NavDeepLinkBuilder(targetContext)
+
+        deepLinkBuilder.setGraph(nav_simple_route_graph)
+        deepLinkBuilder.setDestination("second_test")
+        val args = Bundle().apply {
+            putString("test", "test")
+        }
+        deepLinkBuilder.setArguments(args)
+        val firstPendingIntent = deepLinkBuilder.createPendingIntent()
+
+        // Change the destination but not the args
+        deepLinkBuilder.setDestination("start_test")
+        val secondPendingIntent = deepLinkBuilder.createPendingIntent()
+        assertWithMessage("PendingIntents with different destinations should be different")
+            .that(firstPendingIntent)
+            .isNotEqualTo(secondPendingIntent)
+    }
+
+    @Test
     fun pendingIntentNotEqualsWithDifferentArgs() {
         val deepLinkBuilder = NavDeepLinkBuilder(targetContext)
 
@@ -153,6 +268,26 @@
     }
 
     @Test
+    fun pendingIntentNotEqualsWithDifferentArgsRoute() {
+        val deepLinkBuilder = NavDeepLinkBuilder(targetContext)
+
+        deepLinkBuilder.setGraph(nav_simple_route_graph)
+        deepLinkBuilder.setDestination("second_test")
+        val args = Bundle().apply {
+            putString("test", "test")
+        }
+        deepLinkBuilder.setArguments(args)
+        val firstPendingIntent = deepLinkBuilder.createPendingIntent()
+
+        // Change the args but not the destination
+        args.putString("test", "test2")
+        val secondPendingIntent = deepLinkBuilder.createPendingIntent()
+        assertWithMessage("PendingIntents with different arguments should be different")
+            .that(firstPendingIntent)
+            .isNotEqualTo(secondPendingIntent)
+    }
+
+    @Test
     fun pendingIntentNotEqualsWithDifferentDestinationArgs() {
         val deepLinkBuilder = NavDeepLinkBuilder(targetContext)
 
@@ -172,4 +307,32 @@
             .that(firstPendingIntent)
             .isNotEqualTo(secondPendingIntent)
     }
+
+    @Test
+    fun pendingIntentNotEqualsWithDifferentDestinationArgsRoute() {
+        val deepLinkBuilder = NavDeepLinkBuilder(targetContext)
+
+        deepLinkBuilder.setGraph(nav_simple_route_graph)
+        val args = Bundle().apply {
+            putString("test", "test")
+        }
+        deepLinkBuilder.setDestination("second_test", args)
+        val firstPendingIntent = deepLinkBuilder.createPendingIntent()
+
+        // Change the args but not the destination
+        args.putString("test", "test2")
+        val secondPendingIntent = deepLinkBuilder.createPendingIntent()
+        assertWithMessage(
+            "PendingIntents with different destination arguments should be different"
+        )
+            .that(firstPendingIntent)
+            .isNotEqualTo(secondPendingIntent)
+    }
+
+    private fun createNavController(): NavController {
+        val navController = NavController(ApplicationProvider.getApplicationContext())
+        val navigator = TestNavigator()
+        navController.navigatorProvider.addNavigator(navigator)
+        return navController
+    }
 }
diff --git a/navigation/navigation-runtime/src/androidTest/java/androidx/navigation/NavInflaterTest.kt b/navigation/navigation-runtime/src/androidTest/java/androidx/navigation/NavInflaterTest.kt
index 8f6c3a5..9f25763 100644
--- a/navigation/navigation-runtime/src/androidTest/java/androidx/navigation/NavInflaterTest.kt
+++ b/navigation/navigation-runtime/src/androidTest/java/androidx/navigation/NavInflaterTest.kt
@@ -48,7 +48,7 @@
         val graph = navInflater.inflate(R.navigation.nav_simple)
 
         assertThat(graph).isNotNull()
-        assertThat(graph.startDestination)
+        assertThat(graph.startDestinationId)
             .isEqualTo(R.id.start_test)
     }
 
@@ -264,7 +264,7 @@
         val context = ApplicationProvider.getApplicationContext() as Context
         val navInflater = NavInflater(context, TestNavigatorProvider())
         val graph = navInflater.inflate(R.navigation.nav_default_arguments)
-        val startDestination = graph.findNode(graph.startDestination)
+        val startDestination = graph.findNode(graph.startDestinationId)
         val action = startDestination?.getAction(R.id.my_action)
         assertThat(action?.defaultArguments?.get("test_action_arg"))
             .isEqualTo(123L)
@@ -275,7 +275,7 @@
         val navInflater = NavInflater(context, TestNavigatorProvider())
         val graph = navInflater.inflate(R.navigation.nav_default_arguments)
 
-        val startDestination = graph.findNode(graph.startDestination)
+        val startDestination = graph.findNode(graph.startDestinationId)
         val defaultArguments = startDestination?.arguments
 
         assertThat(defaultArguments).isNotNull()
diff --git a/navigation/navigation-runtime/src/main/java/androidx/navigation/NavBackStackEntryState.kt b/navigation/navigation-runtime/src/main/java/androidx/navigation/NavBackStackEntryState.kt
index 212c62b..0caf28a 100644
--- a/navigation/navigation-runtime/src/main/java/androidx/navigation/NavBackStackEntryState.kt
+++ b/navigation/navigation-runtime/src/main/java/androidx/navigation/NavBackStackEntryState.kt
@@ -21,17 +21,16 @@
 import android.os.Parcelable
 import android.os.Parcel
 import androidx.lifecycle.LifecycleOwner
-import java.util.UUID
 
 @SuppressLint("BanParcelableUsage")
 internal class NavBackStackEntryState : Parcelable {
-    val uuid: UUID
+    val id: String
     val destinationId: Int
     val args: Bundle?
     val savedState: Bundle
 
     constructor(entry: NavBackStackEntry) {
-        uuid = entry.id
+        id = entry.id
         destinationId = entry.destination.id
         args = entry.arguments
         savedState = Bundle()
@@ -39,7 +38,7 @@
     }
 
     constructor(inParcel: Parcel) {
-        uuid = UUID.fromString(inParcel.readString())
+        id = inParcel.readString()!!
         destinationId = inParcel.readInt()
         args = inParcel.readBundle(javaClass.classLoader)
         savedState = inParcel.readBundle(javaClass.classLoader)!!
@@ -57,7 +56,7 @@
         return NavBackStackEntry.create(
             context, destination, args,
             lifecycleOwner, viewModel,
-            uuid, savedState
+            id, savedState
         )
     }
 
@@ -66,7 +65,7 @@
     }
 
     override fun writeToParcel(parcel: Parcel, i: Int) {
-        parcel.writeString(uuid.toString())
+        parcel.writeString(id)
         parcel.writeInt(destinationId)
         parcel.writeBundle(args)
         parcel.writeBundle(savedState)
diff --git a/navigation/navigation-runtime/src/main/java/androidx/navigation/NavController.kt b/navigation/navigation-runtime/src/main/java/androidx/navigation/NavController.kt
index a8c319b..054d302 100644
--- a/navigation/navigation-runtime/src/main/java/androidx/navigation/NavController.kt
+++ b/navigation/navigation-runtime/src/main/java/androidx/navigation/NavController.kt
@@ -31,17 +31,18 @@
 import androidx.annotation.NavigationRes
 import androidx.annotation.RestrictTo
 import androidx.core.app.TaskStackBuilder
+import androidx.core.net.toUri
 import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.LifecycleEventObserver
 import androidx.lifecycle.LifecycleObserver
 import androidx.lifecycle.LifecycleOwner
 import androidx.lifecycle.ViewModelStore
 import androidx.lifecycle.ViewModelStoreOwner
+import androidx.navigation.NavDestination.Companion.createRoute
 import kotlinx.coroutines.channels.BufferOverflow
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableSharedFlow
 import kotlinx.coroutines.flow.asSharedFlow
-import java.util.UUID
 
 /**
  * NavController manages app navigation within a [NavHost].
@@ -108,8 +109,8 @@
      */
     @get:RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
     public open val backQueue: ArrayDeque<NavBackStackEntry> = ArrayDeque()
-    private val backStackMap = mutableMapOf<Int, UUID?>()
-    private val backStackStates = mutableMapOf<UUID, ArrayDeque<NavBackStackEntryState>>()
+    private val backStackMap = mutableMapOf<Int, String?>()
+    private val backStackStates = mutableMapOf<String, ArrayDeque<NavBackStackEntryState>>()
     private var lifecycleOwner: LifecycleOwner? = null
     private var viewModel: NavControllerViewModel? = null
     private val onDestinationChangedListeners =
@@ -388,6 +389,28 @@
     }
 
     /**
+     * Attempts to pop the controller's back stack back to a specific destination.
+     *
+     * @param route The topmost destination to retain
+     * @param inclusive Whether the given destination should also be popped.
+     * @param saveState Whether the back stack and the state of all destinations between the
+     * current destination and the [route] should be saved for later
+     * restoration via [NavOptions.Builder.setRestoreState] or the `restoreState` attribute using
+     * the same [route] (note: this matching ID is true whether
+     * [inclusive] is true or false).
+     *
+     * @return true if the stack was popped at least once and the user has been navigated to
+     * another destination, false otherwise
+     */
+    @MainThread
+    @JvmOverloads
+    public fun popBackStack(
+        route: String,
+        inclusive: Boolean,
+        saveState: Boolean = false
+    ): Boolean = popBackStack(createRoute(route).hashCode(), inclusive, saveState)
+
+    /**
      * Attempts to pop the controller's back stack back to a specific destination. This does
      * **not** handle calling [dispatchOnDestinationChanged]
      *
@@ -460,7 +483,7 @@
                 // saved state to the destination you've actually passed to popUpTo
                 // as well as its parents (if it is the start destination)
                 generateSequence(foundDestination) { destination ->
-                    if (destination.parent?.startDestination == destination.id) {
+                    if (destination.parent?.startDestinationId == destination.id) {
                         destination.parent
                     } else {
                         null
@@ -469,7 +492,7 @@
                     // Only add the state if it doesn't already exist
                     !backStackMap.containsKey(destination.id)
                 }.forEach { destination ->
-                    backStackMap[destination.id] = savedState.firstOrNull()?.uuid
+                    backStackMap[destination.id] = savedState.firstOrNull()?.id
                 }
             }
             if (savedState.isNotEmpty()) {
@@ -479,7 +502,7 @@
                 // as well as its parents (if it is the start destination)
                 val firstStateDestination = findDestination(firstState.destinationId)
                 generateSequence(firstStateDestination) { destination ->
-                    if (destination.parent?.startDestination == destination.id) {
+                    if (destination.parent?.startDestinationId == destination.id) {
                         destination.parent
                     } else {
                         null
@@ -488,10 +511,10 @@
                     // Only add the state if it doesn't already exist
                     !backStackMap.containsKey(destination.id)
                 }.forEach { destination ->
-                    backStackMap[destination.id] = firstState.uuid
+                    backStackMap[destination.id] = firstState.id
                 }
                 // And finally, store the actual state itself
-                backStackStates[firstState.uuid] = savedState
+                backStackStates[firstState.id] = savedState
             }
         }
         updateOnBackPressedCallbackEnabled()
@@ -546,7 +569,7 @@
             var destId = currentDestination!!.id
             var parent = currentDestination.parent
             while (parent != null) {
-                if (parent.startDestination != destId) {
+                if (parent.startDestinationId != destId) {
                     val args = Bundle()
                     if (activity != null && activity!!.intent != null) {
                         val data = activity!!.intent.data
@@ -955,8 +978,8 @@
                     graph = node
                     // Automatically go down the navigation graph when
                     // the start destination is also a NavGraph
-                    while (graph!!.findNode(graph.startDestination) is NavGraph) {
-                        graph = graph.findNode(graph.startDestination) as NavGraph?
+                    while (graph!!.findNode(graph.startDestinationId) is NavGraph) {
+                        graph = graph.findNode(graph.startDestinationId) as NavGraph?
                     }
                 }
             } else {
@@ -1003,8 +1026,8 @@
                     graph = node
                     // Automatically go down the navigation graph when
                     // the start destination is also a NavGraph
-                    while (graph!!.findNode(graph.startDestination) is NavGraph) {
-                        graph = graph.findNode(graph.startDestination) as NavGraph?
+                    while (graph!!.findNode(graph.startDestinationId) is NavGraph) {
+                        graph = graph.findNode(graph.startDestinationId) as NavGraph?
                     }
                 }
             }
@@ -1041,6 +1064,19 @@
         return currentGraph.findNode(destinationId)
     }
 
+    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+    public fun findDestination(destinationRoute: String): NavDestination? {
+        if (_graph == null) {
+            return null
+        }
+        if (_graph!!.route == destinationRoute) {
+            return _graph
+        }
+        val currentNode = backQueue.lastOrNull()?.destination ?: _graph!!
+        val currentGraph = if (currentNode is NavGraph) currentNode else currentNode.parent!!
+        return currentGraph.findNode(destinationRoute)
+    }
+
     /**
      * Navigate to a destination from the current navigation graph. This supports both navigating
      * via an [action][NavDestination.getAction] and directly navigating to a destination.
@@ -1141,8 +1177,8 @@
             }
             combinedArgs.putAll(args)
         }
-        if (destId == 0 && finalNavOptions != null && finalNavOptions.popUpTo != -1) {
-            popBackStack(finalNavOptions.popUpTo, finalNavOptions.isPopUpToInclusive())
+        if (destId == 0 && finalNavOptions != null && finalNavOptions.popUpToId != -1) {
+            popBackStack(finalNavOptions.popUpToId, finalNavOptions.isPopUpToInclusive())
             return
         }
         require(destId != 0) {
@@ -1298,9 +1334,9 @@
         var launchSingleTop = false
         var navigated = false
         if (navOptions != null) {
-            if (navOptions.popUpTo != -1) {
+            if (navOptions.popUpToId != -1) {
                 popped = popBackStackInternal(
-                    navOptions.popUpTo,
+                    navOptions.popUpToId,
                     navOptions.isPopUpToInclusive(),
                     navOptions.shouldPopUpToSaveState()
                 )
@@ -1321,10 +1357,10 @@
         }
         // Now determine what new destinations we need to add to the back stack
         if (navOptions?.shouldRestoreState() == true && backStackMap.containsKey(node.id)) {
-            val backStackUUID = backStackMap[node.id]
+            val backStackId = backStackMap[node.id]
             // Clear out the state we're going to restore so that it isn't restored a second time
-            backStackMap.values.removeAll { it == backStackUUID }
-            val backStackState = backStackStates.remove(backStackUUID)
+            backStackMap.values.removeAll { it == backStackId }
+            val backStackState = backStackStates.remove(backStackId)
             // Now restore the back stack from its saved state
             val entries = instantiateBackStack(backStackState)
             // Split up the entries by Navigator so we can restore them as an atomic operation
@@ -1541,6 +1577,41 @@
     }
 
     /**
+     * Navigate to a route in the current NavGraph. If an invalid route is given, an
+     * [IllegalArgumentException] will be thrown.
+     *
+     * @param route route for the destination
+     * @param builder DSL for constructing a new [NavOptions]
+     *
+     * @throws IllegalArgumentException if the given route is invalid
+     */
+    public fun navigate(route: String, builder: NavOptionsBuilder.() -> Unit) {
+        navigate(route, navOptions(builder))
+    }
+
+    /**
+     * Navigate to a route in the current NavGraph. If an invalid route is given, an
+     * [IllegalArgumentException] will be thrown.
+     *
+     * @param route route for the destination
+     * @param navOptions special options for this navigation operation
+     * @param navigatorExtras extras to pass to the [Navigator]
+     *
+     * @throws IllegalArgumentException if the given route is invalid
+     */
+    @JvmOverloads
+    public fun navigate(
+        route: String,
+        navOptions: NavOptions? = null,
+        navigatorExtras: Navigator.Extras? = null
+    ) {
+        navigate(
+            NavDeepLinkRequest.Builder.fromUri(createRoute(route).toUri()).build(), navOptions,
+            navigatorExtras
+        )
+    }
+
+    /**
      * Create a deep link to a destination within this NavController.
      *
      * @return a [NavDeepLinkBuilder] suitable for constructing a deep link
@@ -1590,30 +1661,30 @@
             if (b == null) {
                 b = Bundle()
             }
-            val backStackIds = IntArray(backStackMap.size)
-            val backStackUUIDs = ArrayList<String>()
+            val backStackDestIds = IntArray(backStackMap.size)
+            val backStackIds = ArrayList<String?>()
             var index = 0
-            for ((id, uuid) in backStackMap) {
-                backStackIds[index++] = id
-                backStackUUIDs += uuid.toString()
+            for ((destId, id) in backStackMap) {
+                backStackDestIds[index++] = destId
+                backStackIds += id
             }
-            b.putIntArray(KEY_BACK_STACK_IDS, backStackIds)
-            b.putStringArrayList(KEY_BACK_STACK_UUIDS, backStackUUIDs)
+            b.putIntArray(KEY_BACK_STACK_DEST_IDS, backStackDestIds)
+            b.putStringArrayList(KEY_BACK_STACK_IDS, backStackIds)
         }
         if (backStackStates.isNotEmpty()) {
             if (b == null) {
                 b = Bundle()
             }
-            val backStackStateUUIDs = ArrayList<String>()
-            for ((uuid, backStackStates) in backStackStates) {
-                backStackStateUUIDs += uuid.toString()
+            val backStackStateIds = ArrayList<String>()
+            for ((id, backStackStates) in backStackStates) {
+                backStackStateIds += id
                 val states = arrayOfNulls<Parcelable>(backStackStates.size)
                 backStackStates.forEachIndexed { stateIndex, backStackState ->
                     states[stateIndex] = backStackState
                 }
-                b.putParcelableArray(KEY_BACK_STACK_STATES_PREFIX + uuid, states)
+                b.putParcelableArray(KEY_BACK_STACK_STATES_PREFIX + id, states)
             }
-            b.putStringArrayList(KEY_BACK_STACK_STATES_UUIDS, backStackStateUUIDs)
+            b.putStringArrayList(KEY_BACK_STACK_STATES_IDS, backStackStateIds)
         }
         if (deepLinkHandled) {
             if (b == null) {
@@ -1642,18 +1713,18 @@
         navigatorStateToRestore = navState.getBundle(KEY_NAVIGATOR_STATE)
         backStackToRestore = navState.getParcelableArray(KEY_BACK_STACK)
         backStackStates.clear()
-        val backStackIds = navState.getIntArray(KEY_BACK_STACK_IDS)
-        val backStackUUIDs = navState.getStringArrayList(KEY_BACK_STACK_UUIDS)
-        if (backStackIds != null && backStackUUIDs != null) {
-            backStackIds.forEachIndexed { index, id ->
-                backStackMap[id] = UUID.fromString(backStackUUIDs[index])
+        val backStackDestIds = navState.getIntArray(KEY_BACK_STACK_DEST_IDS)
+        val backStackIds = navState.getStringArrayList(KEY_BACK_STACK_IDS)
+        if (backStackDestIds != null && backStackIds != null) {
+            backStackDestIds.forEachIndexed { index, id ->
+                backStackMap[id] = backStackIds[index]
             }
         }
-        val backStackStateIds = navState.getStringArrayList(KEY_BACK_STACK_STATES_UUIDS)
-        backStackStateIds?.map { UUID.fromString(it) }?.forEach { uuid ->
-            val backStackState = navState.getParcelableArray(KEY_BACK_STACK_STATES_PREFIX + uuid)
+        val backStackStateIds = navState.getStringArrayList(KEY_BACK_STACK_STATES_IDS)
+        backStackStateIds?.forEach { id ->
+            val backStackState = navState.getParcelableArray(KEY_BACK_STACK_STATES_PREFIX + id)
             if (backStackState != null) {
-                backStackStates[uuid] = ArrayDeque<NavBackStackEntryState>(
+                backStackStates[id] = ArrayDeque<NavBackStackEntryState>(
                     backStackState.size
                 ).apply {
                     for (parcelable in backStackState) {
@@ -1758,6 +1829,27 @@
     }
 
     /**
+     * Gets the topmost {@link NavBackStackEntry} for a route.
+     * <p>
+     * This is always safe to use with {@link #getCurrentDestination() the current destination} or
+     * {@link NavDestination#getParent() its parent} or grandparent navigation graphs as these
+     * destinations are guaranteed to be on the back stack.
+     *
+     * @param route route of a destination that exists on the back stack
+     * @throws IllegalArgumentException if the destination is not on the back stack
+     */
+    public fun getBackStackEntry(route: String): NavBackStackEntry {
+        val lastFromBackStack: NavBackStackEntry? = backQueue.lastOrNull { entry ->
+            entry.destination.route == route
+        }
+        requireNotNull(lastFromBackStack) {
+            "No destination with route $route is on the NavController's back stack. The " +
+                "current destination is $currentDestination"
+        }
+        return lastFromBackStack
+    }
+
+    /**
      * Gets the topmost [NavBackStackEntry].
      *
      * @return the topmost entry on the back stack or null if the back stack is empty
@@ -1801,11 +1893,11 @@
         private const val KEY_NAVIGATOR_STATE_NAMES =
             "android-support-nav:controller:navigatorState:names"
         private const val KEY_BACK_STACK = "android-support-nav:controller:backStack"
+        private const val KEY_BACK_STACK_DEST_IDS =
+            "android-support-nav:controller:backStackDestIds"
         private const val KEY_BACK_STACK_IDS =
             "android-support-nav:controller:backStackIds"
-        private const val KEY_BACK_STACK_UUIDS =
-            "android-support-nav:controller:backStackUUIDs"
-        private const val KEY_BACK_STACK_STATES_UUIDS =
+        private const val KEY_BACK_STACK_STATES_IDS =
             "android-support-nav:controller:backStackStates"
         private const val KEY_BACK_STACK_STATES_PREFIX =
             "android-support-nav:controller:backStackStates:"
@@ -1837,3 +1929,16 @@
     @IdRes startDestination: Int,
     builder: NavGraphBuilder.() -> Unit
 ): NavGraph = navigatorProvider.navigation(id, startDestination, builder)
+
+/**
+ * Construct a new [NavGraph]
+ *
+ * @param route the route for the graph
+ * @param startDestination the route for the start destination
+ * @param builder the builder used to construct the graph
+ */
+public fun NavController.createGraph(
+    startDestination: String,
+    route: String? = null,
+    builder: NavGraphBuilder.() -> Unit
+): NavGraph = navigatorProvider.navigation(startDestination, route, builder)
diff --git a/navigation/navigation-runtime/src/main/java/androidx/navigation/NavControllerViewModel.kt b/navigation/navigation-runtime/src/main/java/androidx/navigation/NavControllerViewModel.kt
index aee4d6b..4534b3a 100644
--- a/navigation/navigation-runtime/src/main/java/androidx/navigation/NavControllerViewModel.kt
+++ b/navigation/navigation-runtime/src/main/java/androidx/navigation/NavControllerViewModel.kt
@@ -19,18 +19,17 @@
 import androidx.lifecycle.ViewModelProvider
 import androidx.lifecycle.ViewModelStore
 import androidx.lifecycle.get
-import java.util.UUID
 
 /**
  * NavControllerViewModel is the always up to date view of the NavController's
  * non configuration state
  */
 internal class NavControllerViewModel : ViewModel(), NavViewModelStoreProvider {
-    private val viewModelStores = mutableMapOf<UUID, ViewModelStore>()
+    private val viewModelStores = mutableMapOf<String, ViewModelStore>()
 
-    fun clear(backStackEntryUUID: UUID) {
+    fun clear(backStackEntryId: String) {
         // Clear and remove the NavGraph's ViewModelStore
-        val viewModelStore = viewModelStores.remove(backStackEntryUUID)
+        val viewModelStore = viewModelStores.remove(backStackEntryId)
         viewModelStore?.clear()
     }
 
@@ -41,11 +40,11 @@
         viewModelStores.clear()
     }
 
-    override fun getViewModelStore(backStackEntryUUID: UUID): ViewModelStore {
-        var viewModelStore = viewModelStores[backStackEntryUUID]
+    override fun getViewModelStore(backStackEntryId: String): ViewModelStore {
+        var viewModelStore = viewModelStores[backStackEntryId]
         if (viewModelStore == null) {
             viewModelStore = ViewModelStore()
-            viewModelStores[backStackEntryUUID] = viewModelStore
+            viewModelStores[backStackEntryId] = viewModelStore
         }
         return viewModelStore
     }
@@ -54,7 +53,7 @@
         val sb = StringBuilder("NavControllerViewModel{")
         sb.append(Integer.toHexString(System.identityHashCode(this)))
         sb.append("} ViewModelStores (")
-        val viewModelStoreIterator: Iterator<UUID> = viewModelStores.keys.iterator()
+        val viewModelStoreIterator: Iterator<String> = viewModelStores.keys.iterator()
         while (viewModelStoreIterator.hasNext()) {
             sb.append(viewModelStoreIterator.next())
             if (viewModelStoreIterator.hasNext()) {
diff --git a/navigation/navigation-runtime/src/main/java/androidx/navigation/NavDeepLinkBuilder.kt b/navigation/navigation-runtime/src/main/java/androidx/navigation/NavDeepLinkBuilder.kt
index 9229747..0d8bbb9 100644
--- a/navigation/navigation-runtime/src/main/java/androidx/navigation/NavDeepLinkBuilder.kt
+++ b/navigation/navigation-runtime/src/main/java/androidx/navigation/NavDeepLinkBuilder.kt
@@ -24,6 +24,7 @@
 import androidx.annotation.IdRes
 import androidx.annotation.NavigationRes
 import androidx.core.app.TaskStackBuilder
+import androidx.navigation.NavDestination.Companion.createRoute
 
 /**
  * Class used to construct deep links to a particular destination in a [NavGraph].
@@ -151,6 +152,26 @@
     }
 
     /**
+     * Sets the destination route to deep link to. Any destinations previous added via
+     * [.addDestination] are cleared, effectively resetting this object
+     * back to only this single destination.
+     *
+     * @param destRoute destination route to deep link to.
+     * @param args Arguments to pass to this destination and any synthetic back stack created
+     * due to this destination being added.
+     * @return this object for chaining
+     */
+    @JvmOverloads
+    public fun setDestination(destRoute: String, args: Bundle? = null): NavDeepLinkBuilder {
+        destinations.clear()
+        destinations.add(DeepLinkDestination(createRoute(destRoute).hashCode(), args))
+        if (graph != null) {
+            verifyAllDestinations()
+        }
+        return this
+    }
+
+    /**
      * Add a new destination id to deep link to. This builds off any previous calls to this method
      * or calls to [setDestination], building the minimal synthetic back stack of
      * start destinations between the previous deep link destination and the newly added
@@ -170,6 +191,26 @@
         return this
     }
 
+    /**
+     * Add a new destination route to deep link to. This builds off any previous calls to this
+     * method or calls to [.setDestination], building the minimal synthetic back stack of
+     * start destinations between the previous deep link destination and the newly added
+     * deep link destination.
+     *
+     * @param route destination route to deep link to.
+     * @param args Arguments to pass to this destination and any synthetic back stack created
+     * due to this destination being added.
+     * @return this object for chaining
+     */
+    @JvmOverloads
+    public fun addDestination(route: String, args: Bundle? = null): NavDeepLinkBuilder {
+        destinations.add(DeepLinkDestination(createRoute(route).hashCode(), args))
+        if (graph != null) {
+            verifyAllDestinations()
+        }
+        return this
+    }
+
     private fun findDestination(@IdRes destId: Int): NavDestination? {
         val possibleDestinations = ArrayDeque<NavDestination>()
         possibleDestinations.add(graph!!)
diff --git a/navigation/navigation-safe-args-generator/build.gradle b/navigation/navigation-safe-args-generator/build.gradle
index 82b41da..22d143a 100644
--- a/navigation/navigation-safe-args-generator/build.gradle
+++ b/navigation/navigation-safe-args-generator/build.gradle
@@ -58,8 +58,8 @@
     it.classpath = files(classpath.minus(androidJar).plus(androidJar))
 }
 
-tasks.findByName("compileKotlin").dependsOn(":navigation:navigation-common:jarDebug")
-tasks.findByName("compileKotlin").dependsOn(":lifecycle:lifecycle-viewmodel-savedstate:jarDebug")
+tasks.findByName("compileKotlin").dependsOn(":navigation:navigation-common:jarRelease")
+tasks.findByName("compileKotlin").dependsOn(":lifecycle:lifecycle-viewmodel-savedstate:jarRelease")
 
 androidx {
     name = "Android Navigation TypeSafe Arguments Generator"
diff --git a/navigation/navigation-safe-args-generator/lint-baseline.xml b/navigation/navigation-safe-args-generator/lint-baseline.xml
deleted file mode 100644
index 8794ae8..0000000
--- a/navigation/navigation-safe-args-generator/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" version="4.2.0-beta06">
-
-</issues>
diff --git a/navigation/navigation-safe-args-gradle-plugin/lint-baseline.xml b/navigation/navigation-safe-args-gradle-plugin/lint-baseline.xml
deleted file mode 100644
index 8794ae8..0000000
--- a/navigation/navigation-safe-args-gradle-plugin/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" version="4.2.0-beta06">
-
-</issues>
diff --git a/navigation/navigation-testing/api/current.txt b/navigation/navigation-testing/api/current.txt
index f9ce0d4..8fc24e8 100644
--- a/navigation/navigation-testing/api/current.txt
+++ b/navigation/navigation-testing/api/current.txt
@@ -6,12 +6,16 @@
     method public java.util.List<androidx.navigation.NavBackStackEntry> getBackStack();
     method public void setCurrentDestination(@IdRes int destId, optional android.os.Bundle args);
     method public void setCurrentDestination(@IdRes int destId);
+    method public void setCurrentDestination(String destRoute, optional android.os.Bundle args);
+    method public void setCurrentDestination(String destRoute);
     property public final java.util.List<androidx.navigation.NavBackStackEntry> backStack;
   }
 
   public final class TestNavigatorState extends androidx.navigation.NavigatorState {
+    ctor public TestNavigatorState(optional android.content.Context? context, optional kotlinx.coroutines.CoroutineDispatcher coroutineDispatcher);
     ctor public TestNavigatorState(optional android.content.Context? context);
     method public androidx.navigation.NavBackStackEntry createBackStackEntry(androidx.navigation.NavDestination destination, android.os.Bundle? arguments);
+    method public androidx.navigation.NavBackStackEntry restoreBackStackEntry(androidx.navigation.NavBackStackEntry previouslySavedEntry);
   }
 
 }
diff --git a/navigation/navigation-testing/api/public_plus_experimental_current.txt b/navigation/navigation-testing/api/public_plus_experimental_current.txt
index f9ce0d4..8fc24e8 100644
--- a/navigation/navigation-testing/api/public_plus_experimental_current.txt
+++ b/navigation/navigation-testing/api/public_plus_experimental_current.txt
@@ -6,12 +6,16 @@
     method public java.util.List<androidx.navigation.NavBackStackEntry> getBackStack();
     method public void setCurrentDestination(@IdRes int destId, optional android.os.Bundle args);
     method public void setCurrentDestination(@IdRes int destId);
+    method public void setCurrentDestination(String destRoute, optional android.os.Bundle args);
+    method public void setCurrentDestination(String destRoute);
     property public final java.util.List<androidx.navigation.NavBackStackEntry> backStack;
   }
 
   public final class TestNavigatorState extends androidx.navigation.NavigatorState {
+    ctor public TestNavigatorState(optional android.content.Context? context, optional kotlinx.coroutines.CoroutineDispatcher coroutineDispatcher);
     ctor public TestNavigatorState(optional android.content.Context? context);
     method public androidx.navigation.NavBackStackEntry createBackStackEntry(androidx.navigation.NavDestination destination, android.os.Bundle? arguments);
+    method public androidx.navigation.NavBackStackEntry restoreBackStackEntry(androidx.navigation.NavBackStackEntry previouslySavedEntry);
   }
 
 }
diff --git a/navigation/navigation-testing/api/restricted_current.txt b/navigation/navigation-testing/api/restricted_current.txt
index f9ce0d4..8fc24e8 100644
--- a/navigation/navigation-testing/api/restricted_current.txt
+++ b/navigation/navigation-testing/api/restricted_current.txt
@@ -6,12 +6,16 @@
     method public java.util.List<androidx.navigation.NavBackStackEntry> getBackStack();
     method public void setCurrentDestination(@IdRes int destId, optional android.os.Bundle args);
     method public void setCurrentDestination(@IdRes int destId);
+    method public void setCurrentDestination(String destRoute, optional android.os.Bundle args);
+    method public void setCurrentDestination(String destRoute);
     property public final java.util.List<androidx.navigation.NavBackStackEntry> backStack;
   }
 
   public final class TestNavigatorState extends androidx.navigation.NavigatorState {
+    ctor public TestNavigatorState(optional android.content.Context? context, optional kotlinx.coroutines.CoroutineDispatcher coroutineDispatcher);
     ctor public TestNavigatorState(optional android.content.Context? context);
     method public androidx.navigation.NavBackStackEntry createBackStackEntry(androidx.navigation.NavDestination destination, android.os.Bundle? arguments);
+    method public androidx.navigation.NavBackStackEntry restoreBackStackEntry(androidx.navigation.NavBackStackEntry previouslySavedEntry);
   }
 
 }
diff --git a/navigation/navigation-testing/lint-baseline.xml b/navigation/navigation-testing/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/navigation/navigation-testing/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/navigation/navigation-testing/src/androidTest/java/androidx/navigation/testing/TestNavigatorStateTest.kt b/navigation/navigation-testing/src/androidTest/java/androidx/navigation/testing/TestNavigatorStateTest.kt
new file mode 100644
index 0000000..b20964e
--- /dev/null
+++ b/navigation/navigation-testing/src/androidTest/java/androidx/navigation/testing/TestNavigatorStateTest.kt
@@ -0,0 +1,105 @@
+/*
+ * 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.navigation.testing
+
+import androidx.lifecycle.Lifecycle
+import androidx.navigation.FloatingWindow
+import androidx.navigation.NavDestination
+import androidx.navigation.Navigator
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class TestNavigatorStateTest {
+    private lateinit var state: TestNavigatorState
+
+    @Before
+    fun setUp() {
+        state = TestNavigatorState()
+    }
+
+    @Test
+    fun testLifecycle() {
+        val navigator = TestNavigator()
+        navigator.onAttach(state)
+        val firstEntry = state.createBackStackEntry(navigator.createDestination(), null)
+        assertThat(firstEntry.lifecycle.currentState)
+            .isEqualTo(Lifecycle.State.INITIALIZED)
+
+        navigator.navigate(listOf(firstEntry), null, null)
+        assertThat(firstEntry.lifecycle.currentState)
+            .isEqualTo(Lifecycle.State.RESUMED)
+
+        val secondEntry = state.createBackStackEntry(navigator.createDestination(), null)
+        navigator.navigate(listOf(secondEntry), null, null)
+        assertThat(firstEntry.lifecycle.currentState)
+            .isEqualTo(Lifecycle.State.CREATED)
+        assertThat(secondEntry.lifecycle.currentState)
+            .isEqualTo(Lifecycle.State.RESUMED)
+
+        navigator.popBackStack(secondEntry, false)
+        assertThat(firstEntry.lifecycle.currentState)
+            .isEqualTo(Lifecycle.State.RESUMED)
+        assertThat(secondEntry.lifecycle.currentState)
+            .isEqualTo(Lifecycle.State.DESTROYED)
+    }
+
+    @Test
+    fun testFloatingWindowLifecycle() {
+        val navigator = FloatingWindowTestNavigator()
+        navigator.onAttach(state)
+        val firstEntry = state.createBackStackEntry(navigator.createDestination(), null)
+        assertThat(firstEntry.lifecycle.currentState)
+            .isEqualTo(Lifecycle.State.INITIALIZED)
+
+        navigator.navigate(listOf(firstEntry), null, null)
+        assertThat(firstEntry.lifecycle.currentState)
+            .isEqualTo(Lifecycle.State.RESUMED)
+
+        val secondEntry = state.createBackStackEntry(navigator.createDestination(), null)
+        navigator.navigate(listOf(secondEntry), null, null)
+        assertThat(firstEntry.lifecycle.currentState)
+            .isEqualTo(Lifecycle.State.STARTED)
+        assertThat(secondEntry.lifecycle.currentState)
+            .isEqualTo(Lifecycle.State.RESUMED)
+
+        navigator.popBackStack(secondEntry, false)
+        assertThat(firstEntry.lifecycle.currentState)
+            .isEqualTo(Lifecycle.State.RESUMED)
+        assertThat(secondEntry.lifecycle.currentState)
+            .isEqualTo(Lifecycle.State.DESTROYED)
+    }
+
+    @Navigator.Name("test")
+    internal class TestNavigator : Navigator<NavDestination>() {
+        override fun createDestination(): NavDestination = NavDestination(this)
+    }
+
+    @Navigator.Name("test")
+    internal class FloatingWindowTestNavigator : Navigator<FloatingTestDestination>() {
+        override fun createDestination(): FloatingTestDestination = FloatingTestDestination(this)
+    }
+
+    internal class FloatingTestDestination(
+        navigator: Navigator<out NavDestination>
+    ) : NavDestination(navigator), FloatingWindow
+}
\ No newline at end of file
diff --git a/navigation/navigation-testing/src/main/java/androidx/navigation/testing/TestNavHostController.kt b/navigation/navigation-testing/src/main/java/androidx/navigation/testing/TestNavHostController.kt
index ecf869a..8308c17 100644
--- a/navigation/navigation-testing/src/main/java/androidx/navigation/testing/TestNavHostController.kt
+++ b/navigation/navigation-testing/src/main/java/androidx/navigation/testing/TestNavHostController.kt
@@ -55,4 +55,24 @@
         val intent = taskStackBuilder.editIntentAt(0)
         require(handleDeepLink(intent)) { "Destination does not exist on the NavGraph." }
     }
+
+    /**
+     * Navigate directly to any destination on the current [androidx.navigation.NavGraph] via an
+     * explicit deep link. If an implicit deep link exists for this destination use
+     * [#navigate(Uri)] instead.
+     *
+     * @param destRoute The destination route to navigate to.
+     * @param args The arguments to pass to the destination.
+     * @throws IllegalArgumentException If the [destination][destRoute] does not exist on the
+     * NavGraph.
+     */
+    @JvmOverloads
+    public fun setCurrentDestination(destRoute: String, args: Bundle = Bundle()) {
+        val taskStackBuilder = createDeepLink()
+            .setDestination(destRoute)
+            .setArguments(args)
+            .createTaskStackBuilder()
+        val intent = taskStackBuilder.editIntentAt(0)
+        require(handleDeepLink(intent)) { "Destination does not exist on the NavGraph." }
+    }
 }
diff --git a/navigation/navigation-testing/src/main/java/androidx/navigation/testing/TestNavigatorState.kt b/navigation/navigation-testing/src/main/java/androidx/navigation/testing/TestNavigatorState.kt
index d91c6d2..9140e76 100644
--- a/navigation/navigation-testing/src/main/java/androidx/navigation/testing/TestNavigatorState.kt
+++ b/navigation/navigation-testing/src/main/java/androidx/navigation/testing/TestNavigatorState.kt
@@ -22,11 +22,15 @@
 import androidx.lifecycle.LifecycleOwner
 import androidx.lifecycle.ViewModelStore
 import androidx.lifecycle.testing.TestLifecycleOwner
+import androidx.navigation.FloatingWindow
 import androidx.navigation.NavBackStackEntry
 import androidx.navigation.NavDestination
 import androidx.navigation.NavViewModelStoreProvider
 import androidx.navigation.NavigatorState
-import java.util.UUID
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.withContext
 
 /**
  * An implementation of [NavigatorState] that allows testing a
@@ -36,26 +40,100 @@
  * An optional [context] can be provided to allow for the usages of
  * [androidx.lifecycle.AndroidViewModel] within the created [NavBackStackEntry]
  * instances.
+ *
+ * The [Lifecycle] of all [NavBackStackEntry] instances added to this TestNavigatorState
+ * will be updated as they are added and removed from the state. This work is kicked off
+ * on the [coroutineDispatcher].
  */
 public class TestNavigatorState @JvmOverloads constructor(
-    private val context: Context? = null
+    private val context: Context? = null,
+    private val coroutineDispatcher: CoroutineDispatcher = Dispatchers.Main.immediate
 ) : NavigatorState() {
 
-    private val lifecycleOwner: LifecycleOwner = TestLifecycleOwner(Lifecycle.State.RESUMED)
+    private val lifecycleOwner: LifecycleOwner = TestLifecycleOwner(
+        Lifecycle.State.RESUMED,
+        coroutineDispatcher
+    )
 
     private val viewModelStoreProvider = object : NavViewModelStoreProvider {
-        private val viewModelStores = mutableMapOf<UUID, ViewModelStore>()
+        private val viewModelStores = mutableMapOf<String, ViewModelStore>()
         override fun getViewModelStore(
-            backStackEntryUUID: UUID
-        ) = viewModelStores.getOrPut(backStackEntryUUID) {
+            backStackEntryId: String
+        ) = viewModelStores.getOrPut(backStackEntryId) {
             ViewModelStore()
         }
     }
 
+    private val savedStates = mutableMapOf<String, Bundle>()
+
     override fun createBackStackEntry(
         destination: NavDestination,
         arguments: Bundle?
     ): NavBackStackEntry = NavBackStackEntry.create(
         context, destination, arguments, lifecycleOwner, viewModelStoreProvider
     )
+
+    /**
+     * Restore a previously saved [NavBackStackEntry]. You must have previously called
+     * [pop] with [previouslySavedEntry] and `true`.
+     */
+    public fun restoreBackStackEntry(previouslySavedEntry: NavBackStackEntry): NavBackStackEntry {
+        val savedState = checkNotNull(savedStates[previouslySavedEntry.id]) {
+            "restoreBackStackEntry(previouslySavedEntry) must be passed a NavBackStackEntry " +
+                "that was previously popped with popBackStack(previouslySavedEntry, true)"
+        }
+        return NavBackStackEntry.create(
+            context,
+            previouslySavedEntry.destination, previouslySavedEntry.arguments,
+            lifecycleOwner, viewModelStoreProvider,
+            previouslySavedEntry.id, savedState
+        )
+    }
+
+    override fun add(backStackEntry: NavBackStackEntry) {
+        super.add(backStackEntry)
+        updateMaxLifecycle()
+    }
+
+    override fun pop(popUpTo: NavBackStackEntry, saveState: Boolean) {
+        val beforePopList = backStack.value
+        val poppedList = beforePopList.subList(beforePopList.indexOf(popUpTo), beforePopList.size)
+        super.pop(popUpTo, saveState)
+        updateMaxLifecycle(poppedList, saveState)
+    }
+
+    private fun updateMaxLifecycle(
+        poppedList: List<NavBackStackEntry> = emptyList(),
+        saveState: Boolean = false
+    ) {
+        runBlocking(coroutineDispatcher) {
+            // NavBackStackEntry Lifecycles must be updated on the main thread
+            // as per the contract within Lifecycle, so we explicitly swap to the main thread
+            // no matter what CoroutineDispatcher was passed to us.
+            withContext(Dispatchers.Main.immediate) {
+                // Mark all removed NavBackStackEntries as DESTROYED
+                for (entry in poppedList.reversed()) {
+                    if (saveState) {
+                        // Move the NavBackStackEntry to the stopped state, then save its state
+                        entry.maxLifecycle = Lifecycle.State.CREATED
+                        val savedState = Bundle()
+                        entry.saveState(savedState)
+                        savedStates[entry.id] = savedState
+                    }
+                    entry.maxLifecycle = Lifecycle.State.DESTROYED
+                }
+                // Now go through the current list of destinations, updating their Lifecycle state
+                val currentList = backStack.value
+                var previousEntry: NavBackStackEntry? = null
+                for (entry in currentList.reversed()) {
+                    entry.maxLifecycle = when {
+                        previousEntry == null -> Lifecycle.State.RESUMED
+                        previousEntry.destination is FloatingWindow -> Lifecycle.State.STARTED
+                        else -> Lifecycle.State.CREATED
+                    }
+                    previousEntry = entry
+                }
+            }
+        }
+    }
 }
diff --git a/navigation/navigation-ui-ktx/lint-baseline.xml b/navigation/navigation-ui-ktx/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/navigation/navigation-ui-ktx/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/navigation/navigation-ui/build.gradle b/navigation/navigation-ui/build.gradle
index 446191d..ab64177 100644
--- a/navigation/navigation-ui/build.gradle
+++ b/navigation/navigation-ui/build.gradle
@@ -30,6 +30,9 @@
     buildTypes.all {
         consumerProguardFiles "proguard-rules.pro"
     }
+    defaultConfig {
+        multiDexEnabled true
+    }
 }
 
 dependencies {
@@ -47,6 +50,7 @@
     androidTestImplementation(ANDROIDX_TEST_CORE)
     androidTestImplementation(ANDROIDX_TEST_RUNNER)
     androidTestImplementation(TRUTH)
+    androidTestImplementation(MULTIDEX)
 }
 
 androidx {
diff --git a/navigation/navigation-ui/lint-baseline.xml b/navigation/navigation-ui/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/navigation/navigation-ui/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/navigation/navigation-ui/src/main/java/androidx/navigation/ui/NavigationUI.kt b/navigation/navigation-ui/src/main/java/androidx/navigation/ui/NavigationUI.kt
index e7e5f9c..e385658 100644
--- a/navigation/navigation-ui/src/main/java/androidx/navigation/ui/NavigationUI.kt
+++ b/navigation/navigation-ui/src/main/java/androidx/navigation/ui/NavigationUI.kt
@@ -62,7 +62,7 @@
      */
     @JvmStatic
     public fun onNavDestinationSelected(item: MenuItem, navController: NavController): Boolean {
-        val builder = NavOptions.Builder().setLaunchSingleTop(true)
+        val builder = NavOptions.Builder().setLaunchSingleTop(true).setRestoreState(true)
         if (
             navController.currentDestination!!.parent!!.findNode(item.itemId)
             is ActivityNavigator.Destination
@@ -78,7 +78,11 @@
                 .setPopExitAnim(R.animator.nav_default_pop_exit_anim)
         }
         if (item.order and Menu.CATEGORY_SECONDARY == 0) {
-            builder.setPopUpTo(findStartDestination(navController.graph).id, false)
+            builder.setPopUpTo(
+                findStartDestination(navController.graph).id,
+                inclusive = false,
+                saveState = true
+            )
         }
         val options = builder.build()
         return try {
@@ -518,9 +522,9 @@
     @JvmStatic
     @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
     public fun findStartDestination(graph: NavGraph): NavDestination =
-        generateSequence(graph.findNode(graph.startDestination)) {
+        generateSequence(graph.findNode(graph.startDestinationId)) {
             if (it is NavGraph) {
-                it.findNode(it.startDestination)
+                it.findNode(it.startDestinationId)
             } else {
                 null
             }
diff --git a/paging/common/ktx/lint-baseline.xml b/paging/common/ktx/lint-baseline.xml
deleted file mode 100644
index 8794ae8..0000000
--- a/paging/common/ktx/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" version="4.2.0-beta06">
-
-</issues>
diff --git a/paging/common/src/main/kotlin/androidx/paging/DataSource.kt b/paging/common/src/main/kotlin/androidx/paging/DataSource.kt
index 500558c..76ad766 100644
--- a/paging/common/src/main/kotlin/androidx/paging/DataSource.kt
+++ b/paging/common/src/main/kotlin/androidx/paging/DataSource.kt
@@ -93,7 +93,7 @@
  * can differentiate unloaded placeholder items from content that has been paged in.
  *
  * @param Key Unique identifier for item loaded from DataSource. Often an integer to represent
- * position in data set. Note - this is distinct from e.g. Room's `<Value> Value type
+ * position in data set. Note - this is distinct from e.g. Room's `<Value>` Value type
  * loaded by the DataSource.
  */
 public abstract class DataSource<Key : Any, Value : Any>
diff --git a/paging/guava/lint-baseline.xml b/paging/guava/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/paging/guava/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/paging/paging-compose/build.gradle b/paging/paging-compose/build.gradle
index a481bd4..669f393d3 100644
--- a/paging/paging-compose/build.gradle
+++ b/paging/paging-compose/build.gradle
@@ -32,7 +32,7 @@
 
     implementation(libs.kotlinStdlib)
     api(projectOrArtifact(":compose:foundation:foundation"))
-    api(projectOrArtifact(":paging:paging-common"))
+    api("androidx.paging:paging-common:3.0.0")
 
     androidTestImplementation(projectOrArtifact(":compose:ui:ui-test-junit4"))
     androidTestImplementation(project(":compose:test-utils"))
diff --git a/paging/paging-compose/src/main/java/androidx/paging/compose/LazyPagingItems.kt b/paging/paging-compose/src/main/java/androidx/paging/compose/LazyPagingItems.kt
index 0df5445..7a4302d 100644
--- a/paging/paging-compose/src/main/java/androidx/paging/compose/LazyPagingItems.kt
+++ b/paging/paging-compose/src/main/java/androidx/paging/compose/LazyPagingItems.kt
@@ -48,10 +48,12 @@
  * This instance can be used by the [items] and [itemsIndexed] methods inside [LazyListScope] to
  * display data received from the [Flow] of [PagingData].
  *
- * @param flow the [Flow] object which contains a stream of [PagingData] elements.
  * @param T the type of value used by [PagingData].
  */
 public class LazyPagingItems<T : Any> internal constructor(
+    /**
+     * the [Flow] object which contains a stream of [PagingData] elements.
+     */
     private val flow: Flow<PagingData<T>>
 ) {
     private val mainDispatcher = Dispatchers.Main
diff --git a/paging/runtime/ktx/lint-baseline.xml b/paging/runtime/ktx/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/paging/runtime/ktx/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/paging/rxjava2/ktx/lint-baseline.xml b/paging/rxjava2/ktx/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/paging/rxjava2/ktx/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/paging/rxjava2/lint-baseline.xml b/paging/rxjava2/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/paging/rxjava2/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/paging/rxjava3/lint-baseline.xml b/paging/rxjava3/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/paging/rxjava3/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/paging/samples/lint-baseline.xml b/paging/samples/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/paging/samples/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/palette/palette-ktx/lint-baseline.xml b/palette/palette-ktx/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/palette/palette-ktx/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/palette/palette/lint-baseline.xml b/palette/palette/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/palette/palette/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/preference/preference-ktx/lint-baseline.xml b/preference/preference-ktx/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/preference/preference-ktx/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/recyclerview/recyclerview-benchmark/lint-baseline.xml b/recyclerview/recyclerview-benchmark/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/recyclerview/recyclerview-benchmark/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/recyclerview/recyclerview-lint/lint-baseline.xml b/recyclerview/recyclerview-lint/lint-baseline.xml
deleted file mode 100644
index 8794ae8..0000000
--- a/recyclerview/recyclerview-lint/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" version="4.2.0-beta06">
-
-</issues>
diff --git a/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/RecyclerViewLayoutTest.java b/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/RecyclerViewLayoutTest.java
index 7938e7c..1f308a1 100644
--- a/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/RecyclerViewLayoutTest.java
+++ b/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/RecyclerViewLayoutTest.java
@@ -2693,6 +2693,7 @@
         });
     }
 
+    @FlakyTest(bugId = 187331322)
     @Test
     public void smoothScrollBy_durationOf1_completesAsynchronously() throws Throwable {
         smoothScrollBy_completesAsynchronously(1);
diff --git a/resourceinspection/resourceinspection-processor/lint-baseline.xml b/resourceinspection/resourceinspection-processor/lint-baseline.xml
deleted file mode 100644
index 8794ae8..0000000
--- a/resourceinspection/resourceinspection-processor/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" version="4.2.0-beta06">
-
-</issues>
diff --git a/room/benchmark/lint-baseline.xml b/room/benchmark/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/room/benchmark/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/room/compiler-processing-testing/lint-baseline.xml b/room/compiler-processing-testing/lint-baseline.xml
deleted file mode 100644
index 8794ae8..0000000
--- a/room/compiler-processing-testing/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" version="4.2.0-beta06">
-
-</issues>
diff --git a/room/compiler-processing/lint-baseline.xml b/room/compiler-processing/lint-baseline.xml
deleted file mode 100644
index 8794ae8..0000000
--- a/room/compiler-processing/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" version="4.2.0-beta06">
-
-</issues>
diff --git a/room/compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KSTypeExt.kt b/room/compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KSTypeExt.kt
index 075f0ea..7a93f35 100644
--- a/room/compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KSTypeExt.kt
+++ b/room/compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KSTypeExt.kt
@@ -23,6 +23,7 @@
 import com.google.devtools.ksp.processing.Resolver
 import com.google.devtools.ksp.symbol.KSDeclaration
 import com.google.devtools.ksp.symbol.KSType
+import com.google.devtools.ksp.symbol.KSTypeAlias
 import com.google.devtools.ksp.symbol.KSTypeArgument
 import com.google.devtools.ksp.symbol.KSTypeParameter
 import com.google.devtools.ksp.symbol.KSTypeReference
@@ -56,6 +57,9 @@
  */
 @OptIn(KspExperimental::class)
 internal fun KSDeclaration.typeName(resolver: Resolver): TypeName {
+    if (this is KSTypeAlias) {
+        return this.type.typeName(resolver)
+    }
     // if there is no qualified name, it is a resolution error so just return shared instance
     // KSP may improve that later and if not, we can improve it in Room
     // TODO: https://issuetracker.google.com/issues/168639183
diff --git a/room/compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspProcessingEnv.kt b/room/compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspProcessingEnv.kt
index e07b338..febd6cc 100644
--- a/room/compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspProcessingEnv.kt
+++ b/room/compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspProcessingEnv.kt
@@ -28,6 +28,7 @@
 import com.google.devtools.ksp.processing.Resolver
 import com.google.devtools.ksp.symbol.KSClassDeclaration
 import com.google.devtools.ksp.symbol.KSType
+import com.google.devtools.ksp.symbol.KSTypeAlias
 import com.google.devtools.ksp.symbol.KSTypeArgument
 import com.google.devtools.ksp.symbol.KSTypeParameter
 import com.google.devtools.ksp.symbol.KSTypeReference
@@ -169,8 +170,20 @@
      * decision.
      */
     fun wrap(ksType: KSType, allowPrimitives: Boolean): KspType {
-        val qName = ksType.declaration.qualifiedName?.asString()
         val declaration = ksType.declaration
+        if (declaration is KSTypeAlias) {
+            val actual = wrap(
+                ksType = declaration.type.resolve(),
+                allowPrimitives = allowPrimitives && ksType.nullability == Nullability.NOT_NULL
+            )
+            // if this type is nullable, carry it over
+            return if (ksType.nullability == Nullability.NULLABLE) {
+                actual.makeNullable()
+            } else {
+                actual
+            }
+        }
+        val qName = ksType.declaration.qualifiedName?.asString()
         if (declaration is KSTypeParameter) {
             return KspTypeArgumentType(
                 env = this,
diff --git a/room/compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspTypeArgumentType.kt b/room/compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspTypeArgumentType.kt
index 7495463..91c26a8 100644
--- a/room/compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspTypeArgumentType.kt
+++ b/room/compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspTypeArgumentType.kt
@@ -54,7 +54,7 @@
         return this
     }
 
-    override fun extendsBound(): XType? {
+    override fun extendsBound(): XType {
         return _extendsBound
     }
 
diff --git a/room/compiler-processing/src/test/java/androidx/room/compiler/processing/TypeAliasTest.kt b/room/compiler-processing/src/test/java/androidx/room/compiler/processing/TypeAliasTest.kt
new file mode 100644
index 0000000..8b864c2
--- /dev/null
+++ b/room/compiler-processing/src/test/java/androidx/room/compiler/processing/TypeAliasTest.kt
@@ -0,0 +1,86 @@
+/*
+ * 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.room.compiler.processing
+
+import androidx.room.compiler.processing.util.CONTINUATION_CLASS_NAME
+import androidx.room.compiler.processing.util.Source
+import androidx.room.compiler.processing.util.className
+import androidx.room.compiler.processing.util.compileFiles
+import androidx.room.compiler.processing.util.getField
+import androidx.room.compiler.processing.util.getMethod
+import androidx.room.compiler.processing.util.runProcessorTest
+import com.google.common.truth.Truth.assertThat
+import com.squareup.javapoet.ParameterizedTypeName
+import com.squareup.javapoet.TypeName
+import com.squareup.javapoet.WildcardTypeName
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@RunWith(JUnit4::class)
+class TypeAliasTest {
+    @Test
+    fun kotlinTypeAlias() {
+        fun produceSource(pkg: String) = Source.kotlin(
+            "$pkg/Foo.kt",
+            """
+            package $pkg
+            typealias MyLong = Long
+            class Subject {
+                var prop: MyLong = 1L
+                val nullable: MyLong? = null
+                val inGeneric : List<MyLong> = TODO()
+                suspend fun suspendFun() : MyLong = TODO()
+            }
+            """.trimIndent()
+        )
+        val lib = compileFiles(listOf(produceSource("lib")))
+        runProcessorTest(
+            sources = listOf(produceSource("app")),
+            classpath = listOf(lib)
+        ) { invocation ->
+            listOf("lib", "app").forEach { pkg ->
+                val elm = invocation.processingEnv.requireTypeElement("$pkg.Subject")
+                elm.getField("prop").type.let {
+                    assertThat(it.nullability).isEqualTo(XNullability.NONNULL)
+                    assertThat(it.typeName).isEqualTo(TypeName.LONG)
+                }
+                elm.getField("nullable").type.let {
+                    assertThat(it.nullability).isEqualTo(XNullability.NULLABLE)
+                    assertThat(it.typeName).isEqualTo(TypeName.LONG.box())
+                }
+                elm.getField("inGeneric").type.let {
+                    assertThat(it.nullability).isEqualTo(XNullability.NONNULL)
+                    assertThat(it.typeName).isEqualTo(
+                        ParameterizedTypeName.get(
+                            List::class.className(),
+                            TypeName.LONG.box()
+                        )
+                    )
+                }
+                elm.getMethod("suspendFun").parameters.last().type.let {
+                    assertThat(it.typeName).isEqualTo(
+                        ParameterizedTypeName.get(
+                            CONTINUATION_CLASS_NAME,
+                            WildcardTypeName.supertypeOf(TypeName.LONG.box())
+                        )
+                    )
+                }
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/room/compiler/build.gradle b/room/compiler/build.gradle
index f5e3073..26115ea 100644
--- a/room/compiler/build.gradle
+++ b/room/compiler/build.gradle
@@ -242,8 +242,8 @@
 
 tasks.findByName("compileKotlin").dependsOn(generateAntlrTask)
 tasks.findByName("sourceJar").dependsOn(generateAntlrTask)
-tasks.findByName("compileKotlin").dependsOn(":room:room-runtime:jarDebug")
-tasks.findByName("compileKotlin").dependsOn(":sqlite:sqlite:jarDebug")
+tasks.findByName("compileKotlin").dependsOn(":room:room-runtime:jarRelease")
+tasks.findByName("compileKotlin").dependsOn(":sqlite:sqlite:jarRelease")
 
 tasks.withType(KotlinCompile).configureEach {
     kotlinOptions {
diff --git a/room/compiler/lint-baseline.xml b/room/compiler/lint-baseline.xml
deleted file mode 100644
index 8794ae8..0000000
--- a/room/compiler/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" version="4.2.0-beta06">
-
-</issues>
diff --git a/room/compiler/src/test/kotlin/androidx/room/processor/TableEntityProcessorTest.kt b/room/compiler/src/test/kotlin/androidx/room/processor/TableEntityProcessorTest.kt
index 4b2c808..79f15d2 100644
--- a/room/compiler/src/test/kotlin/androidx/room/processor/TableEntityProcessorTest.kt
+++ b/room/compiler/src/test/kotlin/androidx/room/processor/TableEntityProcessorTest.kt
@@ -20,8 +20,10 @@
 import androidx.room.compiler.processing.util.Source
 import androidx.room.compiler.processing.util.compileFiles
 import androidx.room.compiler.processing.util.getSystemClasspathFiles
+import androidx.room.compiler.processing.util.runProcessorTest
 import androidx.room.parser.SQLTypeAffinity
 import androidx.room.processor.ProcessorErrors.RELATION_IN_ENTITY
+import androidx.room.testing.context
 import androidx.room.vo.CallType
 import androidx.room.vo.Field
 import androidx.room.vo.FieldGetter
@@ -2466,4 +2468,29 @@
             }
         }
     }
+
+    @Test
+    fun typeAlias() {
+        val src = Source.kotlin(
+            "Entity.kt",
+            """
+            import androidx.room.*;
+
+            typealias MyLong = Long
+            @Entity(tableName = "par_table")
+            data class Subject(@PrimaryKey @ColumnInfo(name = "my_long") val myLong: MyLong)
+            """.trimIndent()
+        )
+        runProcessorTest(
+            sources = listOf(src)
+        ) { invocation ->
+            val parser = TableEntityProcessor(
+                invocation.context,
+                invocation.processingEnv.requireTypeElement("Subject")
+            )
+            val parsed = parser.process()
+            val field = parsed.primaryKey.fields.first()
+            assertThat(field.typeName).isEqualTo(TypeName.LONG)
+        }
+    }
 }
diff --git a/room/compiler/src/test/kotlin/androidx/room/solver/TypeAdapterStoreTest.kt b/room/compiler/src/test/kotlin/androidx/room/solver/TypeAdapterStoreTest.kt
index 4e2dcb4..8869c08 100644
--- a/room/compiler/src/test/kotlin/androidx/room/solver/TypeAdapterStoreTest.kt
+++ b/room/compiler/src/test/kotlin/androidx/room/solver/TypeAdapterStoreTest.kt
@@ -46,10 +46,12 @@
 import androidx.room.solver.shortcut.binderprovider.GuavaListenableFutureInsertMethodBinderProvider
 import androidx.room.solver.shortcut.binderprovider.RxCallableDeleteOrUpdateMethodBinderProvider
 import androidx.room.solver.shortcut.binderprovider.RxCallableInsertMethodBinderProvider
+import androidx.room.solver.types.BoxedPrimitiveColumnTypeAdapter
 import androidx.room.solver.types.CompositeAdapter
 import androidx.room.solver.types.CompositeTypeConverter
 import androidx.room.solver.types.CustomTypeConverterWrapper
 import androidx.room.solver.types.EnumColumnTypeAdapter
+import androidx.room.solver.types.PrimitiveColumnTypeAdapter
 import androidx.room.solver.types.TypeConverter
 import androidx.room.testing.context
 import com.google.common.truth.Truth.assertThat
@@ -877,6 +879,93 @@
         }
     }
 
+    @Test
+    fun typeAliases() {
+        val source = Source.kotlin(
+            "Foo.kt",
+            """
+            import androidx.room.*
+            typealias MyLongAlias = Long
+            typealias MyNullableLongAlias = Long?
+
+            data class MyClass(val foo:String)
+            typealias MyClassAlias = MyClass
+            typealias MyClassNullableAlias = MyClass?
+
+            object MyConverters {
+                @TypeConverter
+                fun myClassToString(myClass : MyClass): String = TODO()
+                @TypeConverter
+                fun nullableMyClassToString(myClass : MyClass?): String? = TODO()
+            }
+            class Subject {
+                val myLongAlias : MyLongAlias = TODO()
+                val myLongAlias_nullable : MyLongAlias? = TODO()
+                val myNullableLongAlias : MyNullableLongAlias = TODO()
+                val myNullableLongAlias_nullable : MyNullableLongAlias? = TODO()
+                val myClass : MyClass = TODO()
+                val myClassAlias : MyClassAlias = TODO()
+                val myClassAlias_nullable : MyClassAlias? = TODO()
+                val myClassNullableAlias : MyClassNullableAlias = TODO()
+                val myClassNullableAlias_nullable : MyClassNullableAlias = TODO()
+            }
+            """.trimIndent()
+        )
+        runProcessorTest(
+            sources = listOf(source)
+        ) { invocation ->
+            val converters = CustomConverterProcessor(
+                context = invocation.context,
+                element = invocation.processingEnv.requireTypeElement("MyConverters")
+            ).process().map(::CustomTypeConverterWrapper)
+            val typeAdapterStore = TypeAdapterStore.create(
+                context = invocation.context,
+                extras = converters.toTypedArray()
+            )
+            val subject = invocation.processingEnv.requireTypeElement("Subject")
+            val results = subject.getAllFieldsIncludingPrivateSupers().associate { field ->
+                val binder = typeAdapterStore.findStatementValueBinder(
+                    input = field.type,
+                    affinity = null
+                )
+
+                val signature = when (binder) {
+                    null -> null
+                    is PrimitiveColumnTypeAdapter -> "primitive"
+                    is BoxedPrimitiveColumnTypeAdapter -> "boxed"
+                    is CompositeAdapter -> {
+                        when (val converter = binder.intoStatementConverter) {
+                            null -> "composite null"
+                            is CustomTypeConverterWrapper -> converter.custom.methodName
+                            else -> "composite unknown"
+                        }
+                    }
+                    else -> "unknown"
+                }
+                field.name to signature
+            }
+            // see: 187359339. We use nullability for assignments only in KSP
+            val nullableClassAdapter = if (invocation.isKsp) {
+                "nullableMyClassToString"
+            } else {
+                "myClassToString"
+            }
+            assertThat(results).containsExactlyEntriesIn(
+                mapOf(
+                    "myLongAlias" to "primitive",
+                    "myLongAlias_nullable" to "boxed",
+                    "myNullableLongAlias" to "boxed",
+                    "myNullableLongAlias_nullable" to "boxed",
+                    "myClass" to "myClassToString",
+                    "myClassAlias" to "myClassToString",
+                    "myClassAlias_nullable" to nullableClassAdapter,
+                    "myClassNullableAlias" to nullableClassAdapter,
+                    "myClassNullableAlias_nullable" to nullableClassAdapter,
+                )
+            )
+        }
+    }
+
     private fun createIntListToStringBinders(invocation: XTestInvocation): List<TypeConverter> {
         val intType = invocation.processingEnv.requireType(Integer::class)
         val listElement = invocation.processingEnv.requireTypeElement(java.util.List::class)
diff --git a/room/integration-tests/autovaluetestapp/lint-baseline.xml b/room/integration-tests/autovaluetestapp/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/room/integration-tests/autovaluetestapp/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/room/integration-tests/incremental-annotation-processing/lint-baseline.xml b/room/integration-tests/incremental-annotation-processing/lint-baseline.xml
deleted file mode 100644
index 8794ae8..0000000
--- a/room/integration-tests/incremental-annotation-processing/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" version="4.2.0-beta06">
-
-</issues>
diff --git a/room/integration-tests/kotlintestapp/lint-baseline.xml b/room/integration-tests/kotlintestapp/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/room/integration-tests/kotlintestapp/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/room/integration-tests/noappcompattestapp/lint-baseline.xml b/room/integration-tests/noappcompattestapp/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/room/integration-tests/noappcompattestapp/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/room/rxjava3/lint-baseline.xml b/room/rxjava3/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/room/rxjava3/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/savedstate/savedstate-ktx/lint-baseline.xml b/savedstate/savedstate-ktx/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/savedstate/savedstate-ktx/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/savedstate/savedstate/lint-baseline.xml b/savedstate/savedstate/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/savedstate/savedstate/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/security/identity-credential/lint-baseline.xml b/security/identity-credential/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/security/identity-credential/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/security/security-app-authenticator/lint-baseline.xml b/security/security-app-authenticator/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/security/security-app-authenticator/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/security/security-biometric/lint-baseline.xml b/security/security-biometric/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/security/security-biometric/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/security/security-crypto-ktx/lint-baseline.xml b/security/security-crypto-ktx/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/security/security-crypto-ktx/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/settings.gradle b/settings.gradle
index b657ebe..48fb65e 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -384,6 +384,10 @@
 includeProject(":emoji2:emoji2-views", "emoji2/emoji2-views", [BuildType.MAIN])
 includeProject(":emoji2:emoji2-views-helper", "emoji2/emoji2-views-helper", [BuildType.MAIN])
 includeProject(":emoji2:emoji2-benchmark", "emoji2/emoji2-benchmark", [BuildType.MAIN])
+includeProject(":emoji2:integration-tests:init-disabled-macrobenchmark", "emoji2/integration-tests/init-disabled-macrobenchmark", [BuildType.MAIN])
+includeProject(":emoji2:integration-tests:init-disabled-macrobenchmark-target", "emoji2/integration-tests/init-disabled-macrobenchmark-target", [BuildType.MAIN])
+includeProject(":emoji2:integration-tests:init-enabled-macrobenchmark", "emoji2/integration-tests/init-enabled-macrobenchmark", [BuildType.MAIN])
+includeProject(":emoji2:integration-tests:init-enabled-macrobenchmark-target", "emoji2/integration-tests/init-enabled-macrobenchmark-target", [BuildType.MAIN])
 includeProject(":enterprise-feedback", "enterprise/feedback", [BuildType.MAIN])
 includeProject(":enterprise-feedback-testing", "enterprise/feedback/testing", [BuildType.MAIN])
 includeProject(":exifinterface:exifinterface", "exifinterface/exifinterface", [BuildType.MAIN])
@@ -396,6 +400,7 @@
 includeProject(":fragment:fragment-truth", "fragment/fragment-truth", [BuildType.MAIN, BuildType.FLAN])
 includeProject(":fragment:integration-tests:testapp", "fragment/integration-tests/testapp", [BuildType.MAIN, BuildType.FLAN])
 includeProject(":gridlayout:gridlayout", "gridlayout/gridlayout", [BuildType.MAIN])
+includeProject(":health:health-services-client", "health/health-services-client", [BuildType.MAIN])
 includeProject(":heifwriter:heifwriter", "heifwriter/heifwriter", [BuildType.MAIN])
 includeProject(":hilt:hilt-common", "hilt/hilt-common", [BuildType.MAIN])
 includeProject(":hilt:hilt-compiler", "hilt/hilt-compiler", [BuildType.MAIN])
@@ -665,6 +670,7 @@
 includeProject(":internal-testutils-espresso", "testutils/testutils-espresso", [BuildType.MAIN])
 includeProject(":internal-testutils-truth", "testutils/testutils-truth", [BuildType.MAIN, BuildType.FLAN])
 includeProject(":internal-testutils-ktx", "testutils/testutils-ktx", [BuildType.MAIN, BuildType.COMPOSE])
+includeProject(":internal-testutils-macrobenchmark", "testutils/testutils-macrobenchmark", [BuildType.MAIN, BuildType.COMPOSE])
 includeProject(":internal-testutils-navigation", "testutils/testutils-navigation", [BuildType.MAIN, BuildType.COMPOSE, BuildType.FLAN])
 includeProject(":internal-testutils-paging", "testutils/testutils-paging", [BuildType.MAIN, BuildType.COMPOSE])
 includeProject(":internal-testutils-gradle-plugin", "testutils/testutils-gradle-plugin", [BuildType.MAIN, BuildType.FLAN])
diff --git a/slices/benchmark/lint-baseline.xml b/slices/benchmark/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/slices/benchmark/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/slices/builders/ktx/lint-baseline.xml b/slices/builders/ktx/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/slices/builders/ktx/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/slices/core/src/main/res/values-af/strings.xml b/slices/core/src/main/res/values-af/strings.xml
index 1619490..c6a4763 100644
--- a/slices/core/src/main/res/values-af/strings.xml
+++ b/slices/core/src/main/res/values-af/strings.xml
@@ -21,7 +21,7 @@
     <string name="abc_slice_permission_title" msgid="4175332421259324948">"Laat <xliff:g id="APP_0">%1$s</xliff:g> toe om <xliff:g id="APP_2">%2$s</xliff:g>-skyfies te wys?"</string>
     <string name="abc_slice_permission_text_1" msgid="4525743640399572811">"– Dit kan inligting in <xliff:g id="APP">%1$s</xliff:g> lees"</string>
     <string name="abc_slice_permission_text_2" msgid="7323565634860251794">"– Dit kan handelinge binne <xliff:g id="APP">%1$s</xliff:g> uitvoer"</string>
-    <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"Laat <xliff:g id="APP">%1$s</xliff:g> toe om skyfies uit enige program te wys"</string>
+
     <string name="abc_slice_permission_allow" msgid="5024599872061409708">"Laat toe"</string>
     <string name="abc_slice_permission_deny" msgid="3819478292430407705">"Weier"</string>
 </resources>
diff --git a/slices/core/src/main/res/values-am/strings.xml b/slices/core/src/main/res/values-am/strings.xml
index c187e0f..3fc492a 100644
--- a/slices/core/src/main/res/values-am/strings.xml
+++ b/slices/core/src/main/res/values-am/strings.xml
@@ -21,7 +21,7 @@
     <string name="abc_slice_permission_title" msgid="4175332421259324948">"<xliff:g id="APP_0">%1$s</xliff:g> የ<xliff:g id="APP_2">%2$s</xliff:g> ቁራጮችን እንዲያሳይ ይፈቀድለት?"</string>
     <string name="abc_slice_permission_text_1" msgid="4525743640399572811">"- ከ<xliff:g id="APP">%1$s</xliff:g> የመጣ መረጃን ማንበብ ይችላል"</string>
     <string name="abc_slice_permission_text_2" msgid="7323565634860251794">"- በ<xliff:g id="APP">%1$s</xliff:g> ውስጥ እርምጃዎችን መውሰድ ይችላል"</string>
-    <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"<xliff:g id="APP">%1$s</xliff:g> ከማንኛውም መተግበሪያ የመጡ ቁራጮችን እንዲያሳይ ፍቀድለት"</string>
+
     <string name="abc_slice_permission_allow" msgid="5024599872061409708">"ፍቀድ"</string>
     <string name="abc_slice_permission_deny" msgid="3819478292430407705">"ከልክል"</string>
 </resources>
diff --git a/slices/core/src/main/res/values-ar/strings.xml b/slices/core/src/main/res/values-ar/strings.xml
index b5de01c..2d76a8d 100644
--- a/slices/core/src/main/res/values-ar/strings.xml
+++ b/slices/core/src/main/res/values-ar/strings.xml
@@ -21,7 +21,7 @@
     <string name="abc_slice_permission_title" msgid="4175332421259324948">"هل تريد السماح لتطبيق <xliff:g id="APP_0">%1$s</xliff:g> بعرض شرائح <xliff:g id="APP_2">%2$s</xliff:g>؟"</string>
     <string name="abc_slice_permission_text_1" msgid="4525743640399572811">"- إمكانية قراءة المعلومات من <xliff:g id="APP">%1$s</xliff:g>"</string>
     <string name="abc_slice_permission_text_2" msgid="7323565634860251794">"- إمكانية اتخاذ إجراءات داخل <xliff:g id="APP">%1$s</xliff:g>"</string>
-    <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"السماح لتطبيق <xliff:g id="APP">%1$s</xliff:g> بعرض شرائح من أي تطبيق"</string>
+
     <string name="abc_slice_permission_allow" msgid="5024599872061409708">"سماح"</string>
     <string name="abc_slice_permission_deny" msgid="3819478292430407705">"رفض"</string>
 </resources>
diff --git a/slices/core/src/main/res/values-as/strings.xml b/slices/core/src/main/res/values-as/strings.xml
index 95722b9..ea2a877 100644
--- a/slices/core/src/main/res/values-as/strings.xml
+++ b/slices/core/src/main/res/values-as/strings.xml
@@ -21,7 +21,7 @@
     <string name="abc_slice_permission_title" msgid="4175332421259324948">"<xliff:g id="APP_0">%1$s</xliff:g>ক <xliff:g id="APP_2">%2$s</xliff:g>ৰ অংশ দেখুওৱাবলৈ অনুমতি দিবনে?"</string>
     <string name="abc_slice_permission_text_1" msgid="4525743640399572811">"- ই <xliff:g id="APP">%1$s</xliff:g>ৰ তথ্য পঢ়িব পাৰে"</string>
     <string name="abc_slice_permission_text_2" msgid="7323565634860251794">"- ই <xliff:g id="APP">%1$s</xliff:g>ৰ ভিতৰত কাৰ্য কৰিব পাৰে"</string>
-    <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"<xliff:g id="APP">%1$s</xliff:g>ক যিকোনো এপৰ অংশ দেখুওৱাবলৈ অনুমতি দিয়ক"</string>
+
     <string name="abc_slice_permission_allow" msgid="5024599872061409708">"অনুমতি দিয়ক"</string>
     <string name="abc_slice_permission_deny" msgid="3819478292430407705">"অস্বীকাৰ কৰক"</string>
 </resources>
diff --git a/slices/core/src/main/res/values-az/strings.xml b/slices/core/src/main/res/values-az/strings.xml
index ff7be30..2f1bce4 100644
--- a/slices/core/src/main/res/values-az/strings.xml
+++ b/slices/core/src/main/res/values-az/strings.xml
@@ -21,7 +21,7 @@
     <string name="abc_slice_permission_title" msgid="4175332421259324948">"<xliff:g id="APP_0">%1$s</xliff:g> tətbiqinə <xliff:g id="APP_2">%2$s</xliff:g> hissələrini göstərmək üçün icazə verilsin?"</string>
     <string name="abc_slice_permission_text_1" msgid="4525743640399572811">"- <xliff:g id="APP">%1$s</xliff:g> tətbiqindən məlumat oxuya bilər"</string>
     <string name="abc_slice_permission_text_2" msgid="7323565634860251794">"- <xliff:g id="APP">%1$s</xliff:g> daxilində əməliyyatlar edə bilər"</string>
-    <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"<xliff:g id="APP">%1$s</xliff:g> tətbiqinə istənilən tətbiqdən hissə göstərmək icazəsi verin"</string>
+
     <string name="abc_slice_permission_allow" msgid="5024599872061409708">"İcazə verin"</string>
     <string name="abc_slice_permission_deny" msgid="3819478292430407705">"Rədd edin"</string>
 </resources>
diff --git a/slices/core/src/main/res/values-b+sr+Latn/strings.xml b/slices/core/src/main/res/values-b+sr+Latn/strings.xml
index 95c1692c..3e20b9e 100644
--- a/slices/core/src/main/res/values-b+sr+Latn/strings.xml
+++ b/slices/core/src/main/res/values-b+sr+Latn/strings.xml
@@ -21,7 +21,7 @@
     <string name="abc_slice_permission_title" msgid="4175332421259324948">"Želite li da dozvolite aplikaciji <xliff:g id="APP_0">%1$s</xliff:g> da prikazuje isečke iz aplikacije <xliff:g id="APP_2">%2$s</xliff:g>?"</string>
     <string name="abc_slice_permission_text_1" msgid="4525743640399572811">"– Može da čita podatke iz aplikacije <xliff:g id="APP">%1$s</xliff:g>"</string>
     <string name="abc_slice_permission_text_2" msgid="7323565634860251794">"– Može da obavlja radnje u aplikaciji <xliff:g id="APP">%1$s</xliff:g>"</string>
-    <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"Dozvoli aplikaciji <xliff:g id="APP">%1$s</xliff:g> da prikazuje isečke iz bilo koje aplikacije"</string>
+
     <string name="abc_slice_permission_allow" msgid="5024599872061409708">"Dozvoli"</string>
     <string name="abc_slice_permission_deny" msgid="3819478292430407705">"Odbij"</string>
 </resources>
diff --git a/slices/core/src/main/res/values-be/strings.xml b/slices/core/src/main/res/values-be/strings.xml
index 5fadc3f..84cd4a22 100644
--- a/slices/core/src/main/res/values-be/strings.xml
+++ b/slices/core/src/main/res/values-be/strings.xml
@@ -21,7 +21,7 @@
     <string name="abc_slice_permission_title" msgid="4175332421259324948">"Дазволіць праграме <xliff:g id="APP_0">%1$s</xliff:g> паказваць фрагменты праграмы <xliff:g id="APP_2">%2$s</xliff:g>?"</string>
     <string name="abc_slice_permission_text_1" msgid="4525743640399572811">"- Можа счытваць інфармацыю з праграмы <xliff:g id="APP">%1$s</xliff:g>"</string>
     <string name="abc_slice_permission_text_2" msgid="7323565634860251794">"- Можа выконваць дзеянні ў праграме <xliff:g id="APP">%1$s</xliff:g>"</string>
-    <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"Дазволіць праграме <xliff:g id="APP">%1$s</xliff:g> паказваць фрагменты іншых праграм"</string>
+
     <string name="abc_slice_permission_allow" msgid="5024599872061409708">"Дазволіць"</string>
     <string name="abc_slice_permission_deny" msgid="3819478292430407705">"Адмовіць"</string>
 </resources>
diff --git a/slices/core/src/main/res/values-bg/strings.xml b/slices/core/src/main/res/values-bg/strings.xml
index cb6f067..dcd2606 100644
--- a/slices/core/src/main/res/values-bg/strings.xml
+++ b/slices/core/src/main/res/values-bg/strings.xml
@@ -21,7 +21,7 @@
     <string name="abc_slice_permission_title" msgid="4175332421259324948">"Ще разрешите ли на <xliff:g id="APP_0">%1$s</xliff:g> да показва части от <xliff:g id="APP_2">%2$s</xliff:g>?"</string>
     <string name="abc_slice_permission_text_1" msgid="4525743640399572811">"– Може да чете информация от <xliff:g id="APP">%1$s</xliff:g>"</string>
     <string name="abc_slice_permission_text_2" msgid="7323565634860251794">"– Може да предприема действия в/ъв <xliff:g id="APP">%1$s</xliff:g>"</string>
-    <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"Разрешаване на <xliff:g id="APP">%1$s</xliff:g> да показва части от което и да е приложение"</string>
+
     <string name="abc_slice_permission_allow" msgid="5024599872061409708">"Разрешаване"</string>
     <string name="abc_slice_permission_deny" msgid="3819478292430407705">"Отказ"</string>
 </resources>
diff --git a/slices/core/src/main/res/values-bn/strings.xml b/slices/core/src/main/res/values-bn/strings.xml
index cfa569d..4b46f9b 100644
--- a/slices/core/src/main/res/values-bn/strings.xml
+++ b/slices/core/src/main/res/values-bn/strings.xml
@@ -21,7 +21,7 @@
     <string name="abc_slice_permission_title" msgid="4175332421259324948">"<xliff:g id="APP_0">%1$s</xliff:g> অ্যাপটিকে <xliff:g id="APP_2">%2$s</xliff:g> এর অংশ দেখানোর অনুমতি দেবেন?"</string>
     <string name="abc_slice_permission_text_1" msgid="4525743640399572811">"- এটি <xliff:g id="APP">%1$s</xliff:g> এর তথ্য অ্যাক্সেস করতে পারবে"</string>
     <string name="abc_slice_permission_text_2" msgid="7323565634860251794">"- এটি <xliff:g id="APP">%1$s</xliff:g> এর মধ্যে কাজ করতে পারবে"</string>
-    <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"<xliff:g id="APP">%1$s</xliff:g> অ্যাপটিকে যেকোনও অ্যাপের অংশ দেখাতে দিন"</string>
+
     <string name="abc_slice_permission_allow" msgid="5024599872061409708">"অনুমতি দিন"</string>
     <string name="abc_slice_permission_deny" msgid="3819478292430407705">"খারিজ করুন"</string>
 </resources>
diff --git a/slices/core/src/main/res/values-bs/strings.xml b/slices/core/src/main/res/values-bs/strings.xml
index 651e4d6..b203a79 100644
--- a/slices/core/src/main/res/values-bs/strings.xml
+++ b/slices/core/src/main/res/values-bs/strings.xml
@@ -21,7 +21,7 @@
     <string name="abc_slice_permission_title" msgid="4175332421259324948">"Dozvoliti aplikaciji <xliff:g id="APP_0">%1$s</xliff:g> prikazivanje isječaka aplikacije <xliff:g id="APP_2">%2$s</xliff:g>?"</string>
     <string name="abc_slice_permission_text_1" msgid="4525743640399572811">"- Može čitati informacije iz aplikacije <xliff:g id="APP">%1$s</xliff:g>"</string>
     <string name="abc_slice_permission_text_2" msgid="7323565634860251794">"- Može poduzeti radnje u aplikaciji <xliff:g id="APP">%1$s</xliff:g>"</string>
-    <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"Dozvoli aplikaciji <xliff:g id="APP">%1$s</xliff:g> prikazivanje isječaka iz svake aplikacije"</string>
+
     <string name="abc_slice_permission_allow" msgid="5024599872061409708">"Dozvoli"</string>
     <string name="abc_slice_permission_deny" msgid="3819478292430407705">"Odbij"</string>
 </resources>
diff --git a/slices/core/src/main/res/values-ca/strings.xml b/slices/core/src/main/res/values-ca/strings.xml
index 0c953cf..ac93488 100644
--- a/slices/core/src/main/res/values-ca/strings.xml
+++ b/slices/core/src/main/res/values-ca/strings.xml
@@ -21,7 +21,7 @@
     <string name="abc_slice_permission_title" msgid="4175332421259324948">"Vols permetre que <xliff:g id="APP_0">%1$s</xliff:g> mostri porcions de l\'aplicació <xliff:g id="APP_2">%2$s</xliff:g>?"</string>
     <string name="abc_slice_permission_text_1" msgid="4525743640399572811">"- Pot llegir informació de l\'aplicació <xliff:g id="APP">%1$s</xliff:g>"</string>
     <string name="abc_slice_permission_text_2" msgid="7323565634860251794">"- Pot dur a terme accions dins de l\'aplicació <xliff:g id="APP">%1$s</xliff:g>"</string>
-    <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"Permet que <xliff:g id="APP">%1$s</xliff:g> mostri porcions de qualsevol aplicació"</string>
+
     <string name="abc_slice_permission_allow" msgid="5024599872061409708">"Permet"</string>
     <string name="abc_slice_permission_deny" msgid="3819478292430407705">"Denega"</string>
 </resources>
diff --git a/slices/core/src/main/res/values-cs/strings.xml b/slices/core/src/main/res/values-cs/strings.xml
index 6db0934..da1053b 100644
--- a/slices/core/src/main/res/values-cs/strings.xml
+++ b/slices/core/src/main/res/values-cs/strings.xml
@@ -21,7 +21,7 @@
     <string name="abc_slice_permission_title" msgid="4175332421259324948">"Povolit aplikaci <xliff:g id="APP_0">%1$s</xliff:g> zobrazovat ukázky z aplikace <xliff:g id="APP_2">%2$s</xliff:g>?"</string>
     <string name="abc_slice_permission_text_1" msgid="4525743640399572811">"– Může číst informace z aplikace <xliff:g id="APP">%1$s</xliff:g>"</string>
     <string name="abc_slice_permission_text_2" msgid="7323565634860251794">"– Může provádět akce v aplikaci <xliff:g id="APP">%1$s</xliff:g>"</string>
-    <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"Povolit aplikaci <xliff:g id="APP">%1$s</xliff:g> zobrazovat ukázky z libovolné aplikace"</string>
+
     <string name="abc_slice_permission_allow" msgid="5024599872061409708">"Povolit"</string>
     <string name="abc_slice_permission_deny" msgid="3819478292430407705">"Zamítnout"</string>
 </resources>
diff --git a/slices/core/src/main/res/values-da/strings.xml b/slices/core/src/main/res/values-da/strings.xml
index 70f1a22..67a7ed0 100644
--- a/slices/core/src/main/res/values-da/strings.xml
+++ b/slices/core/src/main/res/values-da/strings.xml
@@ -21,7 +21,7 @@
     <string name="abc_slice_permission_title" msgid="4175332421259324948">"Vil du give <xliff:g id="APP_0">%1$s</xliff:g> tilladelse til at vise eksempler fra <xliff:g id="APP_2">%2$s</xliff:g>?"</string>
     <string name="abc_slice_permission_text_1" msgid="4525743640399572811">"- Den kan læse oplysninger fra <xliff:g id="APP">%1$s</xliff:g>"</string>
     <string name="abc_slice_permission_text_2" msgid="7323565634860251794">"- Den kan foretage handlinger i <xliff:g id="APP">%1$s</xliff:g>"</string>
-    <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"Tillad, at <xliff:g id="APP">%1$s</xliff:g> viser eksempler fra enhver app"</string>
+
     <string name="abc_slice_permission_allow" msgid="5024599872061409708">"Tillad"</string>
     <string name="abc_slice_permission_deny" msgid="3819478292430407705">"Afvis"</string>
 </resources>
diff --git a/slices/core/src/main/res/values-de/strings.xml b/slices/core/src/main/res/values-de/strings.xml
index 89ce98e..419fa20 100644
--- a/slices/core/src/main/res/values-de/strings.xml
+++ b/slices/core/src/main/res/values-de/strings.xml
@@ -21,7 +21,7 @@
     <string name="abc_slice_permission_title" msgid="4175332421259324948">"<xliff:g id="APP_0">%1$s</xliff:g> erlauben, Teile von <xliff:g id="APP_2">%2$s</xliff:g> anzuzeigen?"</string>
     <string name="abc_slice_permission_text_1" msgid="4525743640399572811">"– Darf Informationen aus <xliff:g id="APP">%1$s</xliff:g> lesen"</string>
     <string name="abc_slice_permission_text_2" msgid="7323565634860251794">"– Darf Aktionen in <xliff:g id="APP">%1$s</xliff:g> ausführen"</string>
-    <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"<xliff:g id="APP">%1$s</xliff:g> darf Teile aus jeder beliebigen App anzeigen"</string>
+
     <string name="abc_slice_permission_allow" msgid="5024599872061409708">"Zulassen"</string>
     <string name="abc_slice_permission_deny" msgid="3819478292430407705">"Ablehnen"</string>
 </resources>
diff --git a/slices/core/src/main/res/values-el/strings.xml b/slices/core/src/main/res/values-el/strings.xml
index ed0b624..898d2ea 100644
--- a/slices/core/src/main/res/values-el/strings.xml
+++ b/slices/core/src/main/res/values-el/strings.xml
@@ -21,7 +21,7 @@
     <string name="abc_slice_permission_title" msgid="4175332421259324948">"Να επιτρέπεται στην εφαρμογή <xliff:g id="APP_0">%1$s</xliff:g> να εμφανίζει τμήματα της εφαρμογής <xliff:g id="APP_2">%2$s</xliff:g>;"</string>
     <string name="abc_slice_permission_text_1" msgid="4525743640399572811">"- Μπορεί να διαβάζει πληροφορίες από την εφαρμογή <xliff:g id="APP">%1$s</xliff:g>"</string>
     <string name="abc_slice_permission_text_2" msgid="7323565634860251794">"- Μπορεί να εκτελεί ενέργειες εντός της εφαρμογής <xliff:g id="APP">%1$s</xliff:g>"</string>
-    <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"Να επιτρέπεται στην εφαρμογή <xliff:g id="APP">%1$s</xliff:g> να εμφανίζει τμήματα από οποιαδήποτε εφαρμογή"</string>
+
     <string name="abc_slice_permission_allow" msgid="5024599872061409708">"Να επιτρέπεται"</string>
     <string name="abc_slice_permission_deny" msgid="3819478292430407705">"Να μην επιτρέπεται"</string>
 </resources>
diff --git a/slices/core/src/main/res/values-en-rAU/strings.xml b/slices/core/src/main/res/values-en-rAU/strings.xml
index d47c9ec..b4c1f11 100644
--- a/slices/core/src/main/res/values-en-rAU/strings.xml
+++ b/slices/core/src/main/res/values-en-rAU/strings.xml
@@ -21,7 +21,7 @@
     <string name="abc_slice_permission_title" msgid="4175332421259324948">"Allow <xliff:g id="APP_0">%1$s</xliff:g> to show <xliff:g id="APP_2">%2$s</xliff:g> slices?"</string>
     <string name="abc_slice_permission_text_1" msgid="4525743640399572811">"– It can read information from <xliff:g id="APP">%1$s</xliff:g>"</string>
     <string name="abc_slice_permission_text_2" msgid="7323565634860251794">"– It can take actions inside <xliff:g id="APP">%1$s</xliff:g>"</string>
-    <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"Allow <xliff:g id="APP">%1$s</xliff:g> to show slices from any app"</string>
+
     <string name="abc_slice_permission_allow" msgid="5024599872061409708">"Allow"</string>
     <string name="abc_slice_permission_deny" msgid="3819478292430407705">"Deny"</string>
 </resources>
diff --git a/slices/core/src/main/res/values-en-rCA/strings.xml b/slices/core/src/main/res/values-en-rCA/strings.xml
index d47c9ec..b4c1f11 100644
--- a/slices/core/src/main/res/values-en-rCA/strings.xml
+++ b/slices/core/src/main/res/values-en-rCA/strings.xml
@@ -21,7 +21,7 @@
     <string name="abc_slice_permission_title" msgid="4175332421259324948">"Allow <xliff:g id="APP_0">%1$s</xliff:g> to show <xliff:g id="APP_2">%2$s</xliff:g> slices?"</string>
     <string name="abc_slice_permission_text_1" msgid="4525743640399572811">"– It can read information from <xliff:g id="APP">%1$s</xliff:g>"</string>
     <string name="abc_slice_permission_text_2" msgid="7323565634860251794">"– It can take actions inside <xliff:g id="APP">%1$s</xliff:g>"</string>
-    <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"Allow <xliff:g id="APP">%1$s</xliff:g> to show slices from any app"</string>
+
     <string name="abc_slice_permission_allow" msgid="5024599872061409708">"Allow"</string>
     <string name="abc_slice_permission_deny" msgid="3819478292430407705">"Deny"</string>
 </resources>
diff --git a/slices/core/src/main/res/values-en-rGB/strings.xml b/slices/core/src/main/res/values-en-rGB/strings.xml
index d47c9ec..b4c1f11 100644
--- a/slices/core/src/main/res/values-en-rGB/strings.xml
+++ b/slices/core/src/main/res/values-en-rGB/strings.xml
@@ -21,7 +21,7 @@
     <string name="abc_slice_permission_title" msgid="4175332421259324948">"Allow <xliff:g id="APP_0">%1$s</xliff:g> to show <xliff:g id="APP_2">%2$s</xliff:g> slices?"</string>
     <string name="abc_slice_permission_text_1" msgid="4525743640399572811">"– It can read information from <xliff:g id="APP">%1$s</xliff:g>"</string>
     <string name="abc_slice_permission_text_2" msgid="7323565634860251794">"– It can take actions inside <xliff:g id="APP">%1$s</xliff:g>"</string>
-    <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"Allow <xliff:g id="APP">%1$s</xliff:g> to show slices from any app"</string>
+
     <string name="abc_slice_permission_allow" msgid="5024599872061409708">"Allow"</string>
     <string name="abc_slice_permission_deny" msgid="3819478292430407705">"Deny"</string>
 </resources>
diff --git a/slices/core/src/main/res/values-en-rIN/strings.xml b/slices/core/src/main/res/values-en-rIN/strings.xml
index d47c9ec..b4c1f11 100644
--- a/slices/core/src/main/res/values-en-rIN/strings.xml
+++ b/slices/core/src/main/res/values-en-rIN/strings.xml
@@ -21,7 +21,7 @@
     <string name="abc_slice_permission_title" msgid="4175332421259324948">"Allow <xliff:g id="APP_0">%1$s</xliff:g> to show <xliff:g id="APP_2">%2$s</xliff:g> slices?"</string>
     <string name="abc_slice_permission_text_1" msgid="4525743640399572811">"– It can read information from <xliff:g id="APP">%1$s</xliff:g>"</string>
     <string name="abc_slice_permission_text_2" msgid="7323565634860251794">"– It can take actions inside <xliff:g id="APP">%1$s</xliff:g>"</string>
-    <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"Allow <xliff:g id="APP">%1$s</xliff:g> to show slices from any app"</string>
+
     <string name="abc_slice_permission_allow" msgid="5024599872061409708">"Allow"</string>
     <string name="abc_slice_permission_deny" msgid="3819478292430407705">"Deny"</string>
 </resources>
diff --git a/slices/core/src/main/res/values-en-rXC/strings.xml b/slices/core/src/main/res/values-en-rXC/strings.xml
index 5f82056..efb1d5f 100644
--- a/slices/core/src/main/res/values-en-rXC/strings.xml
+++ b/slices/core/src/main/res/values-en-rXC/strings.xml
@@ -21,7 +21,7 @@
     <string name="abc_slice_permission_title" msgid="4175332421259324948">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‎‎‏‏‏‏‏‎‎‎‏‏‏‎‎‎‎‏‎‏‎‏‏‎‏‏‏‎‎‎‏‏‏‎‎‎‏‎‏‎‏‎‏‎‏‏‎‏‏‏‎‎‎‎‏‎‏‎‎‎Allow ‎‏‎‎‏‏‎<xliff:g id="APP_0">%1$s</xliff:g>‎‏‎‎‏‏‏‎ to show ‎‏‎‎‏‏‎<xliff:g id="APP_2">%2$s</xliff:g>‎‏‎‎‏‏‏‎ slices?‎‏‎‎‏‎"</string>
     <string name="abc_slice_permission_text_1" msgid="4525743640399572811">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‏‏‎‏‏‎‎‏‏‏‎‏‎‏‎‏‎‏‏‏‏‎‏‏‏‎‎‏‏‎‎‏‏‏‎‎‏‏‏‏‏‏‏‏‏‏‎‏‏‏‏‎‏‎‎‏‎‏‏‎- It can read information from ‎‏‎‎‏‏‎<xliff:g id="APP">%1$s</xliff:g>‎‏‎‎‏‏‏‎‎‏‎‎‏‎"</string>
     <string name="abc_slice_permission_text_2" msgid="7323565634860251794">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‎‏‎‏‏‎‏‎‎‎‏‎‏‎‎‎‏‎‎‎‎‎‏‎‎‏‎‎‏‎‎‏‏‎‏‏‎‎‎‏‏‎‏‎‎‏‏‏‏‎‏‎‏‎‎‏‎‎‏‎‎- It can take actions inside ‎‏‎‎‏‏‎<xliff:g id="APP">%1$s</xliff:g>‎‏‎‎‏‏‏‎‎‏‎‎‏‎"</string>
-    <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‏‏‏‎‎‎‎‏‏‏‏‎‏‎‏‏‎‏‏‏‎‎‎‎‏‎‏‏‎‎‎‎‏‏‎‏‏‏‏‎‏‏‎‏‎‏‎‏‏‏‏‏‏‏‏‎‎‏‏‎Allow ‎‏‎‎‏‏‎<xliff:g id="APP">%1$s</xliff:g>‎‏‎‎‏‏‏‎ to show slices from any app‎‏‎‎‏‎"</string>
+
     <string name="abc_slice_permission_allow" msgid="5024599872061409708">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‎‏‎‏‏‎‏‏‏‎‏‎‏‏‏‏‎‏‏‎‏‏‏‏‎‏‏‎‎‏‎‏‎‎‎‎‏‎‏‏‎‎‎‏‏‎‎‎‏‎‎‏‏‎‏‎‏‏‎‎‎Allow‎‏‎‎‏‎"</string>
     <string name="abc_slice_permission_deny" msgid="3819478292430407705">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‏‎‏‎‎‎‎‎‎‎‏‏‎‎‎‎‎‏‏‎‏‎‎‎‏‎‏‎‎‏‏‏‏‏‏‏‎‏‎‎‎‎‏‎‏‎‎‏‎‎‎‎‎‎‏‏‎‎‏‎Deny‎‏‎‎‏‎"</string>
 </resources>
diff --git a/slices/core/src/main/res/values-es-rUS/strings.xml b/slices/core/src/main/res/values-es-rUS/strings.xml
index 9db16c5..31746f0 100644
--- a/slices/core/src/main/res/values-es-rUS/strings.xml
+++ b/slices/core/src/main/res/values-es-rUS/strings.xml
@@ -21,7 +21,7 @@
     <string name="abc_slice_permission_title" msgid="4175332421259324948">"¿Permitir que <xliff:g id="APP_0">%1$s</xliff:g> muestre fragmentos de <xliff:g id="APP_2">%2$s</xliff:g>?"</string>
     <string name="abc_slice_permission_text_1" msgid="4525743640399572811">"- Puede leer información de <xliff:g id="APP">%1$s</xliff:g>"</string>
     <string name="abc_slice_permission_text_2" msgid="7323565634860251794">"- Puede realizar acciones en <xliff:g id="APP">%1$s</xliff:g>"</string>
-    <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"Permitir que <xliff:g id="APP">%1$s</xliff:g> muestre fragmentos de cualquier app"</string>
+
     <string name="abc_slice_permission_allow" msgid="5024599872061409708">"Permitir"</string>
     <string name="abc_slice_permission_deny" msgid="3819478292430407705">"Rechazar"</string>
 </resources>
diff --git a/slices/core/src/main/res/values-es/strings.xml b/slices/core/src/main/res/values-es/strings.xml
index 44283aa..3a65289 100644
--- a/slices/core/src/main/res/values-es/strings.xml
+++ b/slices/core/src/main/res/values-es/strings.xml
@@ -21,7 +21,7 @@
     <string name="abc_slice_permission_title" msgid="4175332421259324948">"¿Permitir que <xliff:g id="APP_0">%1$s</xliff:g> muestre fragmentos de <xliff:g id="APP_2">%2$s</xliff:g>?"</string>
     <string name="abc_slice_permission_text_1" msgid="4525743640399572811">"- Puede leer información de <xliff:g id="APP">%1$s</xliff:g>"</string>
     <string name="abc_slice_permission_text_2" msgid="7323565634860251794">"- Puede realizar acciones en <xliff:g id="APP">%1$s</xliff:g>"</string>
-    <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"Permitir que <xliff:g id="APP">%1$s</xliff:g> muestre fragmentos de cualquier aplicación"</string>
+
     <string name="abc_slice_permission_allow" msgid="5024599872061409708">"Permitir"</string>
     <string name="abc_slice_permission_deny" msgid="3819478292430407705">"Denegar"</string>
 </resources>
diff --git a/slices/core/src/main/res/values-et/strings.xml b/slices/core/src/main/res/values-et/strings.xml
index 6c45d68..5462dd7 100644
--- a/slices/core/src/main/res/values-et/strings.xml
+++ b/slices/core/src/main/res/values-et/strings.xml
@@ -21,7 +21,7 @@
     <string name="abc_slice_permission_title" msgid="4175332421259324948">"Kas lubada rakendusel <xliff:g id="APP_0">%1$s</xliff:g> näidata rakenduse <xliff:g id="APP_2">%2$s</xliff:g> lõike?"</string>
     <string name="abc_slice_permission_text_1" msgid="4525743640399572811">"- See saab lugeda teavet rakendusest <xliff:g id="APP">%1$s</xliff:g>"</string>
     <string name="abc_slice_permission_text_2" msgid="7323565634860251794">"- See saab rakenduses <xliff:g id="APP">%1$s</xliff:g> toiminguid teha"</string>
-    <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"Luba rakendus <xliff:g id="APP">%1$s</xliff:g>, et kuvada lõike mis tahes rakendusest"</string>
+
     <string name="abc_slice_permission_allow" msgid="5024599872061409708">"Lubamine"</string>
     <string name="abc_slice_permission_deny" msgid="3819478292430407705">"Keelamine"</string>
 </resources>
diff --git a/slices/core/src/main/res/values-eu/strings.xml b/slices/core/src/main/res/values-eu/strings.xml
index e608918..8776fec 100644
--- a/slices/core/src/main/res/values-eu/strings.xml
+++ b/slices/core/src/main/res/values-eu/strings.xml
@@ -21,7 +21,7 @@
     <string name="abc_slice_permission_title" msgid="4175332421259324948">"<xliff:g id="APP_2">%2$s</xliff:g> aplikazioaren zatiak erakusteko baimena eman nahi diozu <xliff:g id="APP_0">%1$s</xliff:g> aplikazioari?"</string>
     <string name="abc_slice_permission_text_1" msgid="4525743640399572811">"- <xliff:g id="APP">%1$s</xliff:g> aplikazioaren informazioa irakur dezake."</string>
     <string name="abc_slice_permission_text_2" msgid="7323565634860251794">"- <xliff:g id="APP">%1$s</xliff:g> aplikazioan ekintzak gauza ditzake."</string>
-    <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"Eman aplikazio guztien zatiak erakusteko baimena <xliff:g id="APP">%1$s</xliff:g> aplikazioari"</string>
+
     <string name="abc_slice_permission_allow" msgid="5024599872061409708">"Baimendu"</string>
     <string name="abc_slice_permission_deny" msgid="3819478292430407705">"Ukatu"</string>
 </resources>
diff --git a/slices/core/src/main/res/values-fa/strings.xml b/slices/core/src/main/res/values-fa/strings.xml
index 38a286d..d5e0416 100644
--- a/slices/core/src/main/res/values-fa/strings.xml
+++ b/slices/core/src/main/res/values-fa/strings.xml
@@ -21,7 +21,7 @@
     <string name="abc_slice_permission_title" msgid="4175332421259324948">"به <xliff:g id="APP_0">%1$s</xliff:g> اجازه داده شود تکه‌های <xliff:g id="APP_2">%2$s</xliff:g> را نشان دهد؟"</string>
     <string name="abc_slice_permission_text_1" msgid="4525743640399572811">"- می‌تواند اطلاعات <xliff:g id="APP">%1$s</xliff:g> را بخواند"</string>
     <string name="abc_slice_permission_text_2" msgid="7323565634860251794">"- می‌تواند در <xliff:g id="APP">%1$s</xliff:g> اقدام انجام دهد"</string>
-    <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"به <xliff:g id="APP">%1$s</xliff:g> اجازه داده شود تکه‌هایی از برنامه‌ها نشان دهد"</string>
+
     <string name="abc_slice_permission_allow" msgid="5024599872061409708">"مجاز بودن"</string>
     <string name="abc_slice_permission_deny" msgid="3819478292430407705">"مجاز نبودن"</string>
 </resources>
diff --git a/slices/core/src/main/res/values-fi/strings.xml b/slices/core/src/main/res/values-fi/strings.xml
index 1cc8122..d682f1a 100644
--- a/slices/core/src/main/res/values-fi/strings.xml
+++ b/slices/core/src/main/res/values-fi/strings.xml
@@ -21,7 +21,7 @@
     <string name="abc_slice_permission_title" msgid="4175332421259324948">"Saako <xliff:g id="APP_0">%1$s</xliff:g> näyttää osia sovelluksesta <xliff:g id="APP_2">%2$s</xliff:g>?"</string>
     <string name="abc_slice_permission_text_1" msgid="4525743640399572811">"– Se voi lukea tietoja sovelluksesta <xliff:g id="APP">%1$s</xliff:g>."</string>
     <string name="abc_slice_permission_text_2" msgid="7323565634860251794">"– Se voi suorittaa toimintoja sovelluksessa <xliff:g id="APP">%1$s</xliff:g>."</string>
-    <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"Salli sovelluksen <xliff:g id="APP">%1$s</xliff:g> näyttää osia mistä tahansa sovelluksesta"</string>
+
     <string name="abc_slice_permission_allow" msgid="5024599872061409708">"Salli"</string>
     <string name="abc_slice_permission_deny" msgid="3819478292430407705">"Estä"</string>
 </resources>
diff --git a/slices/core/src/main/res/values-fr-rCA/strings.xml b/slices/core/src/main/res/values-fr-rCA/strings.xml
index d92e649..a0e55d3 100644
--- a/slices/core/src/main/res/values-fr-rCA/strings.xml
+++ b/slices/core/src/main/res/values-fr-rCA/strings.xml
@@ -21,7 +21,7 @@
     <string name="abc_slice_permission_title" msgid="4175332421259324948">"Autoriser <xliff:g id="APP_0">%1$s</xliff:g> à afficher <xliff:g id="APP_2">%2$s</xliff:g> tranches?"</string>
     <string name="abc_slice_permission_text_1" msgid="4525743640399572811">"- Il peut lire de l\'information de <xliff:g id="APP">%1$s</xliff:g>"</string>
     <string name="abc_slice_permission_text_2" msgid="7323565634860251794">"- Il peut effectuer des actions dans <xliff:g id="APP">%1$s</xliff:g>"</string>
-    <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"Autoriser <xliff:g id="APP">%1$s</xliff:g> à afficher des tranches de n\'importe quelle application"</string>
+
     <string name="abc_slice_permission_allow" msgid="5024599872061409708">"Autoriser"</string>
     <string name="abc_slice_permission_deny" msgid="3819478292430407705">"Refuser"</string>
 </resources>
diff --git a/slices/core/src/main/res/values-fr/strings.xml b/slices/core/src/main/res/values-fr/strings.xml
index 9edcb27..65f06bc 100644
--- a/slices/core/src/main/res/values-fr/strings.xml
+++ b/slices/core/src/main/res/values-fr/strings.xml
@@ -21,7 +21,7 @@
     <string name="abc_slice_permission_title" msgid="4175332421259324948">"Autoriser <xliff:g id="APP_0">%1$s</xliff:g> à afficher des éléments de <xliff:g id="APP_2">%2$s</xliff:g> ?"</string>
     <string name="abc_slice_permission_text_1" msgid="4525743640399572811">"- Accès aux informations de <xliff:g id="APP">%1$s</xliff:g>"</string>
     <string name="abc_slice_permission_text_2" msgid="7323565634860251794">"- Capacité d\'action dans <xliff:g id="APP">%1$s</xliff:g>"</string>
-    <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"Autoriser <xliff:g id="APP">%1$s</xliff:g> à afficher des éléments de n\'importe quelle application"</string>
+
     <string name="abc_slice_permission_allow" msgid="5024599872061409708">"Autoriser"</string>
     <string name="abc_slice_permission_deny" msgid="3819478292430407705">"Refuser"</string>
 </resources>
diff --git a/slices/core/src/main/res/values-gl/strings.xml b/slices/core/src/main/res/values-gl/strings.xml
index 617e5fc..50befe5 100644
--- a/slices/core/src/main/res/values-gl/strings.xml
+++ b/slices/core/src/main/res/values-gl/strings.xml
@@ -21,7 +21,7 @@
     <string name="abc_slice_permission_title" msgid="4175332421259324948">"Queres permitir que <xliff:g id="APP_0">%1$s</xliff:g> mostre fragmentos de aplicación de <xliff:g id="APP_2">%2$s</xliff:g>?"</string>
     <string name="abc_slice_permission_text_1" msgid="4525743640399572811">"- Pode ler información da aplicación <xliff:g id="APP">%1$s</xliff:g>"</string>
     <string name="abc_slice_permission_text_2" msgid="7323565634860251794">"- Pode levar a cabo accións dentro da aplicación <xliff:g id="APP">%1$s</xliff:g>"</string>
-    <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"Permitir que <xliff:g id="APP">%1$s</xliff:g> mostre fragmentos de calquera aplicación"</string>
+
     <string name="abc_slice_permission_allow" msgid="5024599872061409708">"Permitir"</string>
     <string name="abc_slice_permission_deny" msgid="3819478292430407705">"Denegar"</string>
 </resources>
diff --git a/slices/core/src/main/res/values-gu/strings.xml b/slices/core/src/main/res/values-gu/strings.xml
index 4e8fcb8..804b774 100644
--- a/slices/core/src/main/res/values-gu/strings.xml
+++ b/slices/core/src/main/res/values-gu/strings.xml
@@ -21,7 +21,7 @@
     <string name="abc_slice_permission_title" msgid="4175332421259324948">"<xliff:g id="APP_0">%1$s</xliff:g>ને <xliff:g id="APP_2">%2$s</xliff:g> સ્લાઇસ બતાવવાની મંજૂરી આપીએ?"</string>
     <string name="abc_slice_permission_text_1" msgid="4525743640399572811">"- તે <xliff:g id="APP">%1$s</xliff:g>માંથી માહિતી વાંચી શકે છે"</string>
     <string name="abc_slice_permission_text_2" msgid="7323565634860251794">"- તે <xliff:g id="APP">%1$s</xliff:g>ની અંદર ક્રિયાઓ કરી શકે છે"</string>
-    <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"<xliff:g id="APP">%1$s</xliff:g>ને કોઈપણ ઍપના સ્લાઇસ બતાવવાની મંજૂરી આપો"</string>
+
     <string name="abc_slice_permission_allow" msgid="5024599872061409708">"મંજૂરી આપો"</string>
     <string name="abc_slice_permission_deny" msgid="3819478292430407705">"નકારો"</string>
 </resources>
diff --git a/slices/core/src/main/res/values-hi/strings.xml b/slices/core/src/main/res/values-hi/strings.xml
index 23e4d3f..9dfe2b0 100644
--- a/slices/core/src/main/res/values-hi/strings.xml
+++ b/slices/core/src/main/res/values-hi/strings.xml
@@ -21,7 +21,7 @@
     <string name="abc_slice_permission_title" msgid="4175332421259324948">"<xliff:g id="APP_0">%1$s</xliff:g> को <xliff:g id="APP_2">%2$s</xliff:g> के हिस्से (स्लाइस) दिखाने की मंज़ूरी दें?"</string>
     <string name="abc_slice_permission_text_1" msgid="4525743640399572811">"- यह <xliff:g id="APP">%1$s</xliff:g> से जानकारी पा सकता है"</string>
     <string name="abc_slice_permission_text_2" msgid="7323565634860251794">"- यह <xliff:g id="APP">%1$s</xliff:g> में कार्रवाई कर सकता है"</string>
-    <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"<xliff:g id="APP">%1$s</xliff:g> काे किसी भी ऐप्लिकेशन के हिस्से (स्लाइस) दिखाने की मंज़ूरी दें"</string>
+
     <string name="abc_slice_permission_allow" msgid="5024599872061409708">"अनुमति दें"</string>
     <string name="abc_slice_permission_deny" msgid="3819478292430407705">"अनुमति न दें"</string>
 </resources>
diff --git a/slices/core/src/main/res/values-hr/strings.xml b/slices/core/src/main/res/values-hr/strings.xml
index e0bffaa..9e614b5 100644
--- a/slices/core/src/main/res/values-hr/strings.xml
+++ b/slices/core/src/main/res/values-hr/strings.xml
@@ -21,7 +21,7 @@
     <string name="abc_slice_permission_title" msgid="4175332421259324948">"Želite li dopustiti aplikaciji <xliff:g id="APP_0">%1$s</xliff:g> da prikazuje isječke aplikacije <xliff:g id="APP_2">%2$s</xliff:g>?"</string>
     <string name="abc_slice_permission_text_1" msgid="4525743640399572811">"– može čitati informacije aplikacije <xliff:g id="APP">%1$s</xliff:g>"</string>
     <string name="abc_slice_permission_text_2" msgid="7323565634860251794">"– može vršiti radnje u aplikaciji <xliff:g id="APP">%1$s</xliff:g>"</string>
-    <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"Dopusti aplikaciji <xliff:g id="APP">%1$s</xliff:g> da prikazuje isječke iz bilo koje aplikacije"</string>
+
     <string name="abc_slice_permission_allow" msgid="5024599872061409708">"Dopusti"</string>
     <string name="abc_slice_permission_deny" msgid="3819478292430407705">"Odbij"</string>
 </resources>
diff --git a/slices/core/src/main/res/values-hu/strings.xml b/slices/core/src/main/res/values-hu/strings.xml
index 4601a88..004829f 100644
--- a/slices/core/src/main/res/values-hu/strings.xml
+++ b/slices/core/src/main/res/values-hu/strings.xml
@@ -21,7 +21,7 @@
     <string name="abc_slice_permission_title" msgid="4175332421259324948">"Engedélyezi a(z) <xliff:g id="APP_0">%1$s</xliff:g> alkalmazásnak, hogy részleteket mutasson a(z) <xliff:g id="APP_2">%2$s</xliff:g> alkalmazásból?"</string>
     <string name="abc_slice_permission_text_1" msgid="4525743640399572811">"– Információkat olvashat a(z) <xliff:g id="APP">%1$s</xliff:g> alkalmazásból"</string>
     <string name="abc_slice_permission_text_2" msgid="7323565634860251794">"– Műveleteket végezhet a(z) <xliff:g id="APP">%1$s</xliff:g> alkalmazáson belül"</string>
-    <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"Engedélyezi a(z) <xliff:g id="APP">%1$s</xliff:g> alkalmazásnak, hogy bármely alkalmazásból részletet jelenítsen meg"</string>
+
     <string name="abc_slice_permission_allow" msgid="5024599872061409708">"Engedélyezés"</string>
     <string name="abc_slice_permission_deny" msgid="3819478292430407705">"Elutasítás"</string>
 </resources>
diff --git a/slices/core/src/main/res/values-hy/strings.xml b/slices/core/src/main/res/values-hy/strings.xml
index c8850ff..1d716d7 100644
--- a/slices/core/src/main/res/values-hy/strings.xml
+++ b/slices/core/src/main/res/values-hy/strings.xml
@@ -21,7 +21,7 @@
     <string name="abc_slice_permission_title" msgid="4175332421259324948">"Թույլատրե՞լ <xliff:g id="APP_0">%1$s</xliff:g> հավելվածին ցուցադրել հատվածներ <xliff:g id="APP_2">%2$s</xliff:g> հավելվածից"</string>
     <string name="abc_slice_permission_text_1" msgid="4525743640399572811">"– Կարող է կարդալ տեղեկություններ <xliff:g id="APP">%1$s</xliff:g> հավելվածից"</string>
     <string name="abc_slice_permission_text_2" msgid="7323565634860251794">"– Կարող է կատարել գործողություններ <xliff:g id="APP">%1$s</xliff:g> հավելվածում"</string>
-    <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"Թույլատրել <xliff:g id="APP">%1$s</xliff:g> հավելվածին ցուցադրել հատվածներ ցանկացած հավելվածից"</string>
+
     <string name="abc_slice_permission_allow" msgid="5024599872061409708">"Թույլատրել"</string>
     <string name="abc_slice_permission_deny" msgid="3819478292430407705">"Մերժել"</string>
 </resources>
diff --git a/slices/core/src/main/res/values-in/strings.xml b/slices/core/src/main/res/values-in/strings.xml
index a68a7c7..860ed1c 100644
--- a/slices/core/src/main/res/values-in/strings.xml
+++ b/slices/core/src/main/res/values-in/strings.xml
@@ -21,7 +21,7 @@
     <string name="abc_slice_permission_title" msgid="4175332421259324948">"Izinkan <xliff:g id="APP_0">%1$s</xliff:g> menampilkan potongan <xliff:g id="APP_2">%2$s</xliff:g>?"</string>
     <string name="abc_slice_permission_text_1" msgid="4525743640399572811">"- Dapat membaca informasi dari <xliff:g id="APP">%1$s</xliff:g>"</string>
     <string name="abc_slice_permission_text_2" msgid="7323565634860251794">"- Dapat mengambil tindakan di dalam <xliff:g id="APP">%1$s</xliff:g>"</string>
-    <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"Izinkan <xliff:g id="APP">%1$s</xliff:g> menampilkan potongan dari aplikasi apa pun"</string>
+
     <string name="abc_slice_permission_allow" msgid="5024599872061409708">"Izinkan"</string>
     <string name="abc_slice_permission_deny" msgid="3819478292430407705">"Tolak"</string>
 </resources>
diff --git a/slices/core/src/main/res/values-is/strings.xml b/slices/core/src/main/res/values-is/strings.xml
index 24f3ace..da79a76 100644
--- a/slices/core/src/main/res/values-is/strings.xml
+++ b/slices/core/src/main/res/values-is/strings.xml
@@ -21,7 +21,7 @@
     <string name="abc_slice_permission_title" msgid="4175332421259324948">"Viltu leyfa <xliff:g id="APP_0">%1$s</xliff:g> að sýna sneiðar úr <xliff:g id="APP_2">%2$s</xliff:g>?"</string>
     <string name="abc_slice_permission_text_1" msgid="4525743640399572811">"- Það getur lesið upplýsingar úr <xliff:g id="APP">%1$s</xliff:g>"</string>
     <string name="abc_slice_permission_text_2" msgid="7323565634860251794">"- Það getur gripið til aðgerða í <xliff:g id="APP">%1$s</xliff:g>"</string>
-    <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"Leyfa <xliff:g id="APP">%1$s</xliff:g> að sýna sneiðar úr hvaða forriti sem er"</string>
+
     <string name="abc_slice_permission_allow" msgid="5024599872061409708">"Leyfa"</string>
     <string name="abc_slice_permission_deny" msgid="3819478292430407705">"Hafna"</string>
 </resources>
diff --git a/slices/core/src/main/res/values-it/strings.xml b/slices/core/src/main/res/values-it/strings.xml
index 333547d..5518d66 100644
--- a/slices/core/src/main/res/values-it/strings.xml
+++ b/slices/core/src/main/res/values-it/strings.xml
@@ -21,7 +21,7 @@
     <string name="abc_slice_permission_title" msgid="4175332421259324948">"Vuoi consentire all\'app <xliff:g id="APP_0">%1$s</xliff:g> di mostrare porzioni dell\'app <xliff:g id="APP_2">%2$s</xliff:g>?"</string>
     <string name="abc_slice_permission_text_1" msgid="4525743640399572811">"- Può leggere informazioni dell\'app <xliff:g id="APP">%1$s</xliff:g>"</string>
     <string name="abc_slice_permission_text_2" msgid="7323565634860251794">"- Può compiere azioni nell\'app <xliff:g id="APP">%1$s</xliff:g>"</string>
-    <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"Consenti all\'app <xliff:g id="APP">%1$s</xliff:g> di mostrare porzioni di qualsiasi app"</string>
+
     <string name="abc_slice_permission_allow" msgid="5024599872061409708">"Consenti"</string>
     <string name="abc_slice_permission_deny" msgid="3819478292430407705">"Rifiuta"</string>
 </resources>
diff --git a/slices/core/src/main/res/values-iw/strings.xml b/slices/core/src/main/res/values-iw/strings.xml
index 7534117..2151548 100644
--- a/slices/core/src/main/res/values-iw/strings.xml
+++ b/slices/core/src/main/res/values-iw/strings.xml
@@ -21,7 +21,7 @@
     <string name="abc_slice_permission_title" msgid="4175332421259324948">"האם לאפשר ל-<xliff:g id="APP_0">%1$s</xliff:g> להציג חלקים מ-<xliff:g id="APP_2">%2$s</xliff:g>?"</string>
     <string name="abc_slice_permission_text_1" msgid="4525743640399572811">"- תהיה לה אפשרות לקרוא מידע מהאפליקציה <xliff:g id="APP">%1$s</xliff:g>"</string>
     <string name="abc_slice_permission_text_2" msgid="7323565634860251794">"- תהיה לה יכולת לנקוט פעולה בתוך <xliff:g id="APP">%1$s</xliff:g>"</string>
-    <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"יש לאשר לאפליקציית <xliff:g id="APP">%1$s</xliff:g> להציג חלקים מכל אפליקציה שהיא"</string>
+
     <string name="abc_slice_permission_allow" msgid="5024599872061409708">"אישור"</string>
     <string name="abc_slice_permission_deny" msgid="3819478292430407705">"אני לא מרשה"</string>
 </resources>
diff --git a/slices/core/src/main/res/values-ja/strings.xml b/slices/core/src/main/res/values-ja/strings.xml
index 315326f..1c28f4c 100644
--- a/slices/core/src/main/res/values-ja/strings.xml
+++ b/slices/core/src/main/res/values-ja/strings.xml
@@ -21,7 +21,7 @@
     <string name="abc_slice_permission_title" msgid="4175332421259324948">"<xliff:g id="APP_2">%2$s</xliff:g> のスライスの表示を <xliff:g id="APP_0">%1$s</xliff:g> に許可しますか?"</string>
     <string name="abc_slice_permission_text_1" msgid="4525743640399572811">"- <xliff:g id="APP">%1$s</xliff:g> からの情報を読み取ることができます"</string>
     <string name="abc_slice_permission_text_2" msgid="7323565634860251794">"- <xliff:g id="APP">%1$s</xliff:g> 内部で操作することがあります"</string>
-    <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"すべてのアプリのスライスを表示することを <xliff:g id="APP">%1$s</xliff:g> に許可する"</string>
+
     <string name="abc_slice_permission_allow" msgid="5024599872061409708">"許可"</string>
     <string name="abc_slice_permission_deny" msgid="3819478292430407705">"拒否"</string>
 </resources>
diff --git a/slices/core/src/main/res/values-ka/strings.xml b/slices/core/src/main/res/values-ka/strings.xml
index f21d1be..b78206c 100644
--- a/slices/core/src/main/res/values-ka/strings.xml
+++ b/slices/core/src/main/res/values-ka/strings.xml
@@ -21,7 +21,7 @@
     <string name="abc_slice_permission_title" msgid="4175332421259324948">"ანიჭებთ ნებართვას <xliff:g id="APP_0">%1$s</xliff:g>-ს, აჩვენოს <xliff:g id="APP_2">%2$s</xliff:g>-ის ფრაგმენტები?"</string>
     <string name="abc_slice_permission_text_1" msgid="4525743640399572811">"- მას შეუძლია ინფორმაციის <xliff:g id="APP">%1$s</xliff:g>-დან წაკითხვა"</string>
     <string name="abc_slice_permission_text_2" msgid="7323565634860251794">"- მას შეუძლია ქმედებების <xliff:g id="APP">%1$s</xliff:g>-ში განხორციელება"</string>
-    <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"<xliff:g id="APP">%1$s</xliff:g>-ისთვის ფრაგმენტების ნებისმიერი აპიდან ჩვენების ნების დართვა"</string>
+
     <string name="abc_slice_permission_allow" msgid="5024599872061409708">"დაშვება"</string>
     <string name="abc_slice_permission_deny" msgid="3819478292430407705">"უარყოფა"</string>
 </resources>
diff --git a/slices/core/src/main/res/values-kk/strings.xml b/slices/core/src/main/res/values-kk/strings.xml
index aa9409f..fcb0509 100644
--- a/slices/core/src/main/res/values-kk/strings.xml
+++ b/slices/core/src/main/res/values-kk/strings.xml
@@ -21,7 +21,7 @@
     <string name="abc_slice_permission_title" msgid="4175332421259324948">"<xliff:g id="APP_0">%1$s</xliff:g> қолданбасына <xliff:g id="APP_2">%2$s</xliff:g> қолданбасының үзінділерін көрсетуге рұқсат берілсін бе?"</string>
     <string name="abc_slice_permission_text_1" msgid="4525743640399572811">"- <xliff:g id="APP">%1$s</xliff:g> қолданбасындағы ақпаратты оқи алады"</string>
     <string name="abc_slice_permission_text_2" msgid="7323565634860251794">"- <xliff:g id="APP">%1$s</xliff:g> қолданбасында әрекет ете алады"</string>
-    <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"<xliff:g id="APP">%1$s</xliff:g> қолданбасына кез келген қолданбаның үзіндісін көрсетуге рұқсат беру"</string>
+
     <string name="abc_slice_permission_allow" msgid="5024599872061409708">"Рұқсат беру"</string>
     <string name="abc_slice_permission_deny" msgid="3819478292430407705">"Тыйым салу"</string>
 </resources>
diff --git a/slices/core/src/main/res/values-km/strings.xml b/slices/core/src/main/res/values-km/strings.xml
index a98f409..5d0a988 100644
--- a/slices/core/src/main/res/values-km/strings.xml
+++ b/slices/core/src/main/res/values-km/strings.xml
@@ -21,7 +21,7 @@
     <string name="abc_slice_permission_title" msgid="4175332421259324948">"អនុញ្ញាតឱ្យ <xliff:g id="APP_0">%1$s</xliff:g> បង្ហាញ​ស្ថិតិប្រើប្រាស់​របស់ <xliff:g id="APP_2">%2$s</xliff:g> ?"</string>
     <string name="abc_slice_permission_text_1" msgid="4525743640399572811">"- វា​អាច​អាន​ព័ត៌មាន​ពី <xliff:g id="APP">%1$s</xliff:g>"</string>
     <string name="abc_slice_permission_text_2" msgid="7323565634860251794">"- វាអាច​ធ្វើសកម្មភាព​នៅក្នុង <xliff:g id="APP">%1$s</xliff:g>"</string>
-    <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"អនុញ្ញាត​ឱ្យ <xliff:g id="APP">%1$s</xliff:g> បង្ហាញ​ស្ថិតិ​ប្រើប្រាស់​ពី​កម្មវិធី​នានា"</string>
+
     <string name="abc_slice_permission_allow" msgid="5024599872061409708">"អនុញ្ញាត"</string>
     <string name="abc_slice_permission_deny" msgid="3819478292430407705">"បដិសេធ"</string>
 </resources>
diff --git a/slices/core/src/main/res/values-kn/strings.xml b/slices/core/src/main/res/values-kn/strings.xml
index e8a0559..7fe32e0 100644
--- a/slices/core/src/main/res/values-kn/strings.xml
+++ b/slices/core/src/main/res/values-kn/strings.xml
@@ -21,7 +21,7 @@
     <string name="abc_slice_permission_title" msgid="4175332421259324948">"<xliff:g id="APP_2">%2$s</xliff:g> ಸ್ಲೈಸ್‌ಗಳನ್ನು ತೋರಿಸಲು <xliff:g id="APP_0">%1$s</xliff:g> ಅನ್ನು ಅನುಮತಿಸುವುದೇ?"</string>
     <string name="abc_slice_permission_text_1" msgid="4525743640399572811">"- ಇದು <xliff:g id="APP">%1$s</xliff:g> ನಿಂದ ಮಾಹಿತಿಯನ್ನು ಓದಬಹುದು"</string>
     <string name="abc_slice_permission_text_2" msgid="7323565634860251794">"- ಇದು <xliff:g id="APP">%1$s</xliff:g> ಒಳಗಡೆ ಕ್ರಿಯೆಗಳನ್ನು ತೆಗೆದುಕೊಳ್ಳಬಹುದು"</string>
-    <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"ಯಾವುದೇ ಅಪ್ಲಿಕೇಶನ್‌ನಿಂದ ಸ್ಲೈಸ್‌ಗಳನ್ನು ತೋರಿಸಲು <xliff:g id="APP">%1$s</xliff:g> ಅನ್ನು ಅನುಮತಿಸಿ"</string>
+
     <string name="abc_slice_permission_allow" msgid="5024599872061409708">"ಅನುಮತಿಸಿ"</string>
     <string name="abc_slice_permission_deny" msgid="3819478292430407705">"ನಿರಾಕರಿಸಿ"</string>
 </resources>
diff --git a/slices/core/src/main/res/values-ko/strings.xml b/slices/core/src/main/res/values-ko/strings.xml
index cc88f25..93c62f0 100644
--- a/slices/core/src/main/res/values-ko/strings.xml
+++ b/slices/core/src/main/res/values-ko/strings.xml
@@ -21,7 +21,7 @@
     <string name="abc_slice_permission_title" msgid="4175332421259324948">"<xliff:g id="APP_0">%1$s</xliff:g>에서 <xliff:g id="APP_2">%2$s</xliff:g>의 슬라이스를 표시하도록 허용하시겠습니까?"</string>
     <string name="abc_slice_permission_text_1" msgid="4525743640399572811">"- <xliff:g id="APP">%1$s</xliff:g>의 정보를 읽을 수 있음"</string>
     <string name="abc_slice_permission_text_2" msgid="7323565634860251794">"- <xliff:g id="APP">%1$s</xliff:g>에서 작업할 수 있음"</string>
-    <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"<xliff:g id="APP">%1$s</xliff:g>에서 모든 앱의 슬라이스를 표시하도록 허용"</string>
+
     <string name="abc_slice_permission_allow" msgid="5024599872061409708">"허용"</string>
     <string name="abc_slice_permission_deny" msgid="3819478292430407705">"거부"</string>
 </resources>
diff --git a/slices/core/src/main/res/values-ky/strings.xml b/slices/core/src/main/res/values-ky/strings.xml
index 99ce121..9615740 100644
--- a/slices/core/src/main/res/values-ky/strings.xml
+++ b/slices/core/src/main/res/values-ky/strings.xml
@@ -21,7 +21,7 @@
     <string name="abc_slice_permission_title" msgid="4175332421259324948">"<xliff:g id="APP_0">%1$s</xliff:g> колдонмосуна <xliff:g id="APP_2">%2$s</xliff:g> үлгүлөрүн көрсөтүүгө уруксат берилсинби?"</string>
     <string name="abc_slice_permission_text_1" msgid="4525743640399572811">"- <xliff:g id="APP">%1$s</xliff:g> колдонмосунун маалыматын окуйт"</string>
     <string name="abc_slice_permission_text_2" msgid="7323565634860251794">"- <xliff:g id="APP">%1$s</xliff:g> колдонмосунда аракеттерди аткарат"</string>
-    <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"<xliff:g id="APP">%1$s</xliff:g> бардык колдонмолордун үлгүлөрүн көрсөтүүгө уруксат берүү"</string>
+
     <string name="abc_slice_permission_allow" msgid="5024599872061409708">"Уруксат берүү"</string>
     <string name="abc_slice_permission_deny" msgid="3819478292430407705">"Тыюу салынат"</string>
 </resources>
diff --git a/slices/core/src/main/res/values-lo/strings.xml b/slices/core/src/main/res/values-lo/strings.xml
index 74bb119..9b18f5b 100644
--- a/slices/core/src/main/res/values-lo/strings.xml
+++ b/slices/core/src/main/res/values-lo/strings.xml
@@ -21,7 +21,7 @@
     <string name="abc_slice_permission_title" msgid="4175332421259324948">"ອະນຸຍາດ <xliff:g id="APP_0">%1$s</xliff:g> ໃຫ້ສະແດງ <xliff:g id="APP_2">%2$s</xliff:g> ສະໄລ້ບໍ?"</string>
     <string name="abc_slice_permission_text_1" msgid="4525743640399572811">"- ມັນສາມາດອ່ານຂໍ້ມູນຈາກ <xliff:g id="APP">%1$s</xliff:g>"</string>
     <string name="abc_slice_permission_text_2" msgid="7323565634860251794">"- ມັນສາມາດໃຊ້ຄຳສັ່ງພາຍໃນ <xliff:g id="APP">%1$s</xliff:g>"</string>
-    <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"ອະນຸຍາດ <xliff:g id="APP">%1$s</xliff:g> ເພື່ອສະແດງສະໄລ້ຈາກແອັບ"</string>
+
     <string name="abc_slice_permission_allow" msgid="5024599872061409708">"ອະນຸຍາດ"</string>
     <string name="abc_slice_permission_deny" msgid="3819478292430407705">"ປະຕິເສດ"</string>
 </resources>
diff --git a/slices/core/src/main/res/values-lt/strings.xml b/slices/core/src/main/res/values-lt/strings.xml
index 50eb4a6..1e88cd6 100644
--- a/slices/core/src/main/res/values-lt/strings.xml
+++ b/slices/core/src/main/res/values-lt/strings.xml
@@ -21,7 +21,7 @@
     <string name="abc_slice_permission_title" msgid="4175332421259324948">"Leisti „<xliff:g id="APP_0">%1$s</xliff:g>“ rodyti „<xliff:g id="APP_2">%2$s</xliff:g>“ fragmentus?"</string>
     <string name="abc_slice_permission_text_1" msgid="4525743640399572811">"– Gali nuskaityti informaciją iš „<xliff:g id="APP">%1$s</xliff:g>“"</string>
     <string name="abc_slice_permission_text_2" msgid="7323565634860251794">"– Gali imtis veiksmų programoje „<xliff:g id="APP">%1$s</xliff:g>“"</string>
-    <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"Leisti „<xliff:g id="APP">%1$s</xliff:g>“ rodyti bet kurios programos fragmentus"</string>
+
     <string name="abc_slice_permission_allow" msgid="5024599872061409708">"Leisti"</string>
     <string name="abc_slice_permission_deny" msgid="3819478292430407705">"Atmesti"</string>
 </resources>
diff --git a/slices/core/src/main/res/values-lv/strings.xml b/slices/core/src/main/res/values-lv/strings.xml
index 4bfb688..1f3ccde 100644
--- a/slices/core/src/main/res/values-lv/strings.xml
+++ b/slices/core/src/main/res/values-lv/strings.xml
@@ -21,7 +21,7 @@
     <string name="abc_slice_permission_title" msgid="4175332421259324948">"Vai atļaut lietotnei <xliff:g id="APP_0">%1$s</xliff:g> rādīt lietotnes <xliff:g id="APP_2">%2$s</xliff:g> sadaļas?"</string>
     <string name="abc_slice_permission_text_1" msgid="4525743640399572811">"- Var lasīt informāciju no lietotnes <xliff:g id="APP">%1$s</xliff:g>"</string>
     <string name="abc_slice_permission_text_2" msgid="7323565634860251794">"- Var veikt darbības lietotnē <xliff:g id="APP">%1$s</xliff:g>"</string>
-    <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"Atļaut lietotnei <xliff:g id="APP">%1$s</xliff:g> rādīt sadaļas no jebkuras lietotnes"</string>
+
     <string name="abc_slice_permission_allow" msgid="5024599872061409708">"Atļaut"</string>
     <string name="abc_slice_permission_deny" msgid="3819478292430407705">"Neatļaut"</string>
 </resources>
diff --git a/slices/core/src/main/res/values-mk/strings.xml b/slices/core/src/main/res/values-mk/strings.xml
index 2525781..a5ccc53 100644
--- a/slices/core/src/main/res/values-mk/strings.xml
+++ b/slices/core/src/main/res/values-mk/strings.xml
@@ -21,7 +21,7 @@
     <string name="abc_slice_permission_title" msgid="4175332421259324948">"Да се дозволи <xliff:g id="APP_0">%1$s</xliff:g> да прикажува делови од <xliff:g id="APP_2">%2$s</xliff:g>?"</string>
     <string name="abc_slice_permission_text_1" msgid="4525743640399572811">"- Може да чита информации од <xliff:g id="APP">%1$s</xliff:g>"</string>
     <string name="abc_slice_permission_text_2" msgid="7323565634860251794">"- Може да презема дејства во <xliff:g id="APP">%1$s</xliff:g>"</string>
-    <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"Дозволете <xliff:g id="APP">%1$s</xliff:g> да прикажува делови од која било апликација"</string>
+
     <string name="abc_slice_permission_allow" msgid="5024599872061409708">"Дозволете"</string>
     <string name="abc_slice_permission_deny" msgid="3819478292430407705">"Одбијте"</string>
 </resources>
diff --git a/slices/core/src/main/res/values-ml/strings.xml b/slices/core/src/main/res/values-ml/strings.xml
index ce177d2..21802b7 100644
--- a/slices/core/src/main/res/values-ml/strings.xml
+++ b/slices/core/src/main/res/values-ml/strings.xml
@@ -21,7 +21,7 @@
     <string name="abc_slice_permission_title" msgid="4175332421259324948">"<xliff:g id="APP_2">%2$s</xliff:g> സ്ലൈസുകൾ കാണിക്കാൻ <xliff:g id="APP_0">%1$s</xliff:g>-നെ അനുവദിക്കണോ?"</string>
     <string name="abc_slice_permission_text_1" msgid="4525743640399572811">"- ഇതിന് <xliff:g id="APP">%1$s</xliff:g>-ൽ നിന്ന് വിവരങ്ങൾ വായിക്കാനാകും"</string>
     <string name="abc_slice_permission_text_2" msgid="7323565634860251794">"- ഇതിന് <xliff:g id="APP">%1$s</xliff:g>-നുള്ളിൽ പ്രവർത്തനങ്ങൾ ചെയ്യാനാകും"</string>
-    <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"ഏത് ആപ്പിൽ നിന്നും സ്ലൈസുകൾ കാണിക്കാൻ <xliff:g id="APP">%1$s</xliff:g>-നെ അനുവദിക്കുക"</string>
+
     <string name="abc_slice_permission_allow" msgid="5024599872061409708">"അനുവദിക്കുക"</string>
     <string name="abc_slice_permission_deny" msgid="3819478292430407705">"നിരസിക്കുക"</string>
 </resources>
diff --git a/slices/core/src/main/res/values-mn/strings.xml b/slices/core/src/main/res/values-mn/strings.xml
index 50764d9..8e0661a 100644
--- a/slices/core/src/main/res/values-mn/strings.xml
+++ b/slices/core/src/main/res/values-mn/strings.xml
@@ -21,7 +21,7 @@
     <string name="abc_slice_permission_title" msgid="4175332421259324948">"<xliff:g id="APP_0">%1$s</xliff:g>-д <xliff:g id="APP_2">%2$s</xliff:g>-н хэсгүүдийг харуулахыг зөвшөөрөх үү?"</string>
     <string name="abc_slice_permission_text_1" msgid="4525743640399572811">"- Энэ <xliff:g id="APP">%1$s</xliff:g>-с мэдээлэл унших боломжтой"</string>
     <string name="abc_slice_permission_text_2" msgid="7323565634860251794">"- Энэ <xliff:g id="APP">%1$s</xliff:g> дотор үйлдэл хийх боломжтой"</string>
-    <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"<xliff:g id="APP">%1$s</xliff:g>-д дурын аппаас хэсэг харуулахыг зөвшөөрөх"</string>
+
     <string name="abc_slice_permission_allow" msgid="5024599872061409708">"Зөвшөөрөх"</string>
     <string name="abc_slice_permission_deny" msgid="3819478292430407705">"Татгалзах"</string>
 </resources>
diff --git a/slices/core/src/main/res/values-mr/strings.xml b/slices/core/src/main/res/values-mr/strings.xml
index f938652..ab48521 100644
--- a/slices/core/src/main/res/values-mr/strings.xml
+++ b/slices/core/src/main/res/values-mr/strings.xml
@@ -21,7 +21,7 @@
     <string name="abc_slice_permission_title" msgid="4175332421259324948">"<xliff:g id="APP_0">%1$s</xliff:g> ला <xliff:g id="APP_2">%2$s</xliff:g> चे तुकडे दाखवण्याची अनुमती द्यायची का?"</string>
     <string name="abc_slice_permission_text_1" msgid="4525743640399572811">"- ते <xliff:g id="APP">%1$s</xliff:g> ची माहिती वाचू शकते"</string>
     <string name="abc_slice_permission_text_2" msgid="7323565634860251794">"- ते <xliff:g id="APP">%1$s</xliff:g> मध्ये कृती करू शकते"</string>
-    <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"<xliff:g id="APP">%1$s</xliff:g> ला कुठल्याही अ‍ॅपमधील तुकडे दाखवण्याची अनुमती द्या"</string>
+
     <string name="abc_slice_permission_allow" msgid="5024599872061409708">"अनुमती द्या"</string>
     <string name="abc_slice_permission_deny" msgid="3819478292430407705">"नकार द्या"</string>
 </resources>
diff --git a/slices/core/src/main/res/values-ms/strings.xml b/slices/core/src/main/res/values-ms/strings.xml
index af2a67e..4b20a63 100644
--- a/slices/core/src/main/res/values-ms/strings.xml
+++ b/slices/core/src/main/res/values-ms/strings.xml
@@ -21,7 +21,7 @@
     <string name="abc_slice_permission_title" msgid="4175332421259324948">"Benarkan <xliff:g id="APP_0">%1$s</xliff:g> menunjukkan hirisan <xliff:g id="APP_2">%2$s</xliff:g>?"</string>
     <string name="abc_slice_permission_text_1" msgid="4525743640399572811">"- Hos hirisan boleh membaca maklumat daripada <xliff:g id="APP">%1$s</xliff:g>"</string>
     <string name="abc_slice_permission_text_2" msgid="7323565634860251794">"- Hos hirisan boleh mengambil tindakan dalam <xliff:g id="APP">%1$s</xliff:g>"</string>
-    <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"Benarkan <xliff:g id="APP">%1$s</xliff:g> menunjukkan hirisan daripada mana-mana apl"</string>
+
     <string name="abc_slice_permission_allow" msgid="5024599872061409708">"Benarkan"</string>
     <string name="abc_slice_permission_deny" msgid="3819478292430407705">"Tolak"</string>
 </resources>
diff --git a/slices/core/src/main/res/values-my/strings.xml b/slices/core/src/main/res/values-my/strings.xml
index 0cf5d3e..666640ef 100644
--- a/slices/core/src/main/res/values-my/strings.xml
+++ b/slices/core/src/main/res/values-my/strings.xml
@@ -21,7 +21,7 @@
     <string name="abc_slice_permission_title" msgid="4175332421259324948">"<xliff:g id="APP_0">%1$s</xliff:g> အား <xliff:g id="APP_2">%2$s</xliff:g> ၏အချပ်များ ပြသခွင့်ပြုပါသလား။"</string>
     <string name="abc_slice_permission_text_1" msgid="4525743640399572811">"- ၎င်းသည် <xliff:g id="APP">%1$s</xliff:g> မှ အချက်အလက်ကို ဖတ်နိုင်သည်"</string>
     <string name="abc_slice_permission_text_2" msgid="7323565634860251794">"- ၎င်းသည် <xliff:g id="APP">%1$s</xliff:g> အတွင်း လုပ်ဆောင်ချက်များ ပြုလုပ်နိုင်သည်"</string>
-    <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"မည်သည့်အက်ပ်မဆိုမှ အချပ်များ ပြသရန်အတွက် <xliff:g id="APP">%1$s</xliff:g> ကို ခွင့်ပြုရန်"</string>
+
     <string name="abc_slice_permission_allow" msgid="5024599872061409708">"ခွင့်ပြုရန်"</string>
     <string name="abc_slice_permission_deny" msgid="3819478292430407705">"ငြင်းပယ်ရန်"</string>
 </resources>
diff --git a/slices/core/src/main/res/values-nb/strings.xml b/slices/core/src/main/res/values-nb/strings.xml
index 634eac3..9448d03 100644
--- a/slices/core/src/main/res/values-nb/strings.xml
+++ b/slices/core/src/main/res/values-nb/strings.xml
@@ -21,7 +21,7 @@
     <string name="abc_slice_permission_title" msgid="4175332421259324948">"Vil du tillate at <xliff:g id="APP_0">%1$s</xliff:g> viser <xliff:g id="APP_2">%2$s</xliff:g>-utsnitt?"</string>
     <string name="abc_slice_permission_text_1" msgid="4525743640399572811">"– Den kan lese informasjon fra <xliff:g id="APP">%1$s</xliff:g>"</string>
     <string name="abc_slice_permission_text_2" msgid="7323565634860251794">"– Den kan utføre handlinger i <xliff:g id="APP">%1$s</xliff:g>"</string>
-    <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"Tillat at <xliff:g id="APP">%1$s</xliff:g> viser utsnitt fra alle apper"</string>
+
     <string name="abc_slice_permission_allow" msgid="5024599872061409708">"Tillat"</string>
     <string name="abc_slice_permission_deny" msgid="3819478292430407705">"Ikke tillat"</string>
 </resources>
diff --git a/slices/core/src/main/res/values-ne/strings.xml b/slices/core/src/main/res/values-ne/strings.xml
index adf3a1b..bfa42445 100644
--- a/slices/core/src/main/res/values-ne/strings.xml
+++ b/slices/core/src/main/res/values-ne/strings.xml
@@ -21,7 +21,7 @@
     <string name="abc_slice_permission_title" msgid="4175332421259324948">"<xliff:g id="APP_0">%1$s</xliff:g> लाई <xliff:g id="APP_2">%2$s</xliff:g> का स्लाइसहरू देखाउन अनुमति दिने हो?"</string>
     <string name="abc_slice_permission_text_1" msgid="4525743640399572811">"- यसले <xliff:g id="APP">%1$s</xliff:g> को जानकारी पढ्न सक्छ"</string>
     <string name="abc_slice_permission_text_2" msgid="7323565634860251794">"- यसले <xliff:g id="APP">%1$s</xliff:g> भित्र कारबाही गर्न सक्छ"</string>
-    <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"<xliff:g id="APP">%1$s</xliff:g> लाई सबै एपका स्लाइसहरू देखाउन अनुमति दिनुहोस्"</string>
+
     <string name="abc_slice_permission_allow" msgid="5024599872061409708">"अनुमति दिनुहोस्"</string>
     <string name="abc_slice_permission_deny" msgid="3819478292430407705">"नदिने"</string>
 </resources>
diff --git a/slices/core/src/main/res/values-nl/strings.xml b/slices/core/src/main/res/values-nl/strings.xml
index 46b2c9f..4d8006e 100644
--- a/slices/core/src/main/res/values-nl/strings.xml
+++ b/slices/core/src/main/res/values-nl/strings.xml
@@ -21,7 +21,7 @@
     <string name="abc_slice_permission_title" msgid="4175332421259324948">"<xliff:g id="APP_0">%1$s</xliff:g> toestaan om segmenten van <xliff:g id="APP_2">%2$s</xliff:g> te tonen?"</string>
     <string name="abc_slice_permission_text_1" msgid="4525743640399572811">"- Deze kan informatie lezen van <xliff:g id="APP">%1$s</xliff:g>"</string>
     <string name="abc_slice_permission_text_2" msgid="7323565634860251794">"- Deze kan acties uitvoeren in <xliff:g id="APP">%1$s</xliff:g>"</string>
-    <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"<xliff:g id="APP">%1$s</xliff:g> toestaan om segmenten van apps te tonen"</string>
+
     <string name="abc_slice_permission_allow" msgid="5024599872061409708">"Toestaan"</string>
     <string name="abc_slice_permission_deny" msgid="3819478292430407705">"Weigeren"</string>
 </resources>
diff --git a/slices/core/src/main/res/values-or/strings.xml b/slices/core/src/main/res/values-or/strings.xml
index a87a506..a757613 100644
--- a/slices/core/src/main/res/values-or/strings.xml
+++ b/slices/core/src/main/res/values-or/strings.xml
@@ -21,7 +21,7 @@
     <string name="abc_slice_permission_title" msgid="4175332421259324948">"<xliff:g id="APP_2">%2$s</xliff:g> ସ୍ଲାଇସ୍‌କୁ ଦେଖାଇବା ପାଇଁ <xliff:g id="APP_0">%1$s</xliff:g>କୁ ଅନୁମତି ଦେବେ?"</string>
     <string name="abc_slice_permission_text_1" msgid="4525743640399572811">"- ଏହା <xliff:g id="APP">%1$s</xliff:g>ରୁ ସୂଚନାକୁ ପଢ଼ିପାରିବ"</string>
     <string name="abc_slice_permission_text_2" msgid="7323565634860251794">"- ଏହା <xliff:g id="APP">%1$s</xliff:g> ଭିତରେ କାମ କରିପାରିବ"</string>
-    <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"ଯେକୌଣସି ଆପ୍‌ରେ ସ୍ଲାଇସ୍‌କୁ ଦେଖାଇବା ପାଇଁ <xliff:g id="APP">%1$s</xliff:g>କୁ ଅନୁମତି ଦିଅନ୍ତୁ"</string>
+
     <string name="abc_slice_permission_allow" msgid="5024599872061409708">"ଅନୁମତି ଦିଅନ୍ତୁ"</string>
     <string name="abc_slice_permission_deny" msgid="3819478292430407705">"ଅଗ୍ରାହ୍ୟ କରନ୍ତୁ"</string>
 </resources>
diff --git a/slices/core/src/main/res/values-pa/strings.xml b/slices/core/src/main/res/values-pa/strings.xml
index e1ef272..d68cc39 100644
--- a/slices/core/src/main/res/values-pa/strings.xml
+++ b/slices/core/src/main/res/values-pa/strings.xml
@@ -21,7 +21,7 @@
     <string name="abc_slice_permission_title" msgid="4175332421259324948">"ਕੀ <xliff:g id="APP_0">%1$s</xliff:g> ਨੂੰ <xliff:g id="APP_2">%2$s</xliff:g> ਦੇ ਹਿੱਸੇ ਦਿਖਾਉਣ ਦੇਣੇ ਹਨ?"</string>
     <string name="abc_slice_permission_text_1" msgid="4525743640399572811">"- ਇਹ <xliff:g id="APP">%1$s</xliff:g> ਵਿੱਚੋਂ ਜਾਣਕਾਰੀ ਪੜ੍ਹ ਸਕਦਾ ਹੈ"</string>
     <string name="abc_slice_permission_text_2" msgid="7323565634860251794">"- ਇਸ <xliff:g id="APP">%1$s</xliff:g> ਦੇ ਅੰਦਰ ਕਾਰਵਾਈਆਂ ਕਰ ਸਕਦਾ ਹੈ"</string>
-    <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"<xliff:g id="APP">%1$s</xliff:g> ਨੂੰ ਕਿਸੇ ਵੀ ਐਪ ਵਿੱਚੋਂ ਹਿੱਸੇ ਦਿਖਾਉਣ ਦਿਓ"</string>
+
     <string name="abc_slice_permission_allow" msgid="5024599872061409708">"ਕਰਨ ਦਿਓ"</string>
     <string name="abc_slice_permission_deny" msgid="3819478292430407705">"ਅਸਵੀਕਾਰ ਕਰੋ"</string>
 </resources>
diff --git a/slices/core/src/main/res/values-pl/strings.xml b/slices/core/src/main/res/values-pl/strings.xml
index f66d32c..ea32902 100644
--- a/slices/core/src/main/res/values-pl/strings.xml
+++ b/slices/core/src/main/res/values-pl/strings.xml
@@ -21,7 +21,7 @@
     <string name="abc_slice_permission_title" msgid="4175332421259324948">"Zezwolić aplikacji <xliff:g id="APP_0">%1$s</xliff:g> na pokazywanie wycinków z aplikacji <xliff:g id="APP_2">%2$s</xliff:g>?"</string>
     <string name="abc_slice_permission_text_1" msgid="4525743640399572811">"- Może odczytywać informacje z aplikacji <xliff:g id="APP">%1$s</xliff:g>"</string>
     <string name="abc_slice_permission_text_2" msgid="7323565634860251794">"- Może wykonywać działania w aplikacji <xliff:g id="APP">%1$s</xliff:g>"</string>
-    <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"Zezwalaj aplikacji <xliff:g id="APP">%1$s</xliff:g> na pokazywanie wycinków z dowolnych aplikacji"</string>
+
     <string name="abc_slice_permission_allow" msgid="5024599872061409708">"Zezwól"</string>
     <string name="abc_slice_permission_deny" msgid="3819478292430407705">"Odmów"</string>
 </resources>
diff --git a/slices/core/src/main/res/values-pt-rBR/strings.xml b/slices/core/src/main/res/values-pt-rBR/strings.xml
index 246fc77..b58e786 100644
--- a/slices/core/src/main/res/values-pt-rBR/strings.xml
+++ b/slices/core/src/main/res/values-pt-rBR/strings.xml
@@ -21,7 +21,7 @@
     <string name="abc_slice_permission_title" msgid="4175332421259324948">"Permitir que <xliff:g id="APP_0">%1$s</xliff:g> mostre partes do app <xliff:g id="APP_2">%2$s</xliff:g>?"</string>
     <string name="abc_slice_permission_text_1" msgid="4525743640399572811">"- Pode ler informações do app <xliff:g id="APP">%1$s</xliff:g>"</string>
     <string name="abc_slice_permission_text_2" msgid="7323565634860251794">"- Pode realizar ações no app <xliff:g id="APP">%1$s</xliff:g>"</string>
-    <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"Permitir que <xliff:g id="APP">%1$s</xliff:g> mostre partes de qualquer app"</string>
+
     <string name="abc_slice_permission_allow" msgid="5024599872061409708">"Permitir"</string>
     <string name="abc_slice_permission_deny" msgid="3819478292430407705">"Negar"</string>
 </resources>
diff --git a/slices/core/src/main/res/values-pt-rPT/strings.xml b/slices/core/src/main/res/values-pt-rPT/strings.xml
index 472dce8..832b5e5 100644
--- a/slices/core/src/main/res/values-pt-rPT/strings.xml
+++ b/slices/core/src/main/res/values-pt-rPT/strings.xml
@@ -21,7 +21,7 @@
     <string name="abc_slice_permission_title" msgid="4175332421259324948">"Permitir que a app <xliff:g id="APP_0">%1$s</xliff:g> mostre partes da app <xliff:g id="APP_2">%2$s</xliff:g>?"</string>
     <string name="abc_slice_permission_text_1" msgid="4525743640399572811">"- Pode ler informações da app <xliff:g id="APP">%1$s</xliff:g>"</string>
     <string name="abc_slice_permission_text_2" msgid="7323565634860251794">"- Pode realizar ações na app <xliff:g id="APP">%1$s</xliff:g>"</string>
-    <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"Permitir que a app <xliff:g id="APP">%1$s</xliff:g> mostre partes de qualquer app"</string>
+
     <string name="abc_slice_permission_allow" msgid="5024599872061409708">"Permitir"</string>
     <string name="abc_slice_permission_deny" msgid="3819478292430407705">"Recusar"</string>
 </resources>
diff --git a/slices/core/src/main/res/values-pt/strings.xml b/slices/core/src/main/res/values-pt/strings.xml
index 246fc77..b58e786 100644
--- a/slices/core/src/main/res/values-pt/strings.xml
+++ b/slices/core/src/main/res/values-pt/strings.xml
@@ -21,7 +21,7 @@
     <string name="abc_slice_permission_title" msgid="4175332421259324948">"Permitir que <xliff:g id="APP_0">%1$s</xliff:g> mostre partes do app <xliff:g id="APP_2">%2$s</xliff:g>?"</string>
     <string name="abc_slice_permission_text_1" msgid="4525743640399572811">"- Pode ler informações do app <xliff:g id="APP">%1$s</xliff:g>"</string>
     <string name="abc_slice_permission_text_2" msgid="7323565634860251794">"- Pode realizar ações no app <xliff:g id="APP">%1$s</xliff:g>"</string>
-    <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"Permitir que <xliff:g id="APP">%1$s</xliff:g> mostre partes de qualquer app"</string>
+
     <string name="abc_slice_permission_allow" msgid="5024599872061409708">"Permitir"</string>
     <string name="abc_slice_permission_deny" msgid="3819478292430407705">"Negar"</string>
 </resources>
diff --git a/slices/core/src/main/res/values-ro/strings.xml b/slices/core/src/main/res/values-ro/strings.xml
index f4b1135..2db5e57 100644
--- a/slices/core/src/main/res/values-ro/strings.xml
+++ b/slices/core/src/main/res/values-ro/strings.xml
@@ -21,7 +21,7 @@
     <string name="abc_slice_permission_title" msgid="4175332421259324948">"Permiteți <xliff:g id="APP_0">%1$s</xliff:g> să afișeze porțiuni din <xliff:g id="APP_2">%2$s</xliff:g>?"</string>
     <string name="abc_slice_permission_text_1" msgid="4525743640399572811">"- Poate citi informații din <xliff:g id="APP">%1$s</xliff:g>"</string>
     <string name="abc_slice_permission_text_2" msgid="7323565634860251794">"- Poate efectua acțiuni în <xliff:g id="APP">%1$s</xliff:g>"</string>
-    <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"Permiteți <xliff:g id="APP">%1$s</xliff:g> să afișeze porțiuni din orice aplicație"</string>
+
     <string name="abc_slice_permission_allow" msgid="5024599872061409708">"Permiteți"</string>
     <string name="abc_slice_permission_deny" msgid="3819478292430407705">"Refuzați"</string>
 </resources>
diff --git a/slices/core/src/main/res/values-ru/strings.xml b/slices/core/src/main/res/values-ru/strings.xml
index 43e261f..89dafbc 100644
--- a/slices/core/src/main/res/values-ru/strings.xml
+++ b/slices/core/src/main/res/values-ru/strings.xml
@@ -21,7 +21,7 @@
     <string name="abc_slice_permission_title" msgid="4175332421259324948">"Разрешить приложению \"<xliff:g id="APP_0">%1$s</xliff:g>\" показывать фрагменты приложения \"<xliff:g id="APP_2">%2$s</xliff:g>\"?"</string>
     <string name="abc_slice_permission_text_1" msgid="4525743640399572811">"– Ему станут доступны данные из приложения \"<xliff:g id="APP">%1$s</xliff:g>\"."</string>
     <string name="abc_slice_permission_text_2" msgid="7323565634860251794">"– Оно сможет совершать действия в приложении \"<xliff:g id="APP">%1$s</xliff:g>\"."</string>
-    <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"Разрешить приложению \"<xliff:g id="APP">%1$s</xliff:g>\" показывать фрагменты других приложений"</string>
+
     <string name="abc_slice_permission_allow" msgid="5024599872061409708">"Да"</string>
     <string name="abc_slice_permission_deny" msgid="3819478292430407705">"Нет"</string>
 </resources>
diff --git a/slices/core/src/main/res/values-si/strings.xml b/slices/core/src/main/res/values-si/strings.xml
index 70816e1..e5e8478 100644
--- a/slices/core/src/main/res/values-si/strings.xml
+++ b/slices/core/src/main/res/values-si/strings.xml
@@ -21,7 +21,7 @@
     <string name="abc_slice_permission_title" msgid="4175332421259324948">"<xliff:g id="APP_0">%1$s</xliff:g> හට කොටස් <xliff:g id="APP_2">%2$s</xliff:g>ක් පෙන්වීමට ඉඩ දෙන්නද?"</string>
     <string name="abc_slice_permission_text_1" msgid="4525743640399572811">"- එයට <xliff:g id="APP">%1$s</xliff:g> වෙතින් තොරතුරු කියවිය හැකිය"</string>
     <string name="abc_slice_permission_text_2" msgid="7323565634860251794">"- එයට <xliff:g id="APP">%1$s</xliff:g> ඇතුළත ක්‍රියාමාර්ග ගත හැකිය"</string>
-    <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"ඕනෑම යෙදුමකින් කොටස් පෙන්වීමට <xliff:g id="APP">%1$s</xliff:g> හට ඉඩ දෙන්න"</string>
+
     <string name="abc_slice_permission_allow" msgid="5024599872061409708">"ඉඩ දෙන්න"</string>
     <string name="abc_slice_permission_deny" msgid="3819478292430407705">"ප්‍රතික්ෂේප කර."</string>
 </resources>
diff --git a/slices/core/src/main/res/values-sk/strings.xml b/slices/core/src/main/res/values-sk/strings.xml
index 4937a6c..8070718 100644
--- a/slices/core/src/main/res/values-sk/strings.xml
+++ b/slices/core/src/main/res/values-sk/strings.xml
@@ -21,7 +21,7 @@
     <string name="abc_slice_permission_title" msgid="4175332421259324948">"Povoliť aplikácii <xliff:g id="APP_0">%1$s</xliff:g> zobrazovať rezy z aplikácie <xliff:g id="APP_2">%2$s</xliff:g>?"</string>
     <string name="abc_slice_permission_text_1" msgid="4525743640399572811">"– Môže čítať informácie z aplikácie <xliff:g id="APP">%1$s</xliff:g>"</string>
     <string name="abc_slice_permission_text_2" msgid="7323565634860251794">"– Môže vykonávať akcie v aplikácii <xliff:g id="APP">%1$s</xliff:g>"</string>
-    <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"Povoliť aplikácii <xliff:g id="APP">%1$s</xliff:g> zobrazovať rezy z ľubovoľnej aplikácie"</string>
+
     <string name="abc_slice_permission_allow" msgid="5024599872061409708">"Povoliť"</string>
     <string name="abc_slice_permission_deny" msgid="3819478292430407705">"Zamietnuť"</string>
 </resources>
diff --git a/slices/core/src/main/res/values-sl/strings.xml b/slices/core/src/main/res/values-sl/strings.xml
index 30f67c4..b3ab6f4 100644
--- a/slices/core/src/main/res/values-sl/strings.xml
+++ b/slices/core/src/main/res/values-sl/strings.xml
@@ -21,7 +21,7 @@
     <string name="abc_slice_permission_title" msgid="4175332421259324948">"Ali aplikaciji <xliff:g id="APP_0">%1$s</xliff:g> dovolite prikazovanje izrezov iz aplikacije <xliff:g id="APP_2">%2$s</xliff:g>?"</string>
     <string name="abc_slice_permission_text_1" msgid="4525743640399572811">"– lahko bere podatke v aplikaciji <xliff:g id="APP">%1$s</xliff:g>"</string>
     <string name="abc_slice_permission_text_2" msgid="7323565634860251794">"– lahko izvaja dejanja v aplikaciji <xliff:g id="APP">%1$s</xliff:g>"</string>
-    <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"Dovoli, da aplikacija <xliff:g id="APP">%1$s</xliff:g> prikaže izreze iz poljubne aplikacije"</string>
+
     <string name="abc_slice_permission_allow" msgid="5024599872061409708">"Dovoli"</string>
     <string name="abc_slice_permission_deny" msgid="3819478292430407705">"Zavrni"</string>
 </resources>
diff --git a/slices/core/src/main/res/values-sq/strings.xml b/slices/core/src/main/res/values-sq/strings.xml
index 47ce934..9ae77dd 100644
--- a/slices/core/src/main/res/values-sq/strings.xml
+++ b/slices/core/src/main/res/values-sq/strings.xml
@@ -21,7 +21,7 @@
     <string name="abc_slice_permission_title" msgid="4175332421259324948">"Të lejohet <xliff:g id="APP_0">%1$s</xliff:g> që të shfaqë pjesë të <xliff:g id="APP_2">%2$s</xliff:g>?"</string>
     <string name="abc_slice_permission_text_1" msgid="4525743640399572811">"- Mund të lexojë informacion nga <xliff:g id="APP">%1$s</xliff:g>"</string>
     <string name="abc_slice_permission_text_2" msgid="7323565634860251794">"- Mund të ndërmarrë veprime brenda <xliff:g id="APP">%1$s</xliff:g>"</string>
-    <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"Lejo <xliff:g id="APP">%1$s</xliff:g> për të shfaqur pjesë nga çdo aplikacion"</string>
+
     <string name="abc_slice_permission_allow" msgid="5024599872061409708">"Lejo"</string>
     <string name="abc_slice_permission_deny" msgid="3819478292430407705">"Refuzo"</string>
 </resources>
diff --git a/slices/core/src/main/res/values-sr/strings.xml b/slices/core/src/main/res/values-sr/strings.xml
index e059b1c..b7c29c5 100644
--- a/slices/core/src/main/res/values-sr/strings.xml
+++ b/slices/core/src/main/res/values-sr/strings.xml
@@ -21,7 +21,7 @@
     <string name="abc_slice_permission_title" msgid="4175332421259324948">"Желите ли да дозволите апликацији <xliff:g id="APP_0">%1$s</xliff:g> да приказује исечке из апликације <xliff:g id="APP_2">%2$s</xliff:g>?"</string>
     <string name="abc_slice_permission_text_1" msgid="4525743640399572811">"– Може да чита податке из апликације <xliff:g id="APP">%1$s</xliff:g>"</string>
     <string name="abc_slice_permission_text_2" msgid="7323565634860251794">"– Може да обавља радње у апликацији <xliff:g id="APP">%1$s</xliff:g>"</string>
-    <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"Дозволи апликацији <xliff:g id="APP">%1$s</xliff:g> да приказује исечке из било које апликације"</string>
+
     <string name="abc_slice_permission_allow" msgid="5024599872061409708">"Дозволи"</string>
     <string name="abc_slice_permission_deny" msgid="3819478292430407705">"Одбиј"</string>
 </resources>
diff --git a/slices/core/src/main/res/values-sv/strings.xml b/slices/core/src/main/res/values-sv/strings.xml
index e065cb3..9be28b3 100644
--- a/slices/core/src/main/res/values-sv/strings.xml
+++ b/slices/core/src/main/res/values-sv/strings.xml
@@ -21,7 +21,7 @@
     <string name="abc_slice_permission_title" msgid="4175332421259324948">"Tillåter du att bitar av <xliff:g id="APP_2">%2$s</xliff:g> visas i <xliff:g id="APP_0">%1$s</xliff:g>?"</string>
     <string name="abc_slice_permission_text_1" msgid="4525743640399572811">"– Kan läsa information från <xliff:g id="APP">%1$s</xliff:g>"</string>
     <string name="abc_slice_permission_text_2" msgid="7323565634860251794">"– Kan vidta åtgärder i <xliff:g id="APP">%1$s</xliff:g>"</string>
-    <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"Tillåt att bitar av vilken app som helst visas i <xliff:g id="APP">%1$s</xliff:g>"</string>
+
     <string name="abc_slice_permission_allow" msgid="5024599872061409708">"Tillåt"</string>
     <string name="abc_slice_permission_deny" msgid="3819478292430407705">"Neka"</string>
 </resources>
diff --git a/slices/core/src/main/res/values-sw/strings.xml b/slices/core/src/main/res/values-sw/strings.xml
index 2365547..28459cd 100644
--- a/slices/core/src/main/res/values-sw/strings.xml
+++ b/slices/core/src/main/res/values-sw/strings.xml
@@ -21,7 +21,7 @@
     <string name="abc_slice_permission_title" msgid="4175332421259324948">"Ungependa kuruhusu <xliff:g id="APP_0">%1$s</xliff:g> ionyeshe vipengee <xliff:g id="APP_2">%2$s</xliff:g>?"</string>
     <string name="abc_slice_permission_text_1" msgid="4525743640399572811">"- Inaweza kusoma maelezo kutoka <xliff:g id="APP">%1$s</xliff:g>"</string>
     <string name="abc_slice_permission_text_2" msgid="7323565634860251794">"- Inaweza kuchukua hatua ndani ya <xliff:g id="APP">%1$s</xliff:g>"</string>
-    <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"Ruhusu <xliff:g id="APP">%1$s</xliff:g> ionyeshe vipengee kutoka programu yoyote"</string>
+
     <string name="abc_slice_permission_allow" msgid="5024599872061409708">"Ruhusu"</string>
     <string name="abc_slice_permission_deny" msgid="3819478292430407705">"Kataa"</string>
 </resources>
diff --git a/slices/core/src/main/res/values-ta/strings.xml b/slices/core/src/main/res/values-ta/strings.xml
index 0c8d874..36be75a 100644
--- a/slices/core/src/main/res/values-ta/strings.xml
+++ b/slices/core/src/main/res/values-ta/strings.xml
@@ -21,7 +21,7 @@
     <string name="abc_slice_permission_title" msgid="4175332421259324948">"<xliff:g id="APP_2">%2$s</xliff:g> ஆப்ஸின் விழிப்பூட்டல்களைக் காண்பிக்க, <xliff:g id="APP_0">%1$s</xliff:g> ஆப்ஸை அனுமதிக்கவா?"</string>
     <string name="abc_slice_permission_text_1" msgid="4525743640399572811">"- இது, <xliff:g id="APP">%1$s</xliff:g> பயன்பாட்டிலிருக்கும் தகவலைப் படிக்கும்"</string>
     <string name="abc_slice_permission_text_2" msgid="7323565634860251794">"- இது, <xliff:g id="APP">%1$s</xliff:g> பயன்பாட்டிற்குள் செயல்பாடுகளில் ஈடுபடும்"</string>
-    <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"எந்தப் பயன்பாட்டிலிருந்தும் விழிப்பூட்டல்களைக் காண்பிக்க, <xliff:g id="APP">%1$s</xliff:g> ஆப்ஸை அனுமதி"</string>
+
     <string name="abc_slice_permission_allow" msgid="5024599872061409708">"அனுமதி"</string>
     <string name="abc_slice_permission_deny" msgid="3819478292430407705">"நிராகரி"</string>
 </resources>
diff --git a/slices/core/src/main/res/values-te/strings.xml b/slices/core/src/main/res/values-te/strings.xml
index 3f89a75..ce51a47 100644
--- a/slices/core/src/main/res/values-te/strings.xml
+++ b/slices/core/src/main/res/values-te/strings.xml
@@ -21,7 +21,7 @@
     <string name="abc_slice_permission_title" msgid="4175332421259324948">"<xliff:g id="APP_2">%2$s</xliff:g> స్లైస్‌లను చూపించడానికి <xliff:g id="APP_0">%1$s</xliff:g>ని అనుమతించాలా?"</string>
     <string name="abc_slice_permission_text_1" msgid="4525743640399572811">"- ఇది <xliff:g id="APP">%1$s</xliff:g> నుండి సమాచారాన్ని చదవగలుగుతుంది"</string>
     <string name="abc_slice_permission_text_2" msgid="7323565634860251794">"- ఇది <xliff:g id="APP">%1$s</xliff:g> లోపల చర్యలు తీసుకోగలుగుతుంది"</string>
-    <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"ఏ యాప్ నుండి అయినా స్లైస్‌లను చూపించడానికి <xliff:g id="APP">%1$s</xliff:g>ని అనుమతించండి"</string>
+
     <string name="abc_slice_permission_allow" msgid="5024599872061409708">"అనుమతించు"</string>
     <string name="abc_slice_permission_deny" msgid="3819478292430407705">"తిరస్కరించు"</string>
 </resources>
diff --git a/slices/core/src/main/res/values-th/strings.xml b/slices/core/src/main/res/values-th/strings.xml
index afc42f2..07371a0 100644
--- a/slices/core/src/main/res/values-th/strings.xml
+++ b/slices/core/src/main/res/values-th/strings.xml
@@ -21,7 +21,7 @@
     <string name="abc_slice_permission_title" msgid="4175332421259324948">"อนุญาตให้ <xliff:g id="APP_0">%1$s</xliff:g> แสดงส่วนต่างๆ ของ <xliff:g id="APP_2">%2$s</xliff:g>"</string>
     <string name="abc_slice_permission_text_1" msgid="4525743640399572811">"- อ่านข้อมูลจาก <xliff:g id="APP">%1$s</xliff:g> ได้"</string>
     <string name="abc_slice_permission_text_2" msgid="7323565634860251794">"- ดำเนินการใน <xliff:g id="APP">%1$s</xliff:g> ได้"</string>
-    <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"อนุญาตให้ <xliff:g id="APP">%1$s</xliff:g> แสดงส่วนต่างๆ จากแอปใดก็ได้"</string>
+
     <string name="abc_slice_permission_allow" msgid="5024599872061409708">"อนุญาต"</string>
     <string name="abc_slice_permission_deny" msgid="3819478292430407705">"ปฏิเสธ"</string>
 </resources>
diff --git a/slices/core/src/main/res/values-tl/strings.xml b/slices/core/src/main/res/values-tl/strings.xml
index aa9d7b4..e4bf3e0 100644
--- a/slices/core/src/main/res/values-tl/strings.xml
+++ b/slices/core/src/main/res/values-tl/strings.xml
@@ -21,7 +21,7 @@
     <string name="abc_slice_permission_title" msgid="4175332421259324948">"Payagan ang <xliff:g id="APP_0">%1$s</xliff:g> na ipakita ang mga slice ng <xliff:g id="APP_2">%2$s</xliff:g>?"</string>
     <string name="abc_slice_permission_text_1" msgid="4525743640399572811">"- Nakakabasa ito ng impormasyon mula sa <xliff:g id="APP">%1$s</xliff:g>"</string>
     <string name="abc_slice_permission_text_2" msgid="7323565634860251794">"- Nakakagawa ito ng mga pagkilos sa loob ng <xliff:g id="APP">%1$s</xliff:g>"</string>
-    <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"Payagan ang <xliff:g id="APP">%1$s</xliff:g> na ipakita ang mga slice mula sa anumang app"</string>
+
     <string name="abc_slice_permission_allow" msgid="5024599872061409708">"Payagan"</string>
     <string name="abc_slice_permission_deny" msgid="3819478292430407705">"Tanggihan"</string>
 </resources>
diff --git a/slices/core/src/main/res/values-tr/strings.xml b/slices/core/src/main/res/values-tr/strings.xml
index 216a87c..2cbcfda 100644
--- a/slices/core/src/main/res/values-tr/strings.xml
+++ b/slices/core/src/main/res/values-tr/strings.xml
@@ -21,7 +21,7 @@
     <string name="abc_slice_permission_title" msgid="4175332421259324948">"<xliff:g id="APP_0">%1$s</xliff:g> uygulamasının, <xliff:g id="APP_2">%2$s</xliff:g> dilimlerini göstermesine izin verilsin mi?"</string>
     <string name="abc_slice_permission_text_1" msgid="4525743640399572811">"- <xliff:g id="APP">%1$s</xliff:g> uygulamasından bilgileri okuyabilir"</string>
     <string name="abc_slice_permission_text_2" msgid="7323565634860251794">"- <xliff:g id="APP">%1$s</xliff:g> uygulamasında işlem yapabilir"</string>
-    <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"<xliff:g id="APP">%1$s</xliff:g> uygulamasının tüm uygulamalardan dilimleri göstermesine izin ver"</string>
+
     <string name="abc_slice_permission_allow" msgid="5024599872061409708">"İzin ver"</string>
     <string name="abc_slice_permission_deny" msgid="3819478292430407705">"Reddet"</string>
 </resources>
diff --git a/slices/core/src/main/res/values-uk/strings.xml b/slices/core/src/main/res/values-uk/strings.xml
index e908f43..b842f5c 100644
--- a/slices/core/src/main/res/values-uk/strings.xml
+++ b/slices/core/src/main/res/values-uk/strings.xml
@@ -21,7 +21,7 @@
     <string name="abc_slice_permission_title" msgid="4175332421259324948">"Дозволити додатку <xliff:g id="APP_0">%1$s</xliff:g> показувати фрагменти додатка <xliff:g id="APP_2">%2$s</xliff:g>?"</string>
     <string name="abc_slice_permission_text_1" msgid="4525743640399572811">"- Може переглядати інформацію з додатка <xliff:g id="APP">%1$s</xliff:g>"</string>
     <string name="abc_slice_permission_text_2" msgid="7323565634860251794">"- Може виконувати дії в додатку <xliff:g id="APP">%1$s</xliff:g>"</string>
-    <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"Дозволити додатку <xliff:g id="APP">%1$s</xliff:g> показувати фрагменти будь-якого додатка"</string>
+
     <string name="abc_slice_permission_allow" msgid="5024599872061409708">"Дозволити"</string>
     <string name="abc_slice_permission_deny" msgid="3819478292430407705">"Заборонити"</string>
 </resources>
diff --git a/slices/core/src/main/res/values-ur/strings.xml b/slices/core/src/main/res/values-ur/strings.xml
index 0e97e52..8999e25 100644
--- a/slices/core/src/main/res/values-ur/strings.xml
+++ b/slices/core/src/main/res/values-ur/strings.xml
@@ -21,7 +21,7 @@
     <string name="abc_slice_permission_title" msgid="4175332421259324948">"<xliff:g id="APP_0">%1$s</xliff:g> کو <xliff:g id="APP_2">%2$s</xliff:g> کے سلائسز دکھانے کی اجازت دیں؟"</string>
     <string name="abc_slice_permission_text_1" msgid="4525743640399572811">"- یہ <xliff:g id="APP">%1$s</xliff:g> کی معلومات پڑھ سکتا ہے"</string>
     <string name="abc_slice_permission_text_2" msgid="7323565634860251794">"- یہ <xliff:g id="APP">%1$s</xliff:g> کے اندر کارروائیاں کر سکتا ہے"</string>
-    <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"<xliff:g id="APP">%1$s</xliff:g> کو کسی بھی ایپ سے سلائسز دکھانے کی اجازت دیں"</string>
+
     <string name="abc_slice_permission_allow" msgid="5024599872061409708">"اجازت دیں"</string>
     <string name="abc_slice_permission_deny" msgid="3819478292430407705">"مسترد کریں"</string>
 </resources>
diff --git a/slices/core/src/main/res/values-uz/strings.xml b/slices/core/src/main/res/values-uz/strings.xml
index 8de5b2e..9fcb4ce 100644
--- a/slices/core/src/main/res/values-uz/strings.xml
+++ b/slices/core/src/main/res/values-uz/strings.xml
@@ -21,7 +21,7 @@
     <string name="abc_slice_permission_title" msgid="4175332421259324948">"<xliff:g id="APP_0">%1$s</xliff:g> ilovasiga <xliff:g id="APP_2">%2$s</xliff:g> ilovasidan fragmentlar ko‘rsatishga ruxsat berilsinmi?"</string>
     <string name="abc_slice_permission_text_1" msgid="4525743640399572811">"– <xliff:g id="APP">%1$s</xliff:g> ma’lumotlarini o‘qiy oladi"</string>
     <string name="abc_slice_permission_text_2" msgid="7323565634860251794">"– <xliff:g id="APP">%1$s</xliff:g> ichida amallar bajara oladi"</string>
-    <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"<xliff:g id="APP">%1$s</xliff:g> ilovasiga boshqa ilovalardan fragmentlarni ko‘rsatishga ruxsat berish"</string>
+
     <string name="abc_slice_permission_allow" msgid="5024599872061409708">"Ruxsat"</string>
     <string name="abc_slice_permission_deny" msgid="3819478292430407705">"Rad etish"</string>
 </resources>
diff --git a/slices/core/src/main/res/values-vi/strings.xml b/slices/core/src/main/res/values-vi/strings.xml
index 8ecd246..c067ac8 100644
--- a/slices/core/src/main/res/values-vi/strings.xml
+++ b/slices/core/src/main/res/values-vi/strings.xml
@@ -21,7 +21,7 @@
     <string name="abc_slice_permission_title" msgid="4175332421259324948">"Cho phép <xliff:g id="APP_0">%1$s</xliff:g> hiển thị các lát của <xliff:g id="APP_2">%2$s</xliff:g>?"</string>
     <string name="abc_slice_permission_text_1" msgid="4525743640399572811">"- Có thể đọc thông tin từ <xliff:g id="APP">%1$s</xliff:g>"</string>
     <string name="abc_slice_permission_text_2" msgid="7323565634860251794">"- Có thể thực hiện hành động bên trong <xliff:g id="APP">%1$s</xliff:g>"</string>
-    <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"Cho phép <xliff:g id="APP">%1$s</xliff:g> hiển thị các lát từ mọi ứng dụng"</string>
+
     <string name="abc_slice_permission_allow" msgid="5024599872061409708">"Cho phép"</string>
     <string name="abc_slice_permission_deny" msgid="3819478292430407705">"Từ chối"</string>
 </resources>
diff --git a/slices/core/src/main/res/values-zh-rCN/strings.xml b/slices/core/src/main/res/values-zh-rCN/strings.xml
index 3a99e82..6ab2d3e 100644
--- a/slices/core/src/main/res/values-zh-rCN/strings.xml
+++ b/slices/core/src/main/res/values-zh-rCN/strings.xml
@@ -21,7 +21,7 @@
     <string name="abc_slice_permission_title" msgid="4175332421259324948">"要允许“<xliff:g id="APP_0">%1$s</xliff:g>”显示“<xliff:g id="APP_2">%2$s</xliff:g>”图块吗?"</string>
     <string name="abc_slice_permission_text_1" msgid="4525743640399572811">"- 可以读取“<xliff:g id="APP">%1$s</xliff:g>”中的信息"</string>
     <string name="abc_slice_permission_text_2" msgid="7323565634860251794">"- 可以在“<xliff:g id="APP">%1$s</xliff:g>”内执行操作"</string>
-    <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"允许“<xliff:g id="APP">%1$s</xliff:g>”显示任何应用的图块"</string>
+
     <string name="abc_slice_permission_allow" msgid="5024599872061409708">"允许"</string>
     <string name="abc_slice_permission_deny" msgid="3819478292430407705">"拒绝"</string>
 </resources>
diff --git a/slices/core/src/main/res/values-zh-rHK/strings.xml b/slices/core/src/main/res/values-zh-rHK/strings.xml
index bb478fc..5cf6943 100644
--- a/slices/core/src/main/res/values-zh-rHK/strings.xml
+++ b/slices/core/src/main/res/values-zh-rHK/strings.xml
@@ -21,7 +21,7 @@
     <string name="abc_slice_permission_title" msgid="4175332421259324948">"要允許「<xliff:g id="APP_0">%1$s</xliff:g>」顯示「<xliff:g id="APP_2">%2$s</xliff:g>」的快訊嗎?"</string>
     <string name="abc_slice_permission_text_1" msgid="4525743640399572811">"- 可以讀取「<xliff:g id="APP">%1$s</xliff:g>」中的資料"</string>
     <string name="abc_slice_permission_text_2" msgid="7323565634860251794">"- 可以在「<xliff:g id="APP">%1$s</xliff:g>」內執行操作"</string>
-    <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"允許「<xliff:g id="APP">%1$s</xliff:g>」顯示任何應用程式的快訊"</string>
+
     <string name="abc_slice_permission_allow" msgid="5024599872061409708">"允許"</string>
     <string name="abc_slice_permission_deny" msgid="3819478292430407705">"拒絕"</string>
 </resources>
diff --git a/slices/core/src/main/res/values-zh-rTW/strings.xml b/slices/core/src/main/res/values-zh-rTW/strings.xml
index caeeff1..df1ab8b 100644
--- a/slices/core/src/main/res/values-zh-rTW/strings.xml
+++ b/slices/core/src/main/res/values-zh-rTW/strings.xml
@@ -21,7 +21,7 @@
     <string name="abc_slice_permission_title" msgid="4175332421259324948">"要允許「<xliff:g id="APP_0">%1$s</xliff:g>」顯示「<xliff:g id="APP_2">%2$s</xliff:g>」的區塊嗎?"</string>
     <string name="abc_slice_permission_text_1" msgid="4525743640399572811">"- 它可以讀取「<xliff:g id="APP">%1$s</xliff:g>」的資訊"</string>
     <string name="abc_slice_permission_text_2" msgid="7323565634860251794">"- 它可以在「<xliff:g id="APP">%1$s</xliff:g>」內執行操作"</string>
-    <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"允許「<xliff:g id="APP">%1$s</xliff:g>」顯示任何應用程式的區塊"</string>
+
     <string name="abc_slice_permission_allow" msgid="5024599872061409708">"允許"</string>
     <string name="abc_slice_permission_deny" msgid="3819478292430407705">"拒絕"</string>
 </resources>
diff --git a/slices/core/src/main/res/values-zu/strings.xml b/slices/core/src/main/res/values-zu/strings.xml
index 940334c..d9ada48 100644
--- a/slices/core/src/main/res/values-zu/strings.xml
+++ b/slices/core/src/main/res/values-zu/strings.xml
@@ -21,7 +21,7 @@
     <string name="abc_slice_permission_title" msgid="4175332421259324948">"Vumela i-<xliff:g id="APP_0">%1$s</xliff:g> ukuthi ibonise izingcezu ze-<xliff:g id="APP_2">%2$s</xliff:g>?"</string>
     <string name="abc_slice_permission_text_1" msgid="4525743640399572811">"- Ingafunda ulwazi kusukela ku-<xliff:g id="APP">%1$s</xliff:g>"</string>
     <string name="abc_slice_permission_text_2" msgid="7323565634860251794">"- Ingenza izenzo ngaphakathi kwe-<xliff:g id="APP">%1$s</xliff:g>"</string>
-    <string name="abc_slice_permission_checkbox" msgid="5696872682700058611">"Vumela i-<xliff:g id="APP">%1$s</xliff:g> ukuthi ikubonise izingcezu kusukela kunoma iluphi uhlelo lokusebenza"</string>
+
     <string name="abc_slice_permission_allow" msgid="5024599872061409708">"Vumela"</string>
     <string name="abc_slice_permission_deny" msgid="3819478292430407705">"Phika"</string>
 </resources>
diff --git a/slices/core/src/main/res/values/strings.xml b/slices/core/src/main/res/values/strings.xml
index 2959af8..6f96605 100644
--- a/slices/core/src/main/res/values/strings.xml
+++ b/slices/core/src/main/res/values/strings.xml
@@ -28,9 +28,6 @@
     <!-- Description of what kind of access is given to a slice host [CHAR LIMIT=NONE] -->
     <string name="abc_slice_permission_text_2"> - It can take actions inside <xliff:g id="app" example="Example App">%1$s</xliff:g></string>
 
-    <!-- Text on checkbox allowing the app to show slices from all apps [CHAR LIMIT=NONE] -->
-    <string name="abc_slice_permission_checkbox">Allow <xliff:g id="app" example="Example App">%1$s</xliff:g> to show slices from any app</string>
-
     <!-- Option to grant the slice permission request on the screen [CHAR LIMIT=15] -->
     <string name="abc_slice_permission_allow">Allow</string>
 
diff --git a/slices/remotecallback/lint-baseline.xml b/slices/remotecallback/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/slices/remotecallback/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/sqlite/sqlite-ktx/lint-baseline.xml b/sqlite/sqlite-ktx/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/sqlite/sqlite-ktx/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/startup/integration-tests/first-library/lint-baseline.xml b/startup/integration-tests/first-library/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/startup/integration-tests/first-library/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/startup/integration-tests/second-library/lint-baseline.xml b/startup/integration-tests/second-library/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/startup/integration-tests/second-library/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/startup/integration-tests/test-app/lint-baseline.xml b/startup/integration-tests/test-app/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/startup/integration-tests/test-app/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/startup/startup-runtime-lint/lint-baseline.xml b/startup/startup-runtime-lint/lint-baseline.xml
deleted file mode 100644
index 8794ae8..0000000
--- a/startup/startup-runtime-lint/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" version="4.2.0-beta06">
-
-</issues>
diff --git a/startup/startup-runtime/lint-baseline.xml b/startup/startup-runtime/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/startup/startup-runtime/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/testutils/testutils-appcompat/lint-baseline.xml b/testutils/testutils-appcompat/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/testutils/testutils-appcompat/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/testutils/testutils-gradle-plugin/lint-baseline.xml b/testutils/testutils-gradle-plugin/lint-baseline.xml
deleted file mode 100644
index 8794ae8..0000000
--- a/testutils/testutils-gradle-plugin/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" version="4.2.0-beta06">
-
-</issues>
diff --git a/testutils/testutils-macrobenchmark/OWNERS b/testutils/testutils-macrobenchmark/OWNERS
new file mode 100644
index 0000000..bd5d7e4
--- /dev/null
+++ b/testutils/testutils-macrobenchmark/OWNERS
@@ -0,0 +1 @@
+ccraik@google.com
\ No newline at end of file
diff --git a/testutils/testutils-macrobenchmark/build.gradle b/testutils/testutils-macrobenchmark/build.gradle
new file mode 100644
index 0000000..d331ce5
--- /dev/null
+++ b/testutils/testutils-macrobenchmark/build.gradle
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2018 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.
+ */
+
+
+import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
+
+import static androidx.build.dependencies.DependenciesKt.*
+
+plugins {
+    id("AndroidXPlugin")
+    id("com.android.library")
+    id("kotlin-android")
+}
+
+dependencies {
+    implementation(project(":benchmark:benchmark-macro"))
+    implementation(project(":benchmark:benchmark-macro-junit4"))
+
+    implementation(KOTLIN_STDLIB)
+}
+
+android {
+    defaultConfig {
+        minSdkVersion 18
+    }
+}
diff --git a/testutils/testutils-macrobenchmark/src/main/AndroidManifest.xml b/testutils/testutils-macrobenchmark/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..e78b95a
--- /dev/null
+++ b/testutils/testutils-macrobenchmark/src/main/AndroidManifest.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  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.
+  -->
+<manifest package="androidx.testutils"/>
diff --git a/compose/integration-tests/macrobenchmark/src/androidTest/java/androidx/compose/integration/macrobenchmark/MacrobenchUtils.kt b/testutils/testutils-macrobenchmark/src/main/java/androidx/testutils/MacrobenchUtils.kt
similarity index 92%
rename from compose/integration-tests/macrobenchmark/src/androidTest/java/androidx/compose/integration/macrobenchmark/MacrobenchUtils.kt
rename to testutils/testutils-macrobenchmark/src/main/java/androidx/testutils/MacrobenchUtils.kt
index f893da1..2ec05dd 100644
--- a/compose/integration-tests/macrobenchmark/src/androidTest/java/androidx/compose/integration/macrobenchmark/MacrobenchUtils.kt
+++ b/testutils/testutils-macrobenchmark/src/main/java/androidx/testutils/MacrobenchUtils.kt
@@ -14,24 +14,25 @@
  * limitations under the License.
  */
 
-package androidx.compose.integration.macrobenchmark
+package androidx.testutils
 
 import android.content.Intent
+import androidx.annotation.RequiresApi
 import androidx.benchmark.macro.CompilationMode
 import androidx.benchmark.macro.StartupMode
 import androidx.benchmark.macro.StartupTimingMetric
 import androidx.benchmark.macro.isSupportedWithVmSettings
 import androidx.benchmark.macro.junit4.MacrobenchmarkRule
 
-const val TARGET_PACKAGE = "androidx.compose.integration.macrobenchmark.target"
-
+@RequiresApi(29)
 fun MacrobenchmarkRule.measureStartup(
     compilationMode: CompilationMode,
     startupMode: StartupMode,
+    packageName: String,
     iterations: Int = 3,
     setupIntent: Intent.() -> Unit = {}
 ) = measureRepeated(
-    packageName = TARGET_PACKAGE,
+    packageName = packageName,
     metrics = listOf(StartupTimingMetric()),
     compilationMode = compilationMode,
     iterations = iterations,
@@ -39,7 +40,7 @@
 ) {
     pressHome()
     val intent = Intent()
-    intent.setPackage(TARGET_PACKAGE)
+    intent.setPackage(packageName)
     setupIntent(intent)
     startActivityAndWait(intent)
 }
diff --git a/testutils/testutils-mockito/lint-baseline.xml b/testutils/testutils-mockito/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/testutils/testutils-mockito/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/testutils/testutils-navigation/build.gradle b/testutils/testutils-navigation/build.gradle
index a1494e1..61664a7 100644
--- a/testutils/testutils-navigation/build.gradle
+++ b/testutils/testutils-navigation/build.gradle
@@ -30,6 +30,7 @@
     api(projectOrArtifact(":navigation:navigation-common"))
 
     testImplementation(projectOrArtifact(":navigation:navigation-testing"))
+    testImplementation("androidx.arch.core:core-testing:2.1.0")
     testImplementation(JUNIT)
     testImplementation(MOCKITO_CORE)
 
diff --git a/testutils/testutils-navigation/src/androidTest/java/androidx/testutils/TestNavigatorDestinationBuilderTest.kt b/testutils/testutils-navigation/src/androidTest/java/androidx/testutils/TestNavigatorDestinationBuilderTest.kt
index 198a897..fb2d187 100644
--- a/testutils/testutils-navigation/src/androidTest/java/androidx/testutils/TestNavigatorDestinationBuilderTest.kt
+++ b/testutils/testutils-navigation/src/androidTest/java/androidx/testutils/TestNavigatorDestinationBuilderTest.kt
@@ -43,6 +43,17 @@
     }
 
     @Test
+    fun testRoute() {
+        val graph = provider.navigation(startDestination = DESTINATION_ROUTE) {
+            test(DESTINATION_ROUTE)
+        }
+        assertTrue(
+            "Destination should be added to the graph",
+            DESTINATION_ROUTE in graph
+        )
+    }
+
+    @Test
     fun testWithBody() {
         val graph = provider.navigation(startDestination = DESTINATION_ID) {
             test(DESTINATION_ID) {
@@ -58,7 +69,25 @@
             LABEL, graph[DESTINATION_ID].label
         )
     }
+
+    @Test
+    fun testRouteWithBody() {
+        val graph = provider.navigation(startDestination = DESTINATION_ROUTE) {
+            test(DESTINATION_ROUTE) {
+                label = LABEL
+            }
+        }
+        assertTrue(
+            "Destination should be added to the graph",
+            DESTINATION_ROUTE in graph
+        )
+        assertEquals(
+            "Destination should have label set",
+            LABEL, graph[DESTINATION_ROUTE].label
+        )
+    }
 }
 
 private const val DESTINATION_ID = 1
+private const val DESTINATION_ROUTE = "route"
 private const val LABEL = "Test"
diff --git a/testutils/testutils-navigation/src/main/java/androidx/testutils/TestNavigatorDestinationBuilder.kt b/testutils/testutils-navigation/src/main/java/androidx/testutils/TestNavigatorDestinationBuilder.kt
index bcda994..f8b90e7 100644
--- a/testutils/testutils-navigation/src/main/java/androidx/testutils/TestNavigatorDestinationBuilder.kt
+++ b/testutils/testutils-navigation/src/main/java/androidx/testutils/TestNavigatorDestinationBuilder.kt
@@ -32,6 +32,11 @@
 /**
  * Construct a new [TestNavigator.Destination]
  */
+inline fun NavGraphBuilder.test(route: String) = test(route) {}
+
+/**
+ * Construct a new [TestNavigator.Destination]
+ */
 inline fun NavGraphBuilder.test(
     @IdRes id: Int,
     builder: TestNavigatorDestinationBuilder.() -> Unit
@@ -43,10 +48,20 @@
 )
 
 /**
+ * Construct a new [TestNavigator.Destination]
+ */
+inline fun NavGraphBuilder.test(
+    route: String,
+    builder: TestNavigatorDestinationBuilder.() -> Unit
+) = destination(
+    TestNavigatorDestinationBuilder(provider[TestNavigator::class], route).apply(builder)
+)
+
+/**
  * DSL for constructing a new [TestNavigator.Destination]
  */
 @NavDestinationDsl
-class TestNavigatorDestinationBuilder(
-    navigator: TestNavigator,
-    @IdRes id: Int
-) : NavDestinationBuilder<TestNavigator.Destination>(navigator, id)
+class TestNavigatorDestinationBuilder : NavDestinationBuilder<TestNavigator.Destination> {
+    constructor(navigator: TestNavigator, @IdRes id: Int = 0) : super(navigator, id)
+    constructor(navigator: TestNavigator, route: String) : super(navigator, route)
+}
diff --git a/testutils/testutils-navigation/src/test/java/androidx/testutils/TestNavigatorTest.kt b/testutils/testutils-navigation/src/test/java/androidx/testutils/TestNavigatorTest.kt
index 454883a..ce8b38d 100644
--- a/testutils/testutils-navigation/src/test/java/androidx/testutils/TestNavigatorTest.kt
+++ b/testutils/testutils-navigation/src/test/java/androidx/testutils/TestNavigatorTest.kt
@@ -17,8 +17,10 @@
 package androidx.testutils
 
 import android.os.Bundle
+import androidx.arch.core.executor.testing.InstantTaskExecutorRule
 import androidx.navigation.testing.TestNavigatorState
 import org.junit.Assert.assertEquals
+import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.JUnit4
@@ -26,6 +28,9 @@
 @RunWith(JUnit4::class)
 class TestNavigatorTest {
 
+    @get:Rule
+    val instantTaskExecutorRule = InstantTaskExecutorRule()
+
     @Test
     fun backStack() {
         val testNavigator = TestNavigator()
diff --git a/testutils/testutils-truth/lint-baseline.xml b/testutils/testutils-truth/lint-baseline.xml
deleted file mode 100644
index 8794ae8..0000000
--- a/testutils/testutils-truth/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" version="4.2.0-beta06">
-
-</issues>
diff --git a/text/text/src/main/java/androidx/compose/ui/text/android/style/TextDecorationSpan.kt b/text/text/src/main/java/androidx/compose/ui/text/android/style/TextDecorationSpan.kt
new file mode 100644
index 0000000..afa6802
--- /dev/null
+++ b/text/text/src/main/java/androidx/compose/ui/text/android/style/TextDecorationSpan.kt
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.ui.text.android.style
+
+import android.text.TextPaint
+import android.text.style.CharacterStyle
+import androidx.compose.ui.text.android.InternalPlatformTextApi
+
+/**
+ * A span which applies the underline and strike through to the affected text.
+ *
+ * @property isUnderlineText whether to draw the under for the affected text.
+ * @property isStrikethroughText whether to draw strikethrough line for the affected text.
+ * @suppress
+ */
+@InternalPlatformTextApi
+class TextDecorationSpan(
+    val isUnderlineText: Boolean,
+    val isStrikethroughText: Boolean
+) : CharacterStyle() {
+    override fun updateDrawState(textPaint: TextPaint) {
+        textPaint.isUnderlineText = isUnderlineText
+        textPaint.isStrikeThruText = isStrikethroughText
+    }
+}
\ No newline at end of file
diff --git a/transition/transition-ktx/lint-baseline.xml b/transition/transition-ktx/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/transition/transition-ktx/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/vectordrawable/vectordrawable-seekable/lint-baseline.xml b/vectordrawable/vectordrawable-seekable/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/vectordrawable/vectordrawable-seekable/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/wear/compose/foundation/build.gradle b/wear/compose/foundation/build.gradle
index eaa74c9..ad3829e 100644
--- a/wear/compose/foundation/build.gradle
+++ b/wear/compose/foundation/build.gradle
@@ -13,34 +13,26 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-
-
-import androidx.build.RunApiTasks
-
-import static androidx.build.dependencies.DependenciesKt.*
-
 import androidx.build.LibraryGroups
 import androidx.build.LibraryType
 import androidx.build.LibraryVersions
+import androidx.build.RunApiTasks
+import androidx.build.AndroidXUiPlugin
+import static androidx.build.dependencies.DependenciesKt.*
 
 plugins {
     id("AndroidXPlugin")
     id("com.android.library")
+    id("AndroidXUiPlugin")
+    id("org.jetbrains.kotlin.android")
 }
 
+AndroidXUiPlugin.applyAndConfigureKotlinPlugin(project, false)
+
 dependencies {
-    api("androidx.annotation:annotation:1.1.0")
-    api(GUAVA_LISTENABLE_FUTURE)
+    kotlinPlugin(project(":compose:compiler:compiler"))
 
-    implementation 'androidx.annotation:annotation:1.2.0-alpha01'
-
-    testImplementation(ANDROIDX_TEST_EXT_JUNIT)
-    testImplementation(ANDROIDX_TEST_EXT_TRUTH)
-    testImplementation(ANDROIDX_TEST_CORE)
-    testImplementation(ANDROIDX_TEST_RUNNER)
-    testImplementation(ANDROIDX_TEST_RULES)
-    testImplementation(ROBOLECTRIC)
-    testImplementation(MOCKITO_CORE)
+    implementation(KOTLIN_STDLIB)
 }
 
 android {
@@ -50,7 +42,6 @@
     buildTypes.all {
         consumerProguardFiles "proguard-rules.pro"
     }
-
     // Use Robolectric 4.+
     testOptions.unitTests.includeAndroidResources = true
 }
@@ -65,5 +56,6 @@
             "to write Jetpack Compose applications for Wearable devices by providing " +
             "functionality to support wearable specific devices sizes, shapes and navigation " +
             "gestures. It builds upon the Jetpack Compose libraries."
+    targetsJavaConsumers = false
     runApiTasks = new RunApiTasks.No("API tracking disabled while the package is empty")
 }
diff --git a/wear/compose/foundation/src/main/AndroidManifest.xml b/wear/compose/foundation/src/androidMain/AndroidManifest.xml
similarity index 100%
rename from wear/compose/foundation/src/main/AndroidManifest.xml
rename to wear/compose/foundation/src/androidMain/AndroidManifest.xml
diff --git a/wear/compose/material/build.gradle b/wear/compose/material/build.gradle
index fb76b02..62cbb74 100644
--- a/wear/compose/material/build.gradle
+++ b/wear/compose/material/build.gradle
@@ -13,14 +13,12 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-
-
 import androidx.build.LibraryGroups
 import androidx.build.LibraryType
 import androidx.build.LibraryVersions
 import androidx.build.RunApiTasks
-
-import static androidx.build.dependencies.DependenciesKt.getKOTLIN_STDLIB
+import androidx.build.AndroidXUiPlugin
+import static androidx.build.dependencies.DependenciesKt.*
 
 plugins {
     id("AndroidXPlugin")
@@ -29,20 +27,19 @@
     id("org.jetbrains.kotlin.android")
 }
 
+AndroidXUiPlugin.applyAndConfigureKotlinPlugin(project, false)
+
 dependencies {
     kotlinPlugin(project(":compose:compiler:compiler"))
 
+    api(project(":compose:foundation:foundation"))
+    api(project(":compose:ui:ui"))
+    api(project(":compose:ui:ui-text"))
+    api(project(":compose:runtime:runtime"))
+
     implementation(KOTLIN_STDLIB)
-
-    compileOnly(project(":annotation:annotation-sampled"))
-
-    implementation(project(":compose:animation:animation"))
-    implementation(project(":compose:foundation:foundation"))
-    implementation(project(":compose:foundation:foundation-layout"))
     implementation(project(":compose:material:material"))
-    implementation(project(":compose:runtime:runtime"))
-    implementation(project(":compose:ui:ui"))
-    implementation(project(":compose:ui:ui-text"))
+    implementation(project(":compose:material:material-ripple"))
 }
 
 android {
@@ -52,7 +49,6 @@
     buildTypes.all {
         consumerProguardFiles "proguard-rules.pro"
     }
-
     // Use Robolectric 4.+
     testOptions.unitTests.includeAndroidResources = true
 }
@@ -67,5 +63,6 @@
             "to write Jetpack Compose applications for Wearable devices that implement Wear " +
             "Material Design UX guidelines and specifications. It builds upon the Jetpack Compose" +
             " libraries."
+    targetsJavaConsumers = false
     runApiTasks = new RunApiTasks.No("API tracking disabled while the package is empty")
 }
diff --git a/wear/compose/material/src/main/AndroidManifest.xml b/wear/compose/material/src/androidMain/AndroidManifest.xml
similarity index 100%
rename from wear/compose/material/src/main/AndroidManifest.xml
rename to wear/compose/material/src/androidMain/AndroidManifest.xml
diff --git a/wear/compose/material/src/androidMain/kotlin/androidx/wear/compose/material/Colors.kt b/wear/compose/material/src/androidMain/kotlin/androidx/wear/compose/material/Colors.kt
new file mode 100644
index 0000000..892fed5
--- /dev/null
+++ b/wear/compose/material/src/androidMain/kotlin/androidx/wear/compose/material/Colors.kt
@@ -0,0 +1,170 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package androidx.wear.compose.material
+
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.ReadOnlyComposable
+import androidx.compose.runtime.Stable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.setValue
+import androidx.compose.runtime.staticCompositionLocalOf
+import androidx.compose.runtime.structuralEqualityPolicy
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.takeOrElse
+
+@Stable
+public class Colors(
+    primary: Color = Color(0xFFAECBFA),
+    primaryVariant: Color = Color(0xFF669DF6),
+    secondary: Color = Color(0xFFFDE293),
+    secondaryVariant: Color = Color(0xFF594F33),
+    error: Color = Color(0xFFEE675C),
+) {
+    public var primary: Color by mutableStateOf(primary, structuralEqualityPolicy())
+        internal set
+    public var primaryVariant: Color by mutableStateOf(primaryVariant, structuralEqualityPolicy())
+        internal set
+    public var secondary: Color by mutableStateOf(secondary, structuralEqualityPolicy())
+        internal set
+    public var secondaryVariant: Color by mutableStateOf(
+        secondaryVariant,
+        structuralEqualityPolicy()
+    )
+        internal set
+    public val background: Color = Color.Black
+    public val surface: Color = Color(0xFF202124)
+    public var error: Color by mutableStateOf(error, structuralEqualityPolicy())
+        internal set
+    public val onPrimary: Color = Color(0xFF202124)
+    public val onSecondary: Color = Color(0xFF202124)
+    public val onBackground: Color = Color.White
+    public val onSurface: Color = Color.White
+    public val onSurfaceVariant: Color = Color(0xFFDADCE0)
+    public val onSurfaceVariant2: Color = Color(0xFFBDC1C6)
+    public val onError: Color = Color(0xFF202124)
+
+    /**
+     * Returns a copy of this Colors, optionally overriding some of the values.
+     */
+    public fun copy(
+        primary: Color = this.primary,
+        primaryVariant: Color = this.primaryVariant,
+        secondary: Color = this.secondary,
+        secondaryVariant: Color = this.secondaryVariant,
+        error: Color = this.error,
+    ): Colors = Colors(
+        primary,
+        primaryVariant,
+        secondary,
+        secondaryVariant,
+        error,
+    )
+
+    override fun toString(): String {
+        return "Colors(" +
+            "primary=$primary, " +
+            "primaryVariant=$primaryVariant, " +
+            "secondary=$secondary, " +
+            "secondaryVariant=$secondaryVariant, " +
+            "background=$background, " +
+            "surface=$surface, " +
+            "error=$error, " +
+            "onPrimary=$onPrimary, " +
+            "onSecondary=$onSecondary, " +
+            "onBackground=$onBackground, " +
+            "onSurface=$onSurface, " +
+            "onSurfaceVariant=$onSurfaceVariant, " +
+            "onSurfaceVariant2=$onSurfaceVariant2, " +
+            "onError=$onError" +
+            ")"
+    }
+}
+
+/**
+ * The Material color system contains pairs of colors that are typically used for the background
+ * and content color inside a component. For example, a [Button] typically uses `primary` for its
+ * background, and `onPrimary` for the color of its content (usually text or iconography).
+ *
+ * This function tries to match the provided [backgroundColor] to a 'background' color in this
+ * [Colors], and then will return the corresponding color used for content. For example, when
+ * [backgroundColor] is [Colors.primary], this will return [Colors.onPrimary].
+ *
+ * If [backgroundColor] does not match a background color in the theme, this will return
+ * [Color.Unspecified].
+ *
+ * @return the matching content color for [backgroundColor]. If [backgroundColor] is not present in
+ * the theme's [Colors], then returns [Color.Unspecified].
+ *
+ * @see contentColorFor
+ */
+fun Colors.contentColorFor(backgroundColor: Color): Color {
+    return when (backgroundColor) {
+        primary -> onPrimary
+        primaryVariant -> onPrimary
+        secondary -> onSecondary
+        secondaryVariant -> onSecondary
+        background -> onBackground
+        surface -> onSurface
+        error -> onError
+        else -> Color.Unspecified
+    }
+}
+
+/**
+ * The Material color system contains pairs of colors that are typically used for the background
+ * and content color inside a component. For example, a [Button] typically uses `primary` for its
+ * background, and `onPrimary` for the color of its content (usually text or iconography).
+ *
+ * This function tries to match the provided [backgroundColor] to a 'background' color in this
+ * [Colors], and then will return the corresponding color used for content. For example, when
+ * [backgroundColor] is [Colors.primary], this will return [Colors.onPrimary].
+ *
+ * If [backgroundColor] does not match a background color in the theme, this will return
+ * the current value of [LocalContentColor] as a best-effort color.
+ *
+ * @return the matching content color for [backgroundColor]. If [backgroundColor] is not present in
+ * the theme's [Colors], then returns the current value of [LocalContentColor].
+ *
+ * @see Colors.contentColorFor
+ */
+@Composable
+@ReadOnlyComposable
+public fun contentColorFor(backgroundColor: Color) =
+    MaterialTheme.colors.contentColorFor(backgroundColor).takeOrElse { LocalContentColor.current }
+
+/**
+ * Updates the internal values of the given [Colors] with values from the [other] [Colors]. This
+ * allows efficiently updating a subset of [Colors], without recomposing every composable that
+ * consumes values from [LocalColors].
+ *
+ * Because [Colors] is very wide-reaching, and used by many expensive composables in the
+ * hierarchy, providing a new value to [LocalColors] causes every composable consuming
+ * [LocalColors] to recompose, which is prohibitively expensive in cases such as animating one
+ * color in the theme. Instead, [Colors] is internally backed by [mutableStateOf], and this
+ * function mutates the internal state of [this] to match values in [other]. This means that any
+ * changes will mutate the internal state of [this], and only cause composables that are reading
+ * the specific changed value to recompose.
+ */
+internal fun Colors.updateColorsFrom(other: Colors) {
+    primary = other.primary
+    primaryVariant = other.primaryVariant
+    secondary = other.secondary
+    secondaryVariant = other.secondaryVariant
+    error = other.error
+}
+
+internal val LocalColors = staticCompositionLocalOf<Colors> { Colors() }
diff --git a/wear/compose/material/src/androidMain/kotlin/androidx/wear/compose/material/ContentAlpha.kt b/wear/compose/material/src/androidMain/kotlin/androidx/wear/compose/material/ContentAlpha.kt
new file mode 100644
index 0000000..fc29f92
--- /dev/null
+++ b/wear/compose/material/src/androidMain/kotlin/androidx/wear/compose/material/ContentAlpha.kt
@@ -0,0 +1,124 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package androidx.wear.compose.material
+
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.compositionLocalOf
+import androidx.compose.ui.graphics.luminance
+
+/**
+ * Default alpha levels used by Material components.
+ *
+ * See [LocalContentAlpha].
+ */
+object ContentAlpha {
+    /**
+     * A high level of content alpha, used to represent high emphasis text such as input text in a
+     * selected [TextField].
+     */
+    val high: Float
+        @Composable
+        get() = contentAlpha(
+            highContrastAlpha = HighContrastContentAlpha.high,
+            lowContrastAlpha = LowContrastContentAlpha.high
+        )
+
+    /**
+     * A medium level of content alpha, used to represent medium emphasis text such as
+     * placeholder text in a [TextField].
+     */
+    val medium: Float
+        @Composable
+        get() = contentAlpha(
+            highContrastAlpha = HighContrastContentAlpha.medium,
+            lowContrastAlpha = LowContrastContentAlpha.medium
+        )
+
+    /**
+     * A low level of content alpha used to represent disabled components, such as text in a
+     * disabled [Button].
+     */
+    val disabled: Float
+        @Composable
+        get() = contentAlpha(
+            highContrastAlpha = HighContrastContentAlpha.disabled,
+            lowContrastAlpha = LowContrastContentAlpha.disabled
+        )
+
+    /**
+     * This default implementation uses separate alpha levels depending on the luminance of the
+     * incoming color, and whether the theme is light or dark. This is to ensure correct contrast
+     * and accessibility on all surfaces.
+     *
+     * See [HighContrastContentAlpha] and [LowContrastContentAlpha] for what the levels are
+     * used for, and under what circumstances.
+     */
+    @Composable
+    private fun contentAlpha(
+        /*@FloatRange(from = 0.0, to = 1.0)*/
+        highContrastAlpha: Float,
+        /*@FloatRange(from = 0.0, to = 1.0)*/
+        lowContrastAlpha: Float
+    ): Float {
+        val contentColor = LocalContentColor.current
+        return if (contentColor.luminance() < 0.5) highContrastAlpha else lowContrastAlpha
+    }
+}
+
+/**
+ * CompositionLocal containing the preferred content alpha for a given position in the hierarchy.
+ * This alpha is used for text and iconography ([Text] and [Icon]) to emphasize / de-emphasize
+ * different parts of a component. See the Material guide on
+ * [Text Legibility](https://material.io/design/color/text-legibility.html) for more information on
+ * alpha levels used by text and iconography.
+ *
+ * See [ContentAlpha] for the default levels used by most Material components.
+ *
+ * [MaterialTheme] sets this to [ContentAlpha.high] by default, as this is the default alpha for
+ * body text.
+ *
+ */
+val LocalContentAlpha = compositionLocalOf { 1f }
+
+/**
+ * Alpha levels for high luminance content in light theme, or low luminance content in dark theme.
+ *
+ * This content will typically be placed on colored surfaces, so it is important that the
+ * contrast here is higher to meet accessibility standards, and increase legibility.
+ *
+ * These levels are typically used for text / iconography in primary colored tabs /
+ * bottom navigation / etc.
+ */
+private object HighContrastContentAlpha {
+    const val high: Float = 1.00f
+    const val medium: Float = 0.74f
+    const val disabled: Float = 0.38f
+}
+
+/**
+ * Alpha levels for low luminance content in light theme, or high luminance content in dark theme.
+ *
+ * This content will typically be placed on grayscale surfaces, so the contrast here can be lower
+ * without sacrificing accessibility and legibility.
+ *
+ * These levels are typically used for body text on the main surface (white in light theme, grey
+ * in dark theme) and text / iconography in surface colored tabs / bottom navigation / etc.
+ */
+private object LowContrastContentAlpha {
+    const val high: Float = 0.87f
+    const val medium: Float = 0.60f
+    const val disabled: Float = 0.38f
+}
diff --git a/wear/compose/material/src/androidMain/kotlin/androidx/wear/compose/material/ContentColor.kt b/wear/compose/material/src/androidMain/kotlin/androidx/wear/compose/material/ContentColor.kt
new file mode 100644
index 0000000..a3d369b
--- /dev/null
+++ b/wear/compose/material/src/androidMain/kotlin/androidx/wear/compose/material/ContentColor.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package androidx.wear.compose.material
+
+import androidx.compose.runtime.compositionLocalOf
+import androidx.compose.ui.graphics.Color
+
+/**
+ * CompositionLocal containing the preferred content color for a given position in the hierarchy.
+ * This typically represents the `on` color for a color in [Colors]. For example, if the background
+ * color is [Colors.surface], this color is typically set to [Colors.onSurface].
+ *
+ * This color should be used for any typography / iconography, to ensure that the color of these
+ * adjusts when the background color changes. For example, on a dark background, text should be
+ * light, and on a light background, text should be dark.
+ *
+ * Defaults to [Color.White] if no color has been explicitly set.
+ */
+val LocalContentColor = compositionLocalOf { Color.White }
diff --git a/wear/compose/material/src/androidMain/kotlin/androidx/wear/compose/material/MaterialTextSelectionColors.kt b/wear/compose/material/src/androidMain/kotlin/androidx/wear/compose/material/MaterialTextSelectionColors.kt
new file mode 100644
index 0000000..2838258
--- /dev/null
+++ b/wear/compose/material/src/androidMain/kotlin/androidx/wear/compose/material/MaterialTextSelectionColors.kt
@@ -0,0 +1,239 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package androidx.wear.compose.material
+
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.compositeOver
+import androidx.compose.ui.graphics.luminance
+import androidx.compose.ui.graphics.takeOrElse
+import androidx.compose.foundation.text.selection.TextSelectionColors
+import kotlin.math.max
+import kotlin.math.min
+
+/**
+ * Remembers a [TextSelectionColors] based on [colors]. The handle color will be [Colors.primary]
+ * and the background color will be [Colors.primary] with alpha applied.
+ *
+ * See [calculateSelectionBackgroundColor].
+ */
+@Composable
+internal fun rememberTextSelectionColors(colors: Colors): TextSelectionColors {
+    val primaryColor = colors.primary
+    val backgroundColor = colors.background
+    // Test with ContentAlpha.medium to ensure that the selection background is accessible in the
+    // 'worst case' scenario. We explicitly don't test with ContentAlpha.disabled, as disabled
+    // text shouldn't be selectable / is noted as disabled for accessibility purposes.
+    val textColorWithLowestAlpha = colors.contentColorFor(backgroundColor)
+        .takeOrElse {
+            LocalContentColor.current
+        }.copy(
+            alpha = ContentAlpha.medium
+        )
+    return remember(primaryColor, backgroundColor, textColorWithLowestAlpha) {
+        TextSelectionColors(
+            handleColor = colors.primary,
+            backgroundColor = calculateSelectionBackgroundColor(
+                selectionColor = primaryColor,
+                textColor = textColorWithLowestAlpha,
+                backgroundColor = backgroundColor
+            )
+        )
+    }
+}
+
+/**
+ * Best-effort calculates a color (with alpha) for the selection background that (if possible)
+ * will have at least [DesiredContrastRatio] with [textColor], when the selection background
+ * is on top of [backgroundColor].
+ *
+ * Since this is a minimum contrast ratio, [textColor] should have the lowest alpha that
+ * may be applied to content so we can ensure that the selection background color is accessible
+ * in that worst-case scenario for contrast.
+ *
+ * @param selectionColor the 'raw' (without alpha) selection color that we should search alpha for
+ * @param textColor the color of text with minimal alpha applied to test for contrast with
+ * @param backgroundColor the color of the background that the selection color will typically be
+ * placed against
+ *
+ * @return a resulting [selectionColor] with alpha applied that results in acceptable contrast
+ * (if possible with the values for [selectionColor], [textColor] and [backgroundColor]).
+ */
+/*@VisibleForTesting*/
+internal fun calculateSelectionBackgroundColor(
+    selectionColor: Color,
+    textColor: Color,
+    backgroundColor: Color
+): Color {
+    val maximumContrastRatio = calculateContrastRatio(
+        selectionColor = selectionColor,
+        selectionColorAlpha = DefaultSelectionBackgroundAlpha,
+        textColor = textColor,
+        backgroundColor = backgroundColor
+    )
+
+    val minimumContrastRatio = calculateContrastRatio(
+        selectionColor = selectionColor,
+        selectionColorAlpha = MinimumSelectionBackgroundAlpha,
+        textColor = textColor,
+        backgroundColor = backgroundColor
+    )
+
+    val alpha = when {
+        // If the default alpha has enough contrast, use that
+        maximumContrastRatio >= DesiredContrastRatio -> DefaultSelectionBackgroundAlpha
+        // If the minimum alpha still does not have enough contrast, just use the minimum and return
+        minimumContrastRatio < DesiredContrastRatio -> MinimumSelectionBackgroundAlpha
+        else -> binarySearchForAccessibleSelectionColorAlpha(
+            selectionColor = selectionColor,
+            textColor = textColor,
+            backgroundColor = backgroundColor
+        )
+    }
+
+    return selectionColor.copy(alpha = alpha)
+}
+
+/**
+ * Binary searches for the highest alpha for selection color that results in a contrast ratio at
+ * least equal to and within 1% of [DesiredContrastRatio].
+ *
+ * The resulting alpha will be within the range of [MinimumSelectionBackgroundAlpha] and
+ * [DefaultSelectionBackgroundAlpha] - since not all values for [selectionColor], [textColor] and
+ * [backgroundColor] can be guaranteed to produce an accessible contrast ratio, this is a
+ * best-effort attempt and [MinimumSelectionBackgroundAlpha] might still not produce an
+ * accessible contrast ratio. In this case developers are encouraged to manually choose a
+ * different color for selection that _is_ accessible with their chosen content and background
+ * colors.
+ *
+ * Caps the number of attempts at 7 for performance and to avoid infinite searching when there is
+ * no value that results in an accessible contrast ratio. Because alpha is limited to [0,1], 7
+ * steps results in a precision of ~0.01, since log2(1/0.01) ≈ 7.
+ *
+ * Note: binary searching here is chosen since it is not possible to 'solve' for alpha, since the
+ * transformation from color -> contrast ratio is not linear (the gamma exponent for sRGB colors
+ * is 2.4). We can approximate this to 2, but this results in not that accurate solutions, and we
+ * need to guarantee that they are at least above [DesiredContrastRatio] - falling just below is
+ * not an acceptable result.
+ *
+ * @param selectionColor the 'raw' (without alpha) selection color that we should search alpha for
+ * @param textColor the color of text with minimal alpha applied to test for contrast with
+ * @param backgroundColor the color of the background that the selection color will typically be
+ * placed against
+ */
+private fun binarySearchForAccessibleSelectionColorAlpha(
+    selectionColor: Color,
+    textColor: Color,
+    backgroundColor: Color
+): Float {
+    var attempts = 0
+    val maxAttempts = 7
+
+    var lowAlpha = MinimumSelectionBackgroundAlpha
+    var alpha = DefaultSelectionBackgroundAlpha
+    var highAlpha = DefaultSelectionBackgroundAlpha
+
+    while (attempts < maxAttempts) {
+        val contrastRatio = calculateContrastRatio(
+            selectionColor = selectionColor,
+            selectionColorAlpha = alpha,
+            textColor = textColor,
+            backgroundColor = backgroundColor
+        )
+
+        // Percentage error of the calculated contrast compared to the actual contrast. Positive
+        // numbers here mean we have higher contrast than needed.
+        val percentageError = (contrastRatio / DesiredContrastRatio) - 1f
+        when {
+            // Contrast is at most 1% above the guideline, return
+            percentageError in 0f..0.01f -> break
+            // Contrast too low, decrease alpha
+            percentageError < 0f -> highAlpha = alpha
+            // Contrast higher than required, increase alpha
+            else -> lowAlpha = alpha
+        }
+        alpha = (highAlpha + lowAlpha) / 2f
+        attempts++
+    }
+
+    return alpha
+}
+
+/**
+ * Calculates the contrast ratio of [textColor] against [selectionColor] with
+ * [selectionColorAlpha], all on top of [backgroundColor].
+ *
+ * Both the [selectionColor] and [textColor] will be composited to handle transparency.
+ *
+ * @param selectionColor the 'raw' (without alpha) selection color that we should search alpha for
+ * @param selectionColorAlpha the alpha for [selectionColor] to test contrast with
+ * @param textColor the color of text with minimal alpha applied to test for contrast with
+ * @param backgroundColor the color of the background that the selection color will typically be
+ * placed against
+ *
+ * @return the contrast ratio as a value between 1 and 21. See [calculateContrastRatio]
+ */
+private fun calculateContrastRatio(
+    selectionColor: Color,
+    selectionColorAlpha: Float,
+    textColor: Color,
+    backgroundColor: Color
+): Float {
+    val compositeBackground = selectionColor.copy(alpha = selectionColorAlpha)
+        .compositeOver(backgroundColor)
+    val compositeTextColor = textColor.compositeOver(compositeBackground)
+    return calculateContrastRatio(compositeTextColor, compositeBackground)
+}
+
+/**
+ * Calculates the contrast ratio of [foreground] against [background], returning a value between
+ * 1 and 21. (1:1 and 21:1 ratios).
+ *
+ * Formula taken from [WCAG 2.0](https://www.w3.org/TR/UNDERSTANDING-WCAG20/visual-audio-contrast-contrast.html#contrast-ratiodef)
+ *
+ * Note: [foreground] and [background] *must* be opaque. See [Color.compositeOver] to pre-composite
+ * a translucent foreground over the background.
+ *
+ * @return the contrast ratio as a value between 1 and 21. See [calculateContrastRatio]
+ */
+/*@VisibleForTesting*/
+internal fun calculateContrastRatio(foreground: Color, background: Color): Float {
+    val foregroundLuminance = foreground.luminance() + 0.05f
+    val backgroundLuminance = background.luminance() + 0.05f
+
+    return max(foregroundLuminance, backgroundLuminance) /
+        min(foregroundLuminance, backgroundLuminance)
+}
+
+/**
+ * Default selection background alpha - we will try and use this if it is accessible and produces
+ * the correct contrast ratio.
+ */
+private const val DefaultSelectionBackgroundAlpha = 0.4f
+
+/**
+ * Not all combinations of text color and selection color will have a reasonable alpha that
+ * produces a contrast ratio of at least [DesiredContrastRatio] - in this case just pick a low
+ * but still visible alpha so at least the contrast ratio is as good as it can be - this is
+ * preferable to crashing at runtime.
+ */
+private const val MinimumSelectionBackgroundAlpha = DefaultSelectionBackgroundAlpha / 2f
+
+/**
+ * Material and WCAG 2.0 sc 1.4.3 minimum contrast for AA text
+ */
+private const val DesiredContrastRatio = 4.5f
diff --git a/wear/compose/material/src/androidMain/kotlin/androidx/wear/compose/material/MaterialTheme.kt b/wear/compose/material/src/androidMain/kotlin/androidx/wear/compose/material/MaterialTheme.kt
new file mode 100644
index 0000000..f92126e
--- /dev/null
+++ b/wear/compose/material/src/androidMain/kotlin/androidx/wear/compose/material/MaterialTheme.kt
@@ -0,0 +1,138 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package androidx.wear.compose.material
+
+import androidx.compose.foundation.LocalIndication
+import androidx.compose.foundation.text.selection.LocalTextSelectionColors
+import androidx.compose.material.ripple.LocalRippleTheme
+import androidx.compose.material.ripple.RippleTheme
+import androidx.compose.material.ripple.rememberRipple
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.runtime.Immutable
+import androidx.compose.runtime.ReadOnlyComposable
+import androidx.compose.runtime.compositionLocalOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.structuralEqualityPolicy
+import androidx.compose.ui.text.TextStyle
+
+// TODO: Provide references to the Wear material design specs.
+/**
+ * MaterialTheme defines the styling principles from the WearOS Material design specification
+ * which extends the Material design specification.
+ *
+ * Wear Material components from package/sub-packages in [androidx.wear.compose.material] use values
+ * provided here when retrieving default values.
+ *
+ * It defines colors as specified in the [Wear Material Color theme spec](https://),
+ * typography defined in the [Wear Material Type Scale spec](https://),
+ * and shapes defined in the [Wear Shape scheme](https://).
+ *
+ * All values may be set by providing this component with the [colors][Colors],
+ * [typography][Typography], and [shapes][Shapes] attributes. Use this to configure the
+ * overall theme of elements within this MaterialTheme.
+ *
+ * Any values that are not set will inherit the current value from the theme, falling back to the
+ * defaults if there is no parent MaterialTheme. This allows using a MaterialTheme at the
+ * top of your application, and then separate MaterialTheme(s) for different screens / parts of
+ * your UI, overriding only the parts of the theme definition that need to change.
+ *
+ * @param colors A complete definition of the Wear Material Color theme for this hierarchy
+ * @param typography A set of text styles to be used as this hierarchy's typography system
+ * @param shapes A set of shapes to be used by the components in this hierarchy
+ */
+@Composable
+public fun MaterialTheme(
+    colors: Colors = MaterialTheme.colors,
+    typography: Typography = MaterialTheme.typography,
+    shapes: Shapes = MaterialTheme.shapes,
+    content: @Composable () -> Unit
+) {
+    val rememberedColors = remember {
+        // Explicitly creating a new object here so we don't mutate the initial [colors]
+        // provided, and overwrite the values set in it.
+        colors.copy()
+    }.apply { updateColorsFrom(colors) }
+    val rippleIndication = rememberRipple()
+    val selectionColors = rememberTextSelectionColors(rememberedColors)
+    CompositionLocalProvider(
+        LocalColors provides rememberedColors,
+        LocalShapes provides shapes,
+        LocalTypography provides typography,
+        LocalContentAlpha provides ContentAlpha.high,
+        LocalIndication provides rippleIndication,
+        LocalRippleTheme provides MaterialRippleTheme,
+        LocalTextSelectionColors provides selectionColors,
+
+    ) {
+        ProvideTextStyle(value = typography.body1, content = content)
+    }
+}
+
+public object MaterialTheme {
+    public val colors: Colors
+        @ReadOnlyComposable
+        @Composable
+        get() = LocalColors.current
+
+    public val typography: Typography
+        @ReadOnlyComposable
+        @Composable
+        get() = LocalTypography.current
+
+    public val shapes: Shapes
+        @ReadOnlyComposable
+        @Composable
+        get() = LocalShapes.current
+}
+
+@Immutable
+private object MaterialRippleTheme : RippleTheme {
+    @Composable
+    override fun defaultColor() = RippleTheme.defaultRippleColor(
+        contentColor = LocalContentColor.current,
+        lightTheme = false
+    )
+
+    @Composable
+    override fun rippleAlpha() = RippleTheme.defaultRippleAlpha(
+        contentColor = LocalContentColor.current,
+        lightTheme = false
+    )
+}
+
+/**
+ * CompositionLocal containing the preferred [TextStyle] that will be used by [Text] components by
+ * default. To set the value for this CompositionLocal, see [ProvideTextStyle] which will merge any
+ * missing [TextStyle] properties with the existing [TextStyle] set in this CompositionLocal.
+ *
+ * @see ProvideTextStyle
+ */
+val LocalTextStyle = compositionLocalOf(structuralEqualityPolicy()) { TextStyle.Default }
+
+// TODO: b/156598010 remove this and replace with fold definition on the backing CompositionLocal
+/**
+ * This function is used to set the current value of [LocalTextStyle], merging the given style
+ * with the current style values for any missing attributes. Any [Text] components included in
+ * this component's [content] will be styled with this style unless styled explicitly.
+ *
+ * @see LocalTextStyle
+ */
+@Composable
+fun ProvideTextStyle(value: TextStyle, content: @Composable () -> Unit) {
+    val mergedStyle = LocalTextStyle.current.merge(value)
+    CompositionLocalProvider(LocalTextStyle provides mergedStyle, content = content)
+}
diff --git a/wear/compose/material/src/androidMain/kotlin/androidx/wear/compose/material/Shapes.kt b/wear/compose/material/src/androidMain/kotlin/androidx/wear/compose/material/Shapes.kt
new file mode 100644
index 0000000..4248dfb
--- /dev/null
+++ b/wear/compose/material/src/androidMain/kotlin/androidx/wear/compose/material/Shapes.kt
@@ -0,0 +1,83 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package androidx.wear.compose.material
+
+import androidx.compose.foundation.shape.CornerBasedShape
+import androidx.compose.foundation.shape.CornerSize
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.runtime.Immutable
+import androidx.compose.runtime.staticCompositionLocalOf
+import androidx.compose.ui.unit.dp
+
+/**
+ * Components are grouped into shape categories based on common features. These categories provide a
+ * way to change multiple component values at once, by changing the category’s values.
+ *
+ */
+@Immutable
+public class Shapes(
+    /**
+     * Buttons and Chips use this shape
+     */
+    val small: CornerBasedShape = RoundedCornerShape(corner = CornerSize(50)),
+
+    val medium: CornerBasedShape = RoundedCornerShape(4.dp),
+    /**
+     * Cards use this shape
+     */
+    val large: CornerBasedShape = RoundedCornerShape(24.dp),
+) {
+
+    /**
+     * Returns a copy of this Shapes, optionally overriding some of the values.
+     */
+    public fun copy(
+        small: CornerBasedShape = this.small,
+        medium: CornerBasedShape = this.medium,
+        large: CornerBasedShape = this.large,
+    ): Shapes = Shapes(
+        small = small,
+        medium = medium,
+        large = large,
+    )
+
+    override fun equals(other: Any?): Boolean {
+        if (this === other) return true
+        if (other !is Shapes) return false
+
+        if (small != other.small) return false
+        if (medium != other.medium) return false
+        if (large != other.large) return false
+
+        return true
+    }
+
+    override fun hashCode(): Int {
+        var result = small.hashCode()
+        result = 31 * result + medium.hashCode()
+        result = 31 * result + large.hashCode()
+        return result
+    }
+
+    override fun toString(): String {
+        return "Shapes(small=$small, medium=$medium, large=$large)"
+    }
+}
+
+/**
+ * CompositionLocal used to specify the default shapes for the surfaces.
+ */
+internal val LocalShapes = staticCompositionLocalOf { Shapes() }
diff --git a/wear/compose/material/src/androidMain/kotlin/androidx/wear/compose/material/Typography.kt b/wear/compose/material/src/androidMain/kotlin/androidx/wear/compose/material/Typography.kt
new file mode 100644
index 0000000..e222efa
--- /dev/null
+++ b/wear/compose/material/src/androidMain/kotlin/androidx/wear/compose/material/Typography.kt
@@ -0,0 +1,242 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package androidx.wear.compose.material
+
+import androidx.compose.runtime.Immutable
+import androidx.compose.runtime.staticCompositionLocalOf
+import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.text.font.FontFamily
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.unit.sp
+
+/**
+ * Class holding typography definitions as defined by the Wear Material typography specification.
+ *
+ * @property display1 display1 is the largest headline, reserved for short, important text or
+ * numerals. For headlines, you can choose an expressive font, such as a display, handwritten, or
+ * script style. These unconventional font designs have details and intricacy that help attract the
+ * eye.Ø
+ *
+ * @property display2 display2 is the second largest headline, reserved for short, important text or
+ * numerals. For headlines, you can choose an expressive font, such as a display, handwritten, or
+ * script style. These unconventional font designs have details and intricacy that help attract the
+ * eye.
+ *
+ * @property display3 display3 is the third largest headline, reserved for short, important text or
+ * numerals. For headlines, you can choose an expressive font, such as a display, handwritten, or
+ * script style. These unconventional font designs have details and intricacy that help attract the
+ * eye.
+ *
+ * @property title1 title1 is the largest title, and is typically reserved for medium-emphasis text
+ * that is shorter in length. Serif or sans serif typefaces work well for subtitles.
+ *
+ * @property title2 title2 is the medium title, and is typically reserved for medium-emphasis text
+ * that is shorter in length. Serif or sans serif typefaces work well for subtitles.
+ *
+ * @property title3 title3 is the smallest title, and is typically reserved for medium-emphasis text
+ * that is shorter in length. Serif or sans serif typefaces work well for subtitles.
+ *
+ * @property body1 body1 is the largest body, and is typically used for long-form writing as it
+ * works well for small text sizes. For longer sections of text, a serif or sans serif typeface is
+ * recommended.
+ *
+ * @property body2 body2 is the smallest body, and is typically used for long-form writing as it
+ * works well for small text sizes. For longer sections of text, a serif or sans serif typeface is
+ * recommended.
+ *
+ * @property button button text is a call to action used in different types of buttons (such as
+ * text, outlined and contained buttons) and in tabs, dialogs, and cards. Button text is typically
+ * sans serif, using all caps text.
+ *
+ * @property caption1 caption1 is one of the smallest font sizes. It is used sparingly to annotate
+ * imagery or to introduce a headline.
+ *
+ * @property caption2 caption2 is one of the smallest font sizes. It is used sparingly to annotate
+ * imagery or to introduce a headline.
+ */
+@Immutable
+public class Typography internal constructor (
+    val display1: TextStyle,
+    val display2: TextStyle,
+    val display3: TextStyle,
+    val title1: TextStyle,
+    val title2: TextStyle,
+    val title3: TextStyle,
+    val body1: TextStyle,
+    val body2: TextStyle,
+    val button: TextStyle,
+    val caption1: TextStyle,
+    val caption2: TextStyle,
+) {
+    constructor (
+        defaultFontFamily: FontFamily = FontFamily.Default,
+        display1: TextStyle = TextStyle(
+            fontWeight = FontWeight.Medium,
+            fontSize = 50.sp,
+            letterSpacing = 0.2.sp
+        ),
+        display2: TextStyle = TextStyle(
+            fontWeight = FontWeight.Medium,
+            fontSize = 40.sp,
+            letterSpacing = 0.5.sp
+        ),
+        display3: TextStyle = TextStyle(
+            fontWeight = FontWeight.Medium,
+            fontSize = 30.sp,
+            letterSpacing = 0.5.sp
+        ),
+        title1: TextStyle = TextStyle(
+            fontWeight = FontWeight.Medium,
+            fontSize = 24.sp,
+            letterSpacing = 0.sp
+        ),
+        title2: TextStyle = TextStyle(
+            fontWeight = FontWeight.Medium,
+            fontSize = 20.sp,
+            letterSpacing = 0.2.sp
+        ),
+        title3: TextStyle = TextStyle(
+            fontWeight = FontWeight.Medium,
+            fontSize = 16.sp,
+            letterSpacing = 0.2.sp
+        ),
+        body1: TextStyle = TextStyle(
+            fontWeight = FontWeight.Normal,
+            fontSize = 16.sp,
+            letterSpacing = 0.sp
+        ),
+        body2: TextStyle = TextStyle(
+            fontWeight = FontWeight.Normal,
+            fontSize = 14.sp,
+            letterSpacing = 0.25.sp
+        ),
+        button: TextStyle = TextStyle(
+            fontWeight = FontWeight.Bold,
+            fontSize = 14.sp,
+            letterSpacing = 0.sp
+        ),
+        caption1: TextStyle = TextStyle(
+            fontWeight = FontWeight.Medium,
+            fontSize = 14.sp,
+            letterSpacing = 0.sp
+        ),
+        caption2: TextStyle = TextStyle(
+            fontWeight = FontWeight.Medium,
+            fontSize = 12.sp,
+            letterSpacing = 0.sp
+        )
+    ) : this(
+        display1 = display1.withDefaultFontFamily(defaultFontFamily),
+        display2 = display2.withDefaultFontFamily(defaultFontFamily),
+        display3 = display3.withDefaultFontFamily(defaultFontFamily),
+        title1 = title1.withDefaultFontFamily(defaultFontFamily),
+        title2 = title2.withDefaultFontFamily(defaultFontFamily),
+        title3 = title3.withDefaultFontFamily(defaultFontFamily),
+        body1 = body1.withDefaultFontFamily(defaultFontFamily),
+        body2 = body2.withDefaultFontFamily(defaultFontFamily),
+        button = button.withDefaultFontFamily(defaultFontFamily),
+        caption1 = caption1.withDefaultFontFamily(defaultFontFamily),
+        caption2 = caption2.withDefaultFontFamily(defaultFontFamily),
+    )
+
+    /**
+     * Returns a copy of this Typography, optionally overriding some of the values.
+     */
+    public fun copy(
+        display1: TextStyle = this.display1,
+        display2: TextStyle = this.display2,
+        display3: TextStyle = this.display3,
+        title1: TextStyle = this.title1,
+        title2: TextStyle = this.title2,
+        title3: TextStyle = this.title3,
+        body1: TextStyle = this.body1,
+        body2: TextStyle = this.body2,
+        button: TextStyle = this.button,
+        caption1: TextStyle = this.caption1,
+        caption2: TextStyle = this.caption2,
+    ): Typography = Typography(
+        display1,
+        display2,
+        display3,
+        title1,
+        title2,
+        title3,
+        body1,
+        body2,
+        button,
+        caption1,
+        caption2
+    )
+
+    override fun equals(other: Any?): Boolean {
+        if (this === other) return true
+        if (other !is Typography) return false
+
+        if (display1 != other.display1) return false
+        if (display2 != other.display2) return false
+        if (display3 != other.display3) return false
+        if (title1 != other.title1) return false
+        if (title2 != other.title2) return false
+        if (title3 != other.title3) return false
+        if (body1 != other.body1) return false
+        if (body2 != other.body2) return false
+        if (button != other.button) return false
+        if (caption1 != other.caption1) return false
+        if (caption2 != other.caption2) return false
+
+        return true
+    }
+
+    override fun hashCode(): Int {
+        var result = display1.hashCode()
+        result = 31 * result + display2.hashCode()
+        result = 31 * result + display3.hashCode()
+        result = 31 * result + title1.hashCode()
+        result = 31 * result + title2.hashCode()
+        result = 31 * result + title3.hashCode()
+        result = 31 * result + body1.hashCode()
+        result = 31 * result + body2.hashCode()
+        result = 31 * result + button.hashCode()
+        result = 31 * result + caption1.hashCode()
+        result = 31 * result + caption2.hashCode()
+        return result
+    }
+
+    override fun toString(): String {
+        return "Typography(display1=$display1, display2=$display2, display3=$display3, " +
+            "title1=$title1, title2=$title2, title3=$title3, body1=$body1, body2=$body2, " +
+            "button=$button, caption1=$caption1, caption2=$caption2)"
+    }
+}
+
+/**
+ * @return [this] if there is a [FontFamily] defined, otherwise copies [this] with [default] as
+ * the [FontFamily].
+ */
+private fun TextStyle.withDefaultFontFamily(default: FontFamily): TextStyle {
+    return if (fontFamily != null) this else copy(fontFamily = default)
+}
+
+/**
+ * This Ambient holds on to the current definition of typography for this application as described
+ * by the Wear Material spec. You can read the values in it when creating custom components that
+ * want to use Wear Material types, as well as override the values when you want to re-style a part
+ * of your hierarchy. Material components related to text such as [Button] will use this Ambient
+ * to set values with which to style children text components.
+ *
+ * To access values within this ambient, use [MaterialTheme.typography].
+ */
+internal val LocalTypography = staticCompositionLocalOf { Typography() }
diff --git a/wear/tiles/tiles-proto/src/main/proto/layout.proto b/wear/tiles/tiles-proto/src/main/proto/layout.proto
index ea3d8d8..008344f 100644
--- a/wear/tiles/tiles-proto/src/main/proto/layout.proto
+++ b/wear/tiles/tiles-proto/src/main/proto/layout.proto
@@ -312,6 +312,7 @@
 
 // An extensible ContentScaleMode property.
 message ContentScaleModeProp {
+  // The value
   ContentScaleMode value = 1;
 }
 
diff --git a/wear/tiles/tiles-proto/src/main/proto/requests.proto b/wear/tiles/tiles-proto/src/main/proto/requests.proto
index 17e9522..9fcdfa0 100644
--- a/wear/tiles/tiles-proto/src/main/proto/requests.proto
+++ b/wear/tiles/tiles-proto/src/main/proto/requests.proto
@@ -22,7 +22,8 @@
 // Parameters passed to a Tile provider when the renderer is requesting a
 // specific resource version.
 message ResourcesRequest {
-  // The version of the resources being fetched
+  // The version of the resources being fetched. This is the same as the
+  // requested resource version, passed in Tile.
   string version = 1;
 
   // Requested resource IDs. If not specified, all resources for the given
diff --git a/wear/tiles/tiles-proto/src/main/proto/resources.proto b/wear/tiles/tiles-proto/src/main/proto/resources.proto
index babb7d8..eb4d8af 100644
--- a/wear/tiles/tiles-proto/src/main/proto/resources.proto
+++ b/wear/tiles/tiles-proto/src/main/proto/resources.proto
@@ -49,7 +49,7 @@
 // to what it thinks is appropriate.
 message ImageResource {
   // An image resource that maps to an Android drawable by resource ID.
-  AndroidImageResourceByResId android_resource_by_resid = 1;
+  AndroidImageResourceByResId android_resource_by_res_id = 1;
 
   // An image resource that contains the image data inline.
   InlineImageResource inline_resource = 2;
@@ -64,7 +64,8 @@
   // to separately fetch the resources.
   //
   // This value must match the version of the resources required by the tile
-  // for the tile to render successfully.
+  // for the tile to render successfully, and must match the resource version
+  // specified in ResourcesRequest which triggered this request.
   string version = 1;
 
   // A map of resource_ids to images, which can be used by layouts.
diff --git a/wear/tiles/tiles-proto/src/main/proto/tile.proto b/wear/tiles/tiles-proto/src/main/proto/tile.proto
index 1bbded9..fb41af4 100644
--- a/wear/tiles/tiles-proto/src/main/proto/tile.proto
+++ b/wear/tiles/tiles-proto/src/main/proto/tile.proto
@@ -12,7 +12,10 @@
 // A holder for a tile. This specifies the resources to use for this delivery
 // of the tile, and the timeline for the tile.
 message Tile {
-  // The resource version required for these tiles.
+  // The resource version required for these tiles. This can be any developer-defined
+  // string; it is only used to cache resources, and is passed in
+  // ResourcesRequest if the system does not have a copy of the specified
+  // resource version.
   string resources_version = 1;
 
   // The tiles to show in the carousel, along with their validity periods.
@@ -26,5 +29,12 @@
   // your tile at some point in the future after this interval has lapsed. A
   // value of 0 here signifies that auto-refreshes should not be used (i.e. you
   // will manually request updates via TileProviderService#getRequester).
+  //
+  // This mechanism should not be used to update your tile more frequently than
+  // once a minute, and the system may throttle your updates if you request
+  // updates faster than this interval. This interval is also inexact; the
+  // system will generally update your tile if it is on-screen, or about to be
+  // on-screen, although this is not guaranteed due to system-level
+  // optimizations.
   uint64 freshness_interval_millis = 4;
 }
diff --git a/wear/tiles/tiles-renderer/src/androidTest/java/androidx/wear/tiles/renderer/test/TileRendererGoldenTest.java b/wear/tiles/tiles-renderer/src/androidTest/java/androidx/wear/tiles/renderer/test/TileRendererGoldenTest.java
index 5fb0cfb..90053ee 100644
--- a/wear/tiles/tiles-renderer/src/androidTest/java/androidx/wear/tiles/renderer/test/TileRendererGoldenTest.java
+++ b/wear/tiles/tiles-renderer/src/androidTest/java/androidx/wear/tiles/renderer/test/TileRendererGoldenTest.java
@@ -170,14 +170,14 @@
                 .putIdToImage(
                         "android",
                         ImageResource.newBuilder()
-                                .setAndroidResourceByResid(
+                                .setAndroidResourceByResId(
                                         AndroidImageResourceByResId.newBuilder()
                                                 .setResourceId(R.drawable.android_24dp))
                                 .build())
                 .putIdToImage(
                         "android_withbg_120dp",
                         ImageResource.newBuilder()
-                                .setAndroidResourceByResid(
+                                .setAndroidResourceByResId(
                                         AndroidImageResourceByResId.newBuilder()
                                                 .setResourceId(R.mipmap.android_withbg_120dp))
                                 .build())
@@ -194,14 +194,14 @@
                 .putIdToImage(
                         "broken_image",
                         ImageResource.newBuilder()
-                                .setAndroidResourceByResid(
+                                .setAndroidResourceByResId(
                                         AndroidImageResourceByResId.newBuilder()
                                                 .setResourceId(R.drawable.broken_drawable))
                                 .build())
                 .putIdToImage(
                         "missing_image",
                         ImageResource.newBuilder()
-                                .setAndroidResourceByResid(
+                                .setAndroidResourceByResId(
                                         AndroidImageResourceByResId.newBuilder().setResourceId(-1))
                                 .build())
                 .build();
diff --git a/wear/tiles/tiles-renderer/src/main/java/androidx/wear/tiles/renderer/internal/ResourceResolvers.java b/wear/tiles/tiles-renderer/src/main/java/androidx/wear/tiles/renderer/internal/ResourceResolvers.java
index 528c8fe..0d63cdd 100644
--- a/wear/tiles/tiles-renderer/src/main/java/androidx/wear/tiles/renderer/internal/ResourceResolvers.java
+++ b/wear/tiles/tiles-renderer/src/main/java/androidx/wear/tiles/renderer/internal/ResourceResolvers.java
@@ -113,10 +113,10 @@
                             "Resource " + protoResourceId + " is not defined in resources bundle"));
         }
 
-        if (imageResource.hasAndroidResourceByResid()
+        if (imageResource.hasAndroidResourceByResId()
                 && mAndroidImageResourceByResIdResolver != null) {
             AndroidImageResourceByResIdResolver resolver = mAndroidImageResourceByResIdResolver;
-            return resolver.getDrawable(imageResource.getAndroidResourceByResid());
+            return resolver.getDrawable(imageResource.getAndroidResourceByResId());
         }
 
         if (imageResource.hasInlineResource() && mInlineImageResourceResolver != null) {
diff --git a/wear/tiles/tiles-renderer/src/main/java/androidx/wear/tiles/timeline/TilesTimelineManager.java b/wear/tiles/tiles-renderer/src/main/java/androidx/wear/tiles/timeline/TilesTimelineManager.java
index 1261606..643dc18 100644
--- a/wear/tiles/tiles-renderer/src/main/java/androidx/wear/tiles/timeline/TilesTimelineManager.java
+++ b/wear/tiles/tiles-renderer/src/main/java/androidx/wear/tiles/timeline/TilesTimelineManager.java
@@ -98,6 +98,6 @@
     /** Tears down this Timeline Manager. This will ensure any set alarms are cleared up. */
     @Override
     public void close() {
-        mManager.deInit();
+        mManager.close();
     }
 }
diff --git a/wear/tiles/tiles-renderer/src/main/java/androidx/wear/tiles/timeline/internal/TilesTimelineCacheInternal.java b/wear/tiles/tiles-renderer/src/main/java/androidx/wear/tiles/timeline/internal/TilesTimelineCacheInternal.java
index ac69c6f..f1cbf3b 100644
--- a/wear/tiles/tiles-renderer/src/main/java/androidx/wear/tiles/timeline/internal/TilesTimelineCacheInternal.java
+++ b/wear/tiles/tiles-renderer/src/main/java/androidx/wear/tiles/timeline/internal/TilesTimelineCacheInternal.java
@@ -146,6 +146,7 @@
      * @return The time in millis that {@code entry} should be considered to be expired. This value
      *     will be {@link Long#MAX_VALUE} if {@code entry} does not expire.
      */
+    @MainThread
     public long findCurrentTimelineEntryExpiry(@NonNull TimelineEntry entry, long fromTimeMillis) {
         long currentSmallestExpiry = Long.MAX_VALUE;
         long entryValidityLength = Long.MAX_VALUE;
diff --git a/wear/tiles/tiles-renderer/src/main/java/androidx/wear/tiles/timeline/internal/TilesTimelineManagerInternal.java b/wear/tiles/tiles-renderer/src/main/java/androidx/wear/tiles/timeline/internal/TilesTimelineManagerInternal.java
index d83777f..31fb68f 100644
--- a/wear/tiles/tiles-renderer/src/main/java/androidx/wear/tiles/timeline/internal/TilesTimelineManagerInternal.java
+++ b/wear/tiles/tiles-renderer/src/main/java/androidx/wear/tiles/timeline/internal/TilesTimelineManagerInternal.java
@@ -36,7 +36,7 @@
  * <p>This handles the dispatching of single Tiles layouts from a full timeline. It will set the
  * correct alarms to detect when a layout should be updated, and dispatch it to its listener.
  */
-public class TilesTimelineManagerInternal {
+public class TilesTimelineManagerInternal implements AutoCloseable {
     // 1 minute min delay between tiles.
     public static final long MIN_TILE_UPDATE_DELAY_MILLIS = MINUTES.toMillis(1);
 
@@ -102,7 +102,8 @@
     }
 
     /** Tears down this Timeline Manager. This will ensure any set alarms are cleared up. */
-    public void deInit() {
+    @Override
+    public void close() {
         if (mAlarmListener != null) {
             mAlarmManager.cancel(mAlarmListener);
             mAlarmListener = null;
diff --git a/wear/tiles/tiles/api/current.txt b/wear/tiles/tiles/api/current.txt
index ee9c959..708ea18 100644
--- a/wear/tiles/tiles/api/current.txt
+++ b/wear/tiles/tiles/api/current.txt
@@ -691,8 +691,8 @@
 
   public static final class ResourceBuilders.ImageResource.Builder {
     method public androidx.wear.tiles.builders.ResourceBuilders.ImageResource build();
-    method public androidx.wear.tiles.builders.ResourceBuilders.ImageResource.Builder setAndroidResourceByResid(androidx.wear.tiles.builders.ResourceBuilders.AndroidImageResourceByResId);
-    method public androidx.wear.tiles.builders.ResourceBuilders.ImageResource.Builder setAndroidResourceByResid(androidx.wear.tiles.builders.ResourceBuilders.AndroidImageResourceByResId.Builder);
+    method public androidx.wear.tiles.builders.ResourceBuilders.ImageResource.Builder setAndroidResourceByResId(androidx.wear.tiles.builders.ResourceBuilders.AndroidImageResourceByResId);
+    method public androidx.wear.tiles.builders.ResourceBuilders.ImageResource.Builder setAndroidResourceByResId(androidx.wear.tiles.builders.ResourceBuilders.AndroidImageResourceByResId.Builder);
     method public androidx.wear.tiles.builders.ResourceBuilders.ImageResource.Builder setInlineResource(androidx.wear.tiles.builders.ResourceBuilders.InlineImageResource);
     method public androidx.wear.tiles.builders.ResourceBuilders.ImageResource.Builder setInlineResource(androidx.wear.tiles.builders.ResourceBuilders.InlineImageResource.Builder);
   }
@@ -807,19 +807,15 @@
   }
 
   public static class EventReaders.TileAddEvent {
-    method public int getTileId();
   }
 
   public static class EventReaders.TileEnterEvent {
-    method public int getTileId();
   }
 
   public static class EventReaders.TileLeaveEvent {
-    method public int getTileId();
   }
 
   public static class EventReaders.TileRemoveEvent {
-    method public int getTileId();
   }
 
   public class RequestReaders {
@@ -828,14 +824,12 @@
   public static class RequestReaders.ResourcesRequest {
     method public androidx.wear.tiles.readers.DeviceParametersReaders.DeviceParameters getDeviceParameters();
     method public java.util.List<java.lang.String!> getResourceIds();
-    method public int getTileId();
     method public String getVersion();
   }
 
   public static class RequestReaders.TileRequest {
     method public androidx.wear.tiles.readers.DeviceParametersReaders.DeviceParameters getDeviceParameters();
     method public androidx.wear.tiles.builders.StateBuilders.State getState();
-    method public int getTileId();
   }
 
 }
diff --git a/wear/tiles/tiles/api/public_plus_experimental_current.txt b/wear/tiles/tiles/api/public_plus_experimental_current.txt
index 5a4d2b4..4d017ea9 100644
--- a/wear/tiles/tiles/api/public_plus_experimental_current.txt
+++ b/wear/tiles/tiles/api/public_plus_experimental_current.txt
@@ -696,8 +696,8 @@
 
   public static final class ResourceBuilders.ImageResource.Builder {
     method public androidx.wear.tiles.builders.ResourceBuilders.ImageResource build();
-    method public androidx.wear.tiles.builders.ResourceBuilders.ImageResource.Builder setAndroidResourceByResid(androidx.wear.tiles.builders.ResourceBuilders.AndroidImageResourceByResId);
-    method public androidx.wear.tiles.builders.ResourceBuilders.ImageResource.Builder setAndroidResourceByResid(androidx.wear.tiles.builders.ResourceBuilders.AndroidImageResourceByResId.Builder);
+    method public androidx.wear.tiles.builders.ResourceBuilders.ImageResource.Builder setAndroidResourceByResId(androidx.wear.tiles.builders.ResourceBuilders.AndroidImageResourceByResId);
+    method public androidx.wear.tiles.builders.ResourceBuilders.ImageResource.Builder setAndroidResourceByResId(androidx.wear.tiles.builders.ResourceBuilders.AndroidImageResourceByResId.Builder);
     method public androidx.wear.tiles.builders.ResourceBuilders.ImageResource.Builder setInlineResource(androidx.wear.tiles.builders.ResourceBuilders.InlineImageResource);
     method public androidx.wear.tiles.builders.ResourceBuilders.ImageResource.Builder setInlineResource(androidx.wear.tiles.builders.ResourceBuilders.InlineImageResource.Builder);
   }
@@ -812,19 +812,15 @@
   }
 
   public static class EventReaders.TileAddEvent {
-    method public int getTileId();
   }
 
   public static class EventReaders.TileEnterEvent {
-    method public int getTileId();
   }
 
   public static class EventReaders.TileLeaveEvent {
-    method public int getTileId();
   }
 
   public static class EventReaders.TileRemoveEvent {
-    method public int getTileId();
   }
 
   public class RequestReaders {
@@ -833,14 +829,12 @@
   public static class RequestReaders.ResourcesRequest {
     method public androidx.wear.tiles.readers.DeviceParametersReaders.DeviceParameters getDeviceParameters();
     method public java.util.List<java.lang.String!> getResourceIds();
-    method public int getTileId();
     method public String getVersion();
   }
 
   public static class RequestReaders.TileRequest {
     method public androidx.wear.tiles.readers.DeviceParametersReaders.DeviceParameters getDeviceParameters();
     method public androidx.wear.tiles.builders.StateBuilders.State getState();
-    method public int getTileId();
   }
 
 }
diff --git a/wear/tiles/tiles/api/restricted_current.txt b/wear/tiles/tiles/api/restricted_current.txt
index ee9c959..708ea18 100644
--- a/wear/tiles/tiles/api/restricted_current.txt
+++ b/wear/tiles/tiles/api/restricted_current.txt
@@ -691,8 +691,8 @@
 
   public static final class ResourceBuilders.ImageResource.Builder {
     method public androidx.wear.tiles.builders.ResourceBuilders.ImageResource build();
-    method public androidx.wear.tiles.builders.ResourceBuilders.ImageResource.Builder setAndroidResourceByResid(androidx.wear.tiles.builders.ResourceBuilders.AndroidImageResourceByResId);
-    method public androidx.wear.tiles.builders.ResourceBuilders.ImageResource.Builder setAndroidResourceByResid(androidx.wear.tiles.builders.ResourceBuilders.AndroidImageResourceByResId.Builder);
+    method public androidx.wear.tiles.builders.ResourceBuilders.ImageResource.Builder setAndroidResourceByResId(androidx.wear.tiles.builders.ResourceBuilders.AndroidImageResourceByResId);
+    method public androidx.wear.tiles.builders.ResourceBuilders.ImageResource.Builder setAndroidResourceByResId(androidx.wear.tiles.builders.ResourceBuilders.AndroidImageResourceByResId.Builder);
     method public androidx.wear.tiles.builders.ResourceBuilders.ImageResource.Builder setInlineResource(androidx.wear.tiles.builders.ResourceBuilders.InlineImageResource);
     method public androidx.wear.tiles.builders.ResourceBuilders.ImageResource.Builder setInlineResource(androidx.wear.tiles.builders.ResourceBuilders.InlineImageResource.Builder);
   }
@@ -807,19 +807,15 @@
   }
 
   public static class EventReaders.TileAddEvent {
-    method public int getTileId();
   }
 
   public static class EventReaders.TileEnterEvent {
-    method public int getTileId();
   }
 
   public static class EventReaders.TileLeaveEvent {
-    method public int getTileId();
   }
 
   public static class EventReaders.TileRemoveEvent {
-    method public int getTileId();
   }
 
   public class RequestReaders {
@@ -828,14 +824,12 @@
   public static class RequestReaders.ResourcesRequest {
     method public androidx.wear.tiles.readers.DeviceParametersReaders.DeviceParameters getDeviceParameters();
     method public java.util.List<java.lang.String!> getResourceIds();
-    method public int getTileId();
     method public String getVersion();
   }
 
   public static class RequestReaders.TileRequest {
     method public androidx.wear.tiles.readers.DeviceParametersReaders.DeviceParameters getDeviceParameters();
     method public androidx.wear.tiles.builders.StateBuilders.State getState();
-    method public int getTileId();
   }
 
 }
diff --git a/wear/tiles/tiles/lint-baseline.xml b/wear/tiles/tiles/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/wear/tiles/tiles/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/wear/tiles/tiles/src/main/java/androidx/wear/tiles/builders/ResourceBuilders.java b/wear/tiles/tiles/src/main/java/androidx/wear/tiles/builders/ResourceBuilders.java
index 16abf2d..e2d8230 100644
--- a/wear/tiles/tiles/src/main/java/androidx/wear/tiles/builders/ResourceBuilders.java
+++ b/wear/tiles/tiles/src/main/java/androidx/wear/tiles/builders/ResourceBuilders.java
@@ -241,18 +241,18 @@
             /** Sets an image resource that maps to an Android drawable by resource ID. */
             @SuppressLint("MissingGetterMatchingBuilder")
             @NonNull
-            public Builder setAndroidResourceByResid(
-                    @NonNull AndroidImageResourceByResId androidResourceByResid) {
-                mImpl.setAndroidResourceByResid(androidResourceByResid.toProto());
+            public Builder setAndroidResourceByResId(
+                    @NonNull AndroidImageResourceByResId androidResourceByResId) {
+                mImpl.setAndroidResourceByResId(androidResourceByResId.toProto());
                 return this;
             }
 
             /** Sets an image resource that maps to an Android drawable by resource ID. */
             @SuppressLint("MissingGetterMatchingBuilder")
             @NonNull
-            public Builder setAndroidResourceByResid(
-                    @NonNull AndroidImageResourceByResId.Builder androidResourceByResidBuilder) {
-                mImpl.setAndroidResourceByResid(androidResourceByResidBuilder.build().toProto());
+            public Builder setAndroidResourceByResId(
+                    @NonNull AndroidImageResourceByResId.Builder androidResourceByResIdBuilder) {
+                mImpl.setAndroidResourceByResId(androidResourceByResIdBuilder.build().toProto());
                 return this;
             }
 
@@ -324,7 +324,8 @@
              * the resources.
              *
              * <p>This value must match the version of the resources required by the tile for the
-             * tile to render successfully.
+             * tile to render successfully, and must match the resource version specified in
+             * ResourcesRequest which triggered this request.
              */
             @SuppressLint("MissingGetterMatchingBuilder")
             @NonNull
diff --git a/wear/tiles/tiles/src/main/java/androidx/wear/tiles/builders/TileBuilders.java b/wear/tiles/tiles/src/main/java/androidx/wear/tiles/builders/TileBuilders.java
index ff5b034..847aacd 100644
--- a/wear/tiles/tiles/src/main/java/androidx/wear/tiles/builders/TileBuilders.java
+++ b/wear/tiles/tiles/src/main/java/androidx/wear/tiles/builders/TileBuilders.java
@@ -66,8 +66,12 @@
 
             Builder() {}
 
-            /** Sets the resource version required for these tiles. */
             @SuppressLint("MissingGetterMatchingBuilder")
+            /**
+             * Sets the resource version required for these tiles. This can be any developer-defined
+             * string; it is only used to cache resources, and is passed in ResourcesRequest if the
+             * system does not have a copy of the specified resource version.
+             */
             @NonNull
             public Builder setResourcesVersion(@NonNull String resourcesVersion) {
                 mImpl.setResourcesVersion(resourcesVersion);
@@ -96,6 +100,12 @@
              * point in the future after this interval has lapsed. A value of 0 here signifies that
              * auto-refreshes should not be used (i.e. you will manually request updates via
              * TileProviderService#getRequester).
+             *
+             * <p>This mechanism should not be used to update your tile more frequently than once a
+             * minute, and the system may throttle your updates if you request updates faster than
+             * this interval. This interval is also inexact; the system will generally update your
+             * tile if it is on-screen, or about to be on-screen, although this is not guaranteed
+             * due to system-level optimizations.
              */
             @SuppressLint("MissingGetterMatchingBuilder")
             @NonNull
diff --git a/wear/tiles/tiles/src/main/java/androidx/wear/tiles/readers/EventReaders.java b/wear/tiles/tiles/src/main/java/androidx/wear/tiles/readers/EventReaders.java
index 2ae5a0a..f015db7 100644
--- a/wear/tiles/tiles/src/main/java/androidx/wear/tiles/readers/EventReaders.java
+++ b/wear/tiles/tiles/src/main/java/androidx/wear/tiles/readers/EventReaders.java
@@ -33,6 +33,7 @@
 
     /** Reader for Tile add event parameters. */
     public static class TileAddEvent {
+        @SuppressWarnings("UnusedVariable")
         private final EventProto.TileAddEvent mProto;
 
         private TileAddEvent(@NonNull EventProto.TileAddEvent proto) {
@@ -57,15 +58,11 @@
                         "Passed TileAddEventData did not contain a valid proto payload", ex);
             }
         }
-
-        /** Get the tile ID of the tile added to the carousel. */
-        public int getTileId() {
-            return mProto.getTileId();
-        }
     }
 
     /** Reader for Tile remove event parameters. */
     public static class TileRemoveEvent {
+        @SuppressWarnings("UnusedVariable")
         private final EventProto.TileRemoveEvent mProto;
 
         private TileRemoveEvent(@NonNull EventProto.TileRemoveEvent proto) {
@@ -90,15 +87,11 @@
                         "Passed TileRemoveEventData did not contain a valid proto payload", ex);
             }
         }
-
-        /** Get the tile ID of the tile removed from the carousel. */
-        public int getTileId() {
-            return mProto.getTileId();
-        }
     }
 
     /** Reader for Tile enter event parameters. */
     public static class TileEnterEvent {
+        @SuppressWarnings("UnusedVariable")
         private final EventProto.TileEnterEvent mProto;
 
         private TileEnterEvent(@NonNull EventProto.TileEnterEvent proto) {
@@ -123,15 +116,11 @@
                         "Passed TileEnterEventData did not contain a valid proto payload", ex);
             }
         }
-
-        /** Get the tile ID of the tile that was entered. */
-        public int getTileId() {
-            return mProto.getTileId();
-        }
     }
 
     /** Reader for a Tile leave event parameters. */
     public static class TileLeaveEvent {
+        @SuppressWarnings("UnusedVariable")
         private final EventProto.TileLeaveEvent mProto;
 
         private TileLeaveEvent(@NonNull EventProto.TileLeaveEvent proto) {
@@ -156,10 +145,5 @@
                         "Passed TileLeaveEventData did not contain a valid proto payload", ex);
             }
         }
-
-        /** Get the tile ID of the tile that was left. */
-        public int getTileId() {
-            return mProto.getTileId();
-        }
     }
 }
diff --git a/wear/tiles/tiles/src/main/java/androidx/wear/tiles/readers/RequestReaders.java b/wear/tiles/tiles/src/main/java/androidx/wear/tiles/readers/RequestReaders.java
index d3a50de6..4b2d024 100644
--- a/wear/tiles/tiles/src/main/java/androidx/wear/tiles/readers/RequestReaders.java
+++ b/wear/tiles/tiles/src/main/java/androidx/wear/tiles/readers/RequestReaders.java
@@ -36,6 +36,7 @@
     /** Reader for Tile request parameters. */
     public static class TileRequest {
         private final RequestProto.TileRequest mProto;
+        @SuppressWarnings("UnusedVariable")
         private final int mTileId;
 
         private TileRequest(RequestProto.TileRequest proto, int tileId) {
@@ -55,10 +56,6 @@
             return new DeviceParameters(mProto.getDeviceParameters());
         }
 
-        public int getTileId() {
-            return mTileId;
-        }
-
         /** @hide */
         @RestrictTo(Scope.LIBRARY)
         @NonNull
@@ -78,6 +75,7 @@
     /** Reader for resource request parameters. */
     public static class ResourcesRequest {
         private final RequestProto.ResourcesRequest mProto;
+        @SuppressWarnings("UnusedVariable")
         private final int mTileId;
 
         private ResourcesRequest(@NonNull RequestProto.ResourcesRequest proto, int tileId) {
@@ -101,10 +99,6 @@
             }
         }
 
-        public int getTileId() {
-            return mTileId;
-        }
-
         /** Get the requested resource version. */
         @NonNull
         public String getVersion() {
diff --git a/wear/tiles/tiles/src/test/java/androidx/wear/tiles/TileProviderServiceTest.java b/wear/tiles/tiles/src/test/java/androidx/wear/tiles/TileProviderServiceTest.java
index b9b7c3a..b297695 100644
--- a/wear/tiles/tiles/src/test/java/androidx/wear/tiles/TileProviderServiceTest.java
+++ b/wear/tiles/tiles/src/test/java/androidx/wear/tiles/TileProviderServiceTest.java
@@ -139,78 +139,74 @@
     @Test
     public void tileProvider_onTileAdd() throws Exception {
         EventProto.TileAddEvent addRequest =
-                EventProto.TileAddEvent.newBuilder().setTileId(TILE_ID).build();
+                EventProto.TileAddEvent.getDefaultInstance();
         mTileProviderServiceStub.onTileAddEvent(
                 new TileAddEventData(addRequest.toByteArray(), TileAddEventData.VERSION_PROTOBUF));
         shadowOf(Looper.getMainLooper()).idle();
 
-        expect.that(mDummyTileProviderServiceServiceController.get().mLastOnTileAddId)
-                .isEqualTo(TILE_ID);
+        expect.that(mDummyTileProviderServiceServiceController.get().mOnTileAddCalled).isTrue();
     }
 
     @Test
     public void tileProvider_onTileRemove() throws Exception {
         EventProto.TileRemoveEvent removeRequest =
-                EventProto.TileRemoveEvent.newBuilder().setTileId(TILE_ID).build();
+                EventProto.TileRemoveEvent.getDefaultInstance();
         mTileProviderServiceStub.onTileRemoveEvent(
                 new TileRemoveEventData(
                         removeRequest.toByteArray(), TileRemoveEventData.VERSION_PROTOBUF));
         shadowOf(Looper.getMainLooper()).idle();
 
-        expect.that(mDummyTileProviderServiceServiceController.get().mLastOnTileRemoveId)
-                .isEqualTo(TILE_ID);
+        expect.that(mDummyTileProviderServiceServiceController.get().mOnTileRemoveCalled).isTrue();
     }
 
     @Test
     public void tileProvider_onTileEnter() throws Exception {
         EventProto.TileEnterEvent enterRequest =
-                EventProto.TileEnterEvent.newBuilder().setTileId(TILE_ID).build();
+                EventProto.TileEnterEvent.getDefaultInstance();
         mTileProviderServiceStub.onTileEnterEvent(
                 new TileEnterEventData(
                         enterRequest.toByteArray(), TileEnterEventData.VERSION_PROTOBUF));
         shadowOf(Looper.getMainLooper()).idle();
 
-        expect.that(mDummyTileProviderServiceServiceController.get().mLastOnTileEnterId)
-                .isEqualTo(TILE_ID);
+        expect.that(mDummyTileProviderServiceServiceController.get().mOnTileEnterCalled).isTrue();
     }
 
     @Test
     public void tileProvider_onTileLeave() throws Exception {
         EventProto.TileLeaveEvent leaveRequest =
-                EventProto.TileLeaveEvent.newBuilder().setTileId(TILE_ID).build();
+                EventProto.TileLeaveEvent.getDefaultInstance();
         mTileProviderServiceStub.onTileLeaveEvent(
                 new TileLeaveEventData(
                         leaveRequest.toByteArray(), TileLeaveEventData.VERSION_PROTOBUF));
         shadowOf(Looper.getMainLooper()).idle();
 
-        expect.that(mDummyTileProviderServiceServiceController.get().mLastOnTileLeaveId)
-                .isEqualTo(TILE_ID);
+        expect.that(mDummyTileProviderServiceServiceController.get().mOnTileLeaveCalled).isTrue();
     }
 
     public static class DummyTileProviderService extends TileProviderService {
-        int mLastOnTileAddId = -1;
-        int mLastOnTileRemoveId = -1;
-        int mLastOnTileEnterId = -1;
-        int mLastOnTileLeaveId = -1;
+        boolean mOnTileAddCalled = false;
+        boolean mOnTileRemoveCalled = false;
+        boolean mOnTileEnterCalled = false;
+        boolean mOnTileLeaveCalled = false;
 
         @Override
         protected void onTileAddEvent(@NonNull TileAddEvent requestParams) {
-            this.mLastOnTileAddId = requestParams.getTileId();
+            mOnTileAddCalled = true;
         }
 
         @Override
         protected void onTileRemoveEvent(@NonNull TileRemoveEvent requestParams) {
-            this.mLastOnTileRemoveId = requestParams.getTileId();
+            mOnTileRemoveCalled = true;
         }
 
         @Override
         protected void onTileEnterEvent(@NonNull TileEnterEvent requestParams) {
-            this.mLastOnTileEnterId = requestParams.getTileId();
+            mOnTileEnterCalled = true;
         }
 
         @Override
         protected void onTileLeaveEvent(@NonNull TileLeaveEvent requestParams) {
-            this.mLastOnTileLeaveId = requestParams.getTileId();
+            mOnTileLeaveCalled = true;
         }
 
         @Override
diff --git a/wear/wear-complications-data/api/restricted_current.txt b/wear/wear-complications-data/api/restricted_current.txt
index 1dc59bd..1ca52a5 100644
--- a/wear/wear-complications-data/api/restricted_current.txt
+++ b/wear/wear-complications-data/api/restricted_current.txt
@@ -173,6 +173,8 @@
     field @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public static final String ACTION_REQUEST_UPDATE_ALL_ACTIVE = "android.support.wearable.complications.ACTION_REQUEST_UPDATE_ALL_ACTIVE";
     field @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public static final String ACTION_START_PROVIDER_CHOOSER = "android.support.wearable.complications.ACTION_START_PROVIDER_CHOOSER";
     field @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public static final String EXTRA_WATCH_FACE_COMPONENT = "android.support.wearable.complications.EXTRA_WATCH_FACE_COMPONENT";
+    field public static boolean skipPermissionCheck;
+    field public static boolean useTestComplicationProviderChooserActivity;
   }
 
   public final class ComplicationProviderInfo {
@@ -191,11 +193,11 @@
   }
 
   public final class DefaultComplicationProviderPolicy {
-    ctor @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public DefaultComplicationProviderPolicy(java.util.List<android.content.ComponentName> providers, @androidx.wear.complications.SystemProviders.Companion.ProviderId int systemProviderFallback);
+    ctor @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public DefaultComplicationProviderPolicy(java.util.List<android.content.ComponentName> providers, @androidx.wear.complications.SystemProviders.ProviderId int systemProviderFallback);
     ctor public DefaultComplicationProviderPolicy();
-    ctor public DefaultComplicationProviderPolicy(@androidx.wear.complications.SystemProviders.Companion.ProviderId int systemProvider);
-    ctor public DefaultComplicationProviderPolicy(android.content.ComponentName provider, @androidx.wear.complications.SystemProviders.Companion.ProviderId int systemProviderFallback);
-    ctor public DefaultComplicationProviderPolicy(android.content.ComponentName primaryProvider, android.content.ComponentName secondaryProvider, @androidx.wear.complications.SystemProviders.Companion.ProviderId int systemProviderFallback);
+    ctor public DefaultComplicationProviderPolicy(@androidx.wear.complications.SystemProviders.ProviderId int systemProvider);
+    ctor public DefaultComplicationProviderPolicy(android.content.ComponentName provider, @androidx.wear.complications.SystemProviders.ProviderId int systemProviderFallback);
+    ctor public DefaultComplicationProviderPolicy(android.content.ComponentName primaryProvider, android.content.ComponentName secondaryProvider, @androidx.wear.complications.SystemProviders.ProviderId int systemProviderFallback);
     method public android.content.ComponentName? getPrimaryProvider();
     method public android.content.ComponentName? getSecondaryProvider();
     method public int getSystemProviderFallback();
@@ -266,7 +268,7 @@
   public static final class SystemProviders.Companion {
   }
 
-  @IntDef({androidx.wear.complications.SystemProviders.NO_PROVIDER, androidx.wear.complications.SystemProviders.PROVIDER_WATCH_BATTERY, androidx.wear.complications.SystemProviders.PROVIDER_DATE, androidx.wear.complications.SystemProviders.PROVIDER_TIME_AND_DATE, androidx.wear.complications.SystemProviders.PROVIDER_STEP_COUNT, androidx.wear.complications.SystemProviders.PROVIDER_WORLD_CLOCK, androidx.wear.complications.SystemProviders.PROVIDER_APP_SHORTCUT, androidx.wear.complications.SystemProviders.PROVIDER_UNREAD_NOTIFICATION_COUNT, androidx.wear.complications.SystemProviders.PROVIDER_NEXT_EVENT, androidx.wear.complications.SystemProviders.PROVIDER_RETAIL_STEP_COUNT, androidx.wear.complications.SystemProviders.PROVIDER_RETAIL_CHAT, androidx.wear.complications.SystemProviders.PROVIDER_SUNRISE_SUNSET, androidx.wear.complications.SystemProviders.PROVIDER_DAY_OF_WEEK, androidx.wear.complications.SystemProviders.PROVIDER_FAVORITE_CONTACT, androidx.wear.complications.SystemProviders.PROVIDER_MOST_RECENT_APP, androidx.wear.complications.SystemProviders.PROVIDER_DAY_AND_DATE}) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention) public static @interface SystemProviders.Companion.ProviderId {
+  @IntDef({androidx.wear.complications.SystemProviders.NO_PROVIDER, androidx.wear.complications.SystemProviders.PROVIDER_WATCH_BATTERY, androidx.wear.complications.SystemProviders.PROVIDER_DATE, androidx.wear.complications.SystemProviders.PROVIDER_TIME_AND_DATE, androidx.wear.complications.SystemProviders.PROVIDER_STEP_COUNT, androidx.wear.complications.SystemProviders.PROVIDER_WORLD_CLOCK, androidx.wear.complications.SystemProviders.PROVIDER_APP_SHORTCUT, androidx.wear.complications.SystemProviders.PROVIDER_UNREAD_NOTIFICATION_COUNT, androidx.wear.complications.SystemProviders.PROVIDER_NEXT_EVENT, androidx.wear.complications.SystemProviders.PROVIDER_RETAIL_STEP_COUNT, androidx.wear.complications.SystemProviders.PROVIDER_RETAIL_CHAT, androidx.wear.complications.SystemProviders.PROVIDER_SUNRISE_SUNSET, androidx.wear.complications.SystemProviders.PROVIDER_DAY_OF_WEEK, androidx.wear.complications.SystemProviders.PROVIDER_FAVORITE_CONTACT, androidx.wear.complications.SystemProviders.PROVIDER_MOST_RECENT_APP, androidx.wear.complications.SystemProviders.PROVIDER_DAY_AND_DATE}) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention) public static @interface SystemProviders.ProviderId {
   }
 
 }
diff --git a/wear/wear-complications-data/src/main/java/androidx/wear/complications/ComplicationHelperActivity.java b/wear/wear-complications-data/src/main/java/androidx/wear/complications/ComplicationHelperActivity.java
index a0a7b71..de5f491 100644
--- a/wear/wear-complications-data/src/main/java/androidx/wear/complications/ComplicationHelperActivity.java
+++ b/wear/wear-complications-data/src/main/java/androidx/wear/complications/ComplicationHelperActivity.java
@@ -54,6 +54,7 @@
  * <p>Or, to request the permission, for instance if {@link ComplicationData} of {@link
  * ComplicationData#TYPE_NO_PERMISSION TYPE_NO_PERMISSION} has been received and tapped on, use
  * {@link #createPermissionRequestHelperIntent}.
+ *
  * @hide
  */
 @TargetApi(Build.VERSION_CODES.N)
@@ -62,6 +63,20 @@
 public final class ComplicationHelperActivity extends Activity
         implements ActivityCompat.OnRequestPermissionsResultCallback {
 
+    /**
+     * Whether to invoke a specified activity instead of the system's provider chooser.
+     *
+     * To be used in tests.
+     */
+    public static boolean useTestComplicationProviderChooserActivity = false;
+
+    /**
+     * Whether to skip th permission check and directly attempt to invoke the provider chooser.
+     *
+     * To be used in tests.
+     */
+    public static boolean skipPermissionCheck = false;
+
     /** @hide */
     @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
     public static final String ACTION_REQUEST_UPDATE_ALL_ACTIVE =
@@ -95,9 +110,14 @@
     private static final String COMPLICATIONS_PERMISSION_PRIVILEGED =
             "com.google.android.wearable.permission.RECEIVE_COMPLICATION_DATA_PRIVILEGED";
 
-    @Nullable private ComponentName mWatchFace;
+    @Nullable
+    private ComponentName mWatchFace;
     private int mWfComplicationId;
-    @Nullable @ComplicationData.ComplicationType private int[] mTypes;
+    @Nullable
+    private Bundle mAdditionalExtras;
+    @Nullable
+    @ComplicationData.ComplicationType
+    private int[] mTypes;
 
     @Override
     protected void onCreate(@Nullable Bundle savedInstanceState) {
@@ -115,12 +135,13 @@
                 mWfComplicationId =
                         intent.getIntExtra(ProviderChooserIntent.EXTRA_COMPLICATION_ID, 0);
                 mTypes = intent.getIntArrayExtra(ProviderChooserIntent.EXTRA_SUPPORTED_TYPES);
+                mAdditionalExtras = getAdditionalExtras(intent);
                 if (checkPermission()) {
                     startProviderChooser();
                 } else {
                     ActivityCompat.requestPermissions(
                             this,
-                            new String[] {COMPLICATIONS_PERMISSION},
+                            new String[]{COMPLICATIONS_PERMISSION},
                             PERMISSION_REQUEST_CODE_PROVIDER_CHOOSER);
                 }
                 break;
@@ -133,7 +154,7 @@
                 } else {
                     ActivityCompat.requestPermissions(
                             this,
-                            new String[] {COMPLICATIONS_PERMISSION},
+                            new String[]{COMPLICATIONS_PERMISSION},
                             PERMISSION_REQUEST_CODE_REQUEST_ONLY);
                 }
                 break;
@@ -171,9 +192,10 @@
 
     private boolean checkPermission() {
         return ActivityCompat.checkSelfPermission(this, COMPLICATIONS_PERMISSION_PRIVILEGED)
-                        == PackageManager.PERMISSION_GRANTED
+                == PackageManager.PERMISSION_GRANTED
                 || ActivityCompat.checkSelfPermission(this, COMPLICATIONS_PERMISSION)
-                        == PackageManager.PERMISSION_GRANTED;
+                == PackageManager.PERMISSION_GRANTED
+                || skipPermissionCheck;
     }
 
     /**
@@ -204,15 +226,18 @@
      *
      * <p>From android R onwards this API can only be called during an editing session.
      *
-     * @param context context for the current app, that must contain a ComplicationHelperActivity
-     * @param watchFace the ComponentName of the WatchFaceService being configured.
+     * @param context                 context for the current app, that must contain a
+     *                                ComplicationHelperActivity
+     * @param watchFace               the ComponentName of the WatchFaceService being configured.
      * @param watchFaceComplicationId the watch face's id for the complication being configured.
-     *     This must match the id passed in when the watch face calls
-     *     WatchFaceService.Engine#setActiveComplications.
-     * @param supportedTypes the types supported by the complication, in decreasing order of
-     *     preference. If a provider can supply data for more than one of these types, the type
-     *     chosen will be whichever was specified first.
-     * @param watchFaceInstanceId The ID of the watchface being edited.
+     *                                This must match the id passed in when the watch face calls
+     *                                WatchFaceService.Engine#setActiveComplications.
+     * @param supportedTypes          the types supported by the complication, in decreasing
+     *                                order of
+     *                                preference. If a provider can supply data for more than one
+     *                                of these types, the type
+     *                                chosen will be whichever was specified first.
+     * @param watchFaceInstanceId     The ID of the watchface being edited.
      */
     @NonNull
     public static Intent createProviderChooserHelperIntent(
@@ -253,7 +278,7 @@
      * watch face will be triggered. The provided {@code watchFace} must match the current watch
      * face for this to occur.
      *
-     * @param context context for the current app, that must contain a ComplicationHelperActivity
+     * @param context   context for the current app, that must contain a ComplicationHelperActivity
      * @param watchFace the ComponentName of the WatchFaceService for the current watch face
      */
     @NonNull
@@ -266,10 +291,22 @@
     }
 
     private void startProviderChooser() {
-        startActivityForResult(
+        Intent intent =
                 ProviderChooserIntent.createProviderChooserIntent(
-                        mWatchFace, mWfComplicationId, mTypes),
-                START_REQUEST_CODE_PROVIDER_CHOOSER);
+                        mWatchFace, mWfComplicationId, mTypes);
+        // Add the extras that were provided to the ComplicationHelperActivity. This is done by
+        // first taking the additional extras and adding to that anything that was set in the
+        // chooser intent, and setting them back on the intent itself to avoid the additional
+        // extras being able to override anything that was set by the chooser intent.
+        Bundle extras = new Bundle(mAdditionalExtras);
+        extras.putAll(intent.getExtras());
+        intent.replaceExtras(extras);
+        if (useTestComplicationProviderChooserActivity) {
+            intent.setComponent(new ComponentName(
+                    "androidx.wear.watchface.editor.test",
+                    "androidx.wear.watchface.editor.TestComplicationProviderChooserActivity"));
+        }
+        startActivityForResult(intent, START_REQUEST_CODE_PROVIDER_CHOOSER);
     }
 
     /** Requests that the system update all active complications on the watch face. */
@@ -283,4 +320,17 @@
                 PendingIntent.getActivity(this, 0, new Intent(""), 0));
         sendBroadcast(intent);
     }
+
+    /**
+     * Returns any extras that were not handled by the activity itself.
+     *
+     * <p>These will be forwarded to the chooser activity.
+     */
+    private Bundle getAdditionalExtras(Intent intent) {
+        Bundle extras = intent.getExtras();
+        extras.remove(ProviderChooserIntent.EXTRA_WATCH_FACE_COMPONENT_NAME);
+        extras.remove(ProviderChooserIntent.EXTRA_COMPLICATION_ID);
+        extras.remove(ProviderChooserIntent.EXTRA_SUPPORTED_TYPES);
+        return extras;
+    }
 }
diff --git a/wear/wear-complications-data/src/main/java/androidx/wear/complications/DefaultComplicationProviderPolicy.kt b/wear/wear-complications-data/src/main/java/androidx/wear/complications/DefaultComplicationProviderPolicy.kt
index 902dece..1ab0bb6 100644
--- a/wear/wear-complications-data/src/main/java/androidx/wear/complications/DefaultComplicationProviderPolicy.kt
+++ b/wear/wear-complications-data/src/main/java/androidx/wear/complications/DefaultComplicationProviderPolicy.kt
@@ -18,7 +18,7 @@
 
 import android.content.ComponentName
 import androidx.annotation.RestrictTo
-import androidx.wear.complications.SystemProviders.Companion.ProviderId
+import androidx.wear.complications.SystemProviders.ProviderId
 import java.util.ArrayList
 
 /**
diff --git a/wear/wear-complications-data/src/main/java/androidx/wear/complications/SystemProviders.kt b/wear/wear-complications-data/src/main/java/androidx/wear/complications/SystemProviders.kt
index 862bf3d..839ca39 100644
--- a/wear/wear-complications-data/src/main/java/androidx/wear/complications/SystemProviders.kt
+++ b/wear/wear-complications-data/src/main/java/androidx/wear/complications/SystemProviders.kt
@@ -194,34 +194,34 @@
          * This provider supports only [ComplicationType.SHORT_TEXT].
          */
         public const val PROVIDER_DAY_AND_DATE: Int = 16
-
-        /**
-         * System provider id as defined in [SystemProviders].
-         *
-         * @hide
-         */
-        @IntDef(
-            NO_PROVIDER,
-            PROVIDER_WATCH_BATTERY,
-            PROVIDER_DATE,
-            PROVIDER_TIME_AND_DATE,
-            PROVIDER_STEP_COUNT,
-            PROVIDER_WORLD_CLOCK,
-            PROVIDER_APP_SHORTCUT,
-            PROVIDER_UNREAD_NOTIFICATION_COUNT,
-            PROVIDER_NEXT_EVENT,
-            PROVIDER_RETAIL_STEP_COUNT,
-            PROVIDER_RETAIL_CHAT,
-            PROVIDER_SUNRISE_SUNSET,
-            PROVIDER_DAY_OF_WEEK,
-            PROVIDER_FAVORITE_CONTACT,
-            PROVIDER_MOST_RECENT_APP,
-            PROVIDER_DAY_AND_DATE
-        )
-        @RestrictTo(
-            RestrictTo.Scope.LIBRARY_GROUP
-        )
-        @Retention(AnnotationRetention.SOURCE)
-        public annotation class ProviderId
     }
+
+    /**
+     * System provider id as defined in [SystemProviders].
+     *
+     * @hide
+     */
+    @IntDef(
+        NO_PROVIDER,
+        PROVIDER_WATCH_BATTERY,
+        PROVIDER_DATE,
+        PROVIDER_TIME_AND_DATE,
+        PROVIDER_STEP_COUNT,
+        PROVIDER_WORLD_CLOCK,
+        PROVIDER_APP_SHORTCUT,
+        PROVIDER_UNREAD_NOTIFICATION_COUNT,
+        PROVIDER_NEXT_EVENT,
+        PROVIDER_RETAIL_STEP_COUNT,
+        PROVIDER_RETAIL_CHAT,
+        PROVIDER_SUNRISE_SUNSET,
+        PROVIDER_DAY_OF_WEEK,
+        PROVIDER_FAVORITE_CONTACT,
+        PROVIDER_MOST_RECENT_APP,
+        PROVIDER_DAY_AND_DATE
+    )
+    @RestrictTo(
+        RestrictTo.Scope.LIBRARY_GROUP
+    )
+    @Retention(AnnotationRetention.SOURCE)
+    public annotation class ProviderId
 }
\ No newline at end of file
diff --git a/wear/wear-complications-provider/lint-baseline.xml b/wear/wear-complications-provider/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/wear/wear-complications-provider/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/wear/wear-complications-provider/samples/lint-baseline.xml b/wear/wear-complications-provider/samples/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/wear/wear-complications-provider/samples/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/wear/wear-input-testing/lint-baseline.xml b/wear/wear-input-testing/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/wear/wear-input-testing/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/wear/wear-input/api/current.txt b/wear/wear-input/api/current.txt
index dc46d45..5909312 100644
--- a/wear/wear-input/api/current.txt
+++ b/wear/wear-input/api/current.txt
@@ -9,39 +9,39 @@
 
   @RequiresApi(android.os.Build.VERSION_CODES.N) public final class RemoteInputIntentHelper {
     method public static android.content.Intent createActionRemoteInputIntent();
-    method public static String? getCancelLabelExtra(android.content.Intent intent);
-    method public static String? getConfirmLabelExtra(android.content.Intent intent);
-    method public static String? getInProgressLabelExtra(android.content.Intent intent);
+    method public static CharSequence? getCancelLabelExtra(android.content.Intent intent);
+    method public static CharSequence? getConfirmLabelExtra(android.content.Intent intent);
+    method public static CharSequence? getInProgressLabelExtra(android.content.Intent intent);
     method public static java.util.List<android.app.RemoteInput>? getRemoteInputsExtra(android.content.Intent intent);
     method public static java.util.List<java.lang.CharSequence>? getSmartReplyContextExtra(android.content.Intent intent);
-    method public static String? getTitleExtra(android.content.Intent intent);
+    method public static CharSequence? getTitleExtra(android.content.Intent intent);
     method public static boolean hasRemoteInputsExtra(android.content.Intent intent);
     method public static boolean isActionRemoteInput(android.content.Intent intent);
-    method public static android.content.Intent putCancelLabelExtra(android.content.Intent intent, String label);
-    method public static android.content.Intent putConfirmLabelExtra(android.content.Intent intent, String label);
-    method public static android.content.Intent putInProgressLabelExtra(android.content.Intent intent, String label);
+    method public static android.content.Intent putCancelLabelExtra(android.content.Intent intent, CharSequence label);
+    method public static android.content.Intent putConfirmLabelExtra(android.content.Intent intent, CharSequence label);
+    method public static android.content.Intent putInProgressLabelExtra(android.content.Intent intent, CharSequence label);
     method public static android.content.Intent putRemoteInputsExtra(android.content.Intent intent, java.util.List<android.app.RemoteInput> remoteInputs);
     method public static android.content.Intent putSmartReplyContextExtra(android.content.Intent intent, java.util.List<? extends java.lang.CharSequence> smartReplyContext);
-    method public static android.content.Intent putTitleExtra(android.content.Intent intent, String title);
+    method public static android.content.Intent putTitleExtra(android.content.Intent intent, CharSequence title);
     field public static final androidx.wear.input.RemoteInputIntentHelper.Companion Companion;
   }
 
   public static final class RemoteInputIntentHelper.Companion {
     method public android.content.Intent createActionRemoteInputIntent();
-    method public String? getCancelLabelExtra(android.content.Intent intent);
-    method public String? getConfirmLabelExtra(android.content.Intent intent);
-    method public String? getInProgressLabelExtra(android.content.Intent intent);
+    method public CharSequence? getCancelLabelExtra(android.content.Intent intent);
+    method public CharSequence? getConfirmLabelExtra(android.content.Intent intent);
+    method public CharSequence? getInProgressLabelExtra(android.content.Intent intent);
     method public java.util.List<android.app.RemoteInput>? getRemoteInputsExtra(android.content.Intent intent);
     method public java.util.List<java.lang.CharSequence>? getSmartReplyContextExtra(android.content.Intent intent);
-    method public String? getTitleExtra(android.content.Intent intent);
+    method public CharSequence? getTitleExtra(android.content.Intent intent);
     method public boolean hasRemoteInputsExtra(android.content.Intent intent);
     method public boolean isActionRemoteInput(android.content.Intent intent);
-    method public android.content.Intent putCancelLabelExtra(android.content.Intent intent, String label);
-    method public android.content.Intent putConfirmLabelExtra(android.content.Intent intent, String label);
-    method public android.content.Intent putInProgressLabelExtra(android.content.Intent intent, String label);
+    method public android.content.Intent putCancelLabelExtra(android.content.Intent intent, CharSequence label);
+    method public android.content.Intent putConfirmLabelExtra(android.content.Intent intent, CharSequence label);
+    method public android.content.Intent putInProgressLabelExtra(android.content.Intent intent, CharSequence label);
     method public android.content.Intent putRemoteInputsExtra(android.content.Intent intent, java.util.List<android.app.RemoteInput> remoteInputs);
     method public android.content.Intent putSmartReplyContextExtra(android.content.Intent intent, java.util.List<? extends java.lang.CharSequence> smartReplyContext);
-    method public android.content.Intent putTitleExtra(android.content.Intent intent, String title);
+    method public android.content.Intent putTitleExtra(android.content.Intent intent, CharSequence title);
   }
 
   public final class WearableButtons {
diff --git a/wear/wear-input/api/public_plus_experimental_current.txt b/wear/wear-input/api/public_plus_experimental_current.txt
index dc46d45..5909312 100644
--- a/wear/wear-input/api/public_plus_experimental_current.txt
+++ b/wear/wear-input/api/public_plus_experimental_current.txt
@@ -9,39 +9,39 @@
 
   @RequiresApi(android.os.Build.VERSION_CODES.N) public final class RemoteInputIntentHelper {
     method public static android.content.Intent createActionRemoteInputIntent();
-    method public static String? getCancelLabelExtra(android.content.Intent intent);
-    method public static String? getConfirmLabelExtra(android.content.Intent intent);
-    method public static String? getInProgressLabelExtra(android.content.Intent intent);
+    method public static CharSequence? getCancelLabelExtra(android.content.Intent intent);
+    method public static CharSequence? getConfirmLabelExtra(android.content.Intent intent);
+    method public static CharSequence? getInProgressLabelExtra(android.content.Intent intent);
     method public static java.util.List<android.app.RemoteInput>? getRemoteInputsExtra(android.content.Intent intent);
     method public static java.util.List<java.lang.CharSequence>? getSmartReplyContextExtra(android.content.Intent intent);
-    method public static String? getTitleExtra(android.content.Intent intent);
+    method public static CharSequence? getTitleExtra(android.content.Intent intent);
     method public static boolean hasRemoteInputsExtra(android.content.Intent intent);
     method public static boolean isActionRemoteInput(android.content.Intent intent);
-    method public static android.content.Intent putCancelLabelExtra(android.content.Intent intent, String label);
-    method public static android.content.Intent putConfirmLabelExtra(android.content.Intent intent, String label);
-    method public static android.content.Intent putInProgressLabelExtra(android.content.Intent intent, String label);
+    method public static android.content.Intent putCancelLabelExtra(android.content.Intent intent, CharSequence label);
+    method public static android.content.Intent putConfirmLabelExtra(android.content.Intent intent, CharSequence label);
+    method public static android.content.Intent putInProgressLabelExtra(android.content.Intent intent, CharSequence label);
     method public static android.content.Intent putRemoteInputsExtra(android.content.Intent intent, java.util.List<android.app.RemoteInput> remoteInputs);
     method public static android.content.Intent putSmartReplyContextExtra(android.content.Intent intent, java.util.List<? extends java.lang.CharSequence> smartReplyContext);
-    method public static android.content.Intent putTitleExtra(android.content.Intent intent, String title);
+    method public static android.content.Intent putTitleExtra(android.content.Intent intent, CharSequence title);
     field public static final androidx.wear.input.RemoteInputIntentHelper.Companion Companion;
   }
 
   public static final class RemoteInputIntentHelper.Companion {
     method public android.content.Intent createActionRemoteInputIntent();
-    method public String? getCancelLabelExtra(android.content.Intent intent);
-    method public String? getConfirmLabelExtra(android.content.Intent intent);
-    method public String? getInProgressLabelExtra(android.content.Intent intent);
+    method public CharSequence? getCancelLabelExtra(android.content.Intent intent);
+    method public CharSequence? getConfirmLabelExtra(android.content.Intent intent);
+    method public CharSequence? getInProgressLabelExtra(android.content.Intent intent);
     method public java.util.List<android.app.RemoteInput>? getRemoteInputsExtra(android.content.Intent intent);
     method public java.util.List<java.lang.CharSequence>? getSmartReplyContextExtra(android.content.Intent intent);
-    method public String? getTitleExtra(android.content.Intent intent);
+    method public CharSequence? getTitleExtra(android.content.Intent intent);
     method public boolean hasRemoteInputsExtra(android.content.Intent intent);
     method public boolean isActionRemoteInput(android.content.Intent intent);
-    method public android.content.Intent putCancelLabelExtra(android.content.Intent intent, String label);
-    method public android.content.Intent putConfirmLabelExtra(android.content.Intent intent, String label);
-    method public android.content.Intent putInProgressLabelExtra(android.content.Intent intent, String label);
+    method public android.content.Intent putCancelLabelExtra(android.content.Intent intent, CharSequence label);
+    method public android.content.Intent putConfirmLabelExtra(android.content.Intent intent, CharSequence label);
+    method public android.content.Intent putInProgressLabelExtra(android.content.Intent intent, CharSequence label);
     method public android.content.Intent putRemoteInputsExtra(android.content.Intent intent, java.util.List<android.app.RemoteInput> remoteInputs);
     method public android.content.Intent putSmartReplyContextExtra(android.content.Intent intent, java.util.List<? extends java.lang.CharSequence> smartReplyContext);
-    method public android.content.Intent putTitleExtra(android.content.Intent intent, String title);
+    method public android.content.Intent putTitleExtra(android.content.Intent intent, CharSequence title);
   }
 
   public final class WearableButtons {
diff --git a/wear/wear-input/api/restricted_current.txt b/wear/wear-input/api/restricted_current.txt
index dc46d45..5909312 100644
--- a/wear/wear-input/api/restricted_current.txt
+++ b/wear/wear-input/api/restricted_current.txt
@@ -9,39 +9,39 @@
 
   @RequiresApi(android.os.Build.VERSION_CODES.N) public final class RemoteInputIntentHelper {
     method public static android.content.Intent createActionRemoteInputIntent();
-    method public static String? getCancelLabelExtra(android.content.Intent intent);
-    method public static String? getConfirmLabelExtra(android.content.Intent intent);
-    method public static String? getInProgressLabelExtra(android.content.Intent intent);
+    method public static CharSequence? getCancelLabelExtra(android.content.Intent intent);
+    method public static CharSequence? getConfirmLabelExtra(android.content.Intent intent);
+    method public static CharSequence? getInProgressLabelExtra(android.content.Intent intent);
     method public static java.util.List<android.app.RemoteInput>? getRemoteInputsExtra(android.content.Intent intent);
     method public static java.util.List<java.lang.CharSequence>? getSmartReplyContextExtra(android.content.Intent intent);
-    method public static String? getTitleExtra(android.content.Intent intent);
+    method public static CharSequence? getTitleExtra(android.content.Intent intent);
     method public static boolean hasRemoteInputsExtra(android.content.Intent intent);
     method public static boolean isActionRemoteInput(android.content.Intent intent);
-    method public static android.content.Intent putCancelLabelExtra(android.content.Intent intent, String label);
-    method public static android.content.Intent putConfirmLabelExtra(android.content.Intent intent, String label);
-    method public static android.content.Intent putInProgressLabelExtra(android.content.Intent intent, String label);
+    method public static android.content.Intent putCancelLabelExtra(android.content.Intent intent, CharSequence label);
+    method public static android.content.Intent putConfirmLabelExtra(android.content.Intent intent, CharSequence label);
+    method public static android.content.Intent putInProgressLabelExtra(android.content.Intent intent, CharSequence label);
     method public static android.content.Intent putRemoteInputsExtra(android.content.Intent intent, java.util.List<android.app.RemoteInput> remoteInputs);
     method public static android.content.Intent putSmartReplyContextExtra(android.content.Intent intent, java.util.List<? extends java.lang.CharSequence> smartReplyContext);
-    method public static android.content.Intent putTitleExtra(android.content.Intent intent, String title);
+    method public static android.content.Intent putTitleExtra(android.content.Intent intent, CharSequence title);
     field public static final androidx.wear.input.RemoteInputIntentHelper.Companion Companion;
   }
 
   public static final class RemoteInputIntentHelper.Companion {
     method public android.content.Intent createActionRemoteInputIntent();
-    method public String? getCancelLabelExtra(android.content.Intent intent);
-    method public String? getConfirmLabelExtra(android.content.Intent intent);
-    method public String? getInProgressLabelExtra(android.content.Intent intent);
+    method public CharSequence? getCancelLabelExtra(android.content.Intent intent);
+    method public CharSequence? getConfirmLabelExtra(android.content.Intent intent);
+    method public CharSequence? getInProgressLabelExtra(android.content.Intent intent);
     method public java.util.List<android.app.RemoteInput>? getRemoteInputsExtra(android.content.Intent intent);
     method public java.util.List<java.lang.CharSequence>? getSmartReplyContextExtra(android.content.Intent intent);
-    method public String? getTitleExtra(android.content.Intent intent);
+    method public CharSequence? getTitleExtra(android.content.Intent intent);
     method public boolean hasRemoteInputsExtra(android.content.Intent intent);
     method public boolean isActionRemoteInput(android.content.Intent intent);
-    method public android.content.Intent putCancelLabelExtra(android.content.Intent intent, String label);
-    method public android.content.Intent putConfirmLabelExtra(android.content.Intent intent, String label);
-    method public android.content.Intent putInProgressLabelExtra(android.content.Intent intent, String label);
+    method public android.content.Intent putCancelLabelExtra(android.content.Intent intent, CharSequence label);
+    method public android.content.Intent putConfirmLabelExtra(android.content.Intent intent, CharSequence label);
+    method public android.content.Intent putInProgressLabelExtra(android.content.Intent intent, CharSequence label);
     method public android.content.Intent putRemoteInputsExtra(android.content.Intent intent, java.util.List<android.app.RemoteInput> remoteInputs);
     method public android.content.Intent putSmartReplyContextExtra(android.content.Intent intent, java.util.List<? extends java.lang.CharSequence> smartReplyContext);
-    method public android.content.Intent putTitleExtra(android.content.Intent intent, String title);
+    method public android.content.Intent putTitleExtra(android.content.Intent intent, CharSequence title);
   }
 
   public final class WearableButtons {
diff --git a/wear/wear-input/lint-baseline.xml b/wear/wear-input/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/wear/wear-input/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/wear/wear-input/src/main/java/androidx/wear/input/RemoteInputIntentHelper.kt b/wear/wear-input/src/main/java/androidx/wear/input/RemoteInputIntentHelper.kt
index e1f6a54..42cd43d 100644
--- a/wear/wear-input/src/main/java/androidx/wear/input/RemoteInputIntentHelper.kt
+++ b/wear/wear-input/src/main/java/androidx/wear/input/RemoteInputIntentHelper.kt
@@ -120,107 +120,109 @@
         ): Intent = intent.putExtra(EXTRA_REMOTE_INPUTS, ArrayList(remoteInputs))
 
         /**
-         * Returns the [String] from the given [Intent] that specifies what is displayed on top of
-         * the confirmation screen to describe the action.
+         * Returns the [CharSequence] from the given [Intent] that specifies what is displayed on
+         * top of the confirmation screen to describe the action.
          *
          * @param intent The intent with given data.
-         * @return The string previously added with [putTitleExtra] or null if no value is found.
+         * @return The CharSequence previously added with [putTitleExtra] or null if no value is
+         * found.
          */
         @JvmStatic
         @Nullable
-        public fun getTitleExtra(intent: Intent): String? = intent.getStringExtra(EXTRA_TITLE)
+        public fun getTitleExtra(intent: Intent): CharSequence? =
+            intent.getCharSequenceExtra(EXTRA_TITLE)
 
         /**
-         * Adds the [String] to the given [Intent] that specifies what is displayed on top of
+         * Adds the [CharSequence] to the given [Intent] that specifies what is displayed on top of
          * the confirmation screen to describe the action like "SMS" or "Email".
          *
          * @param intent The intent with given data.
-         * @param title The string to be added.
+         * @param title The CharSequence to be added.
          * @return The given intent.
          */
         @JvmStatic
         @NonNull
-        public fun putTitleExtra(intent: Intent, title: String): Intent =
+        public fun putTitleExtra(intent: Intent, title: CharSequence): Intent =
             intent.putExtra(EXTRA_TITLE, title)
 
         /**
-         * Returns the [String] from the given [Intent] that specifies what is displayed to
+         * Returns the [CharSequence] from the given [Intent] that specifies what is displayed to
          * cancel the action.
          *
          * @param intent The intent with given data.
-         * @return The string previously added with [putCancelLabelExtra] or null if no value is
-         * found.
+         * @return The CharSequence previously added with [putCancelLabelExtra] or null if no value
+         * is found.
          */
         @JvmStatic
         @Nullable
-        public fun getCancelLabelExtra(intent: Intent): String? =
-            intent.getStringExtra(EXTRA_CANCEL_LABEL)
+        public fun getCancelLabelExtra(intent: Intent): CharSequence? =
+            intent.getCharSequenceExtra(EXTRA_CANCEL_LABEL)
 
         /**
-         * Adds the [String] to the given [Intent] that specifies what is displayed to cancel the
-         * action. This is usually an imperative verb, like "Cancel". Defaults to Cancel.
+         * Adds the [CharSequence] to the given [Intent] that specifies what is displayed to cancel
+         * the action. This is usually an imperative verb, like "Cancel". Defaults to Cancel.
          *
          * @param intent The intent with given data.
-         * @param label The string to be added.
+         * @param label The CharSequence to be added.
          * @return The given intent.
          */
         @JvmStatic
         @NonNull
-        public fun putCancelLabelExtra(intent: Intent, label: String): Intent =
+        public fun putCancelLabelExtra(intent: Intent, label: CharSequence): Intent =
             intent.putExtra(EXTRA_CANCEL_LABEL, label)
 
         /**
-         * Returns the [String] from the given [Intent] that specifies what is displayed to
+         * Returns the [CharSequence] from the given [Intent] that specifies what is displayed to
          * confirm that the action should be executed.
          *
          * @param intent The intent with given data.
-         * @return The string previously added with [putConfirmLabelExtra] or null if no value is
-         * found.
+         * @return The CharSequence previously added with [putConfirmLabelExtra] or null if no value
+         * is found.
          */
         @JvmStatic
         @Nullable
-        public fun getConfirmLabelExtra(intent: Intent): String? =
-            intent.getStringExtra(EXTRA_CONFIRM_LABEL)
+        public fun getConfirmLabelExtra(intent: Intent): CharSequence? =
+            intent.getCharSequenceExtra(EXTRA_CONFIRM_LABEL)
 
         /**
-         * Adds the [String] to the given [Intent] that specifies what is displayed to confirm that
-         * the action should be executed. This is usually an imperative verb like "Send".
+         * Adds the [CharSequence] to the given [Intent] that specifies what is displayed to confirm
+         * that the action should be executed. This is usually an imperative verb like "Send".
          * Defaults to "Send".
          *
          * @param intent The intent with given data.
-         * @param label The string to be added.
+         * @param label The CharSequence to be added.
          * @return The given intent.
          */
         @JvmStatic
         @NonNull
-        public fun putConfirmLabelExtra(intent: Intent, label: String): Intent =
+        public fun putConfirmLabelExtra(intent: Intent, label: CharSequence): Intent =
             intent.putExtra(EXTRA_CONFIRM_LABEL, label)
 
         /**
-         * Returns the [String] from the given [Intent] that specifies what is displayed while the
-         * wearable is preparing to automatically execute the action.
+         * Returns the [CharSequence] from the given [Intent] that specifies what is displayed while
+         * the wearable is preparing to automatically execute the action.
          *
          * @param intent The intent with given data.
-         * @return The string previously added with [putInProgressLabelExtra] or null if no
+         * @return The CharSequence previously added with [putInProgressLabelExtra] or null if no
          * value is found.
          */
         @JvmStatic
         @Nullable
-        public fun getInProgressLabelExtra(intent: Intent): String? =
-            intent.getStringExtra(EXTRA_IN_PROGRESS_LABEL)
+        public fun getInProgressLabelExtra(intent: Intent): CharSequence? =
+            intent.getCharSequenceExtra(EXTRA_IN_PROGRESS_LABEL)
 
         /**
-         * Adds the [String] to the given [Intent] that specifies what is displayed while the
+         * Adds the [CharSequence] to the given [Intent] that specifies what is displayed while the
          * wearable is preparing to automatically execute the action. This is usually a 'ing'
          * verb ending in ellipsis like "Sending...". Defaults to "Sending...".
          *
          * @param intent The intent with given data.
-         * @param label The string to be added.
+         * @param label The CharSequence to be added.
          * @return The given intent.
          */
         @JvmStatic
         @NonNull
-        public fun putInProgressLabelExtra(intent: Intent, label: String): Intent =
+        public fun putInProgressLabelExtra(intent: Intent, label: CharSequence): Intent =
             intent.putExtra(EXTRA_IN_PROGRESS_LABEL, label)
 
         /**
diff --git a/wear/wear-ongoing/api/current.txt b/wear/wear-ongoing/api/current.txt
index 779ba555..edd5ae4 100644
--- a/wear/wear-ongoing/api/current.txt
+++ b/wear/wear-ongoing/api/current.txt
@@ -9,14 +9,14 @@
     method public int getNotificationId();
     method public int getOngoingActivityId();
     method public android.graphics.drawable.Icon getStaticIcon();
-    method public androidx.wear.ongoing.OngoingActivityStatus? getStatus();
+    method public androidx.wear.ongoing.Status? getStatus();
     method public String? getTag();
     method public long getTimestamp();
     method public android.app.PendingIntent getTouchIntent();
-    method public static androidx.wear.ongoing.OngoingActivity? recoverOngoingActivity(android.content.Context, java.util.function.Predicate<androidx.wear.ongoing.OngoingActivityData!>);
+    method public static androidx.wear.ongoing.OngoingActivity? recoverOngoingActivity(android.content.Context, java.util.function.Predicate<androidx.wear.ongoing.OngoingActivity!>);
     method public static androidx.wear.ongoing.OngoingActivity? recoverOngoingActivity(android.content.Context);
     method public static androidx.wear.ongoing.OngoingActivity? recoverOngoingActivity(android.content.Context, int);
-    method public void update(android.content.Context, androidx.wear.ongoing.OngoingActivityStatus);
+    method public void update(android.content.Context, androidx.wear.ongoing.Status);
   }
 
   public static final class OngoingActivity.Builder {
@@ -30,61 +30,49 @@
     method public androidx.wear.ongoing.OngoingActivity.Builder setOngoingActivityId(int);
     method public androidx.wear.ongoing.OngoingActivity.Builder setStaticIcon(android.graphics.drawable.Icon);
     method public androidx.wear.ongoing.OngoingActivity.Builder setStaticIcon(@DrawableRes int);
-    method public androidx.wear.ongoing.OngoingActivity.Builder setStatus(androidx.wear.ongoing.OngoingActivityStatus);
+    method public androidx.wear.ongoing.OngoingActivity.Builder setStatus(androidx.wear.ongoing.Status);
     method public androidx.wear.ongoing.OngoingActivity.Builder setTouchIntent(android.app.PendingIntent);
   }
 
-  public class OngoingActivityData implements androidx.versionedparcelable.VersionedParcelable {
+  public class SerializationHelper {
     method public static void copy(android.os.Bundle, android.os.Bundle);
-    method public static androidx.wear.ongoing.OngoingActivityData? create(android.app.Notification);
-    method public static androidx.wear.ongoing.OngoingActivityData? create(android.os.Bundle);
-    method public android.graphics.drawable.Icon? getAnimatedIcon();
-    method public String? getCategory();
-    method public androidx.core.content.LocusIdCompat? getLocusId();
-    method public int getOngoingActivityId();
-    method public android.graphics.drawable.Icon getStaticIcon();
-    method public androidx.wear.ongoing.OngoingActivityStatus? getStatus();
-    method public long getTimestamp();
-    method public android.app.PendingIntent getTouchIntent();
+    method public static androidx.wear.ongoing.OngoingActivity? create(android.app.Notification);
+    method public static androidx.wear.ongoing.OngoingActivity? create(android.os.Bundle);
     method public static boolean hasOngoingActivity(android.app.Notification);
   }
 
-  public class OngoingActivityStatus implements androidx.wear.ongoing.TimeDependentText androidx.versionedparcelable.VersionedParcelable {
-    method public static androidx.wear.ongoing.OngoingActivityStatus forPart(androidx.wear.ongoing.StatusPart);
+  public final class Status implements androidx.wear.ongoing.TimeDependentText {
+    method public static androidx.wear.ongoing.Status forPart(androidx.wear.ongoing.Status.Part);
     method public long getNextChangeTimeMillis(long);
-    method public androidx.wear.ongoing.StatusPart? getPart(String);
+    method public androidx.wear.ongoing.Status.Part? getPart(String);
     method public java.util.Set<java.lang.String!> getPartNames();
     method public java.util.List<java.lang.CharSequence!> getTemplates();
     method public CharSequence getText(android.content.Context, long);
   }
 
-  public static final class OngoingActivityStatus.Builder {
-    ctor public OngoingActivityStatus.Builder();
-    method public androidx.wear.ongoing.OngoingActivityStatus.Builder addPart(String, androidx.wear.ongoing.StatusPart);
-    method public androidx.wear.ongoing.OngoingActivityStatus.Builder addTemplate(CharSequence);
-    method public androidx.wear.ongoing.OngoingActivityStatus build();
+  public static final class Status.Builder {
+    ctor public Status.Builder();
+    method public androidx.wear.ongoing.Status.Builder addPart(String, androidx.wear.ongoing.Status.Part);
+    method public androidx.wear.ongoing.Status.Builder addTemplate(CharSequence);
+    method public androidx.wear.ongoing.Status build();
   }
 
-  public abstract class StatusPart implements androidx.wear.ongoing.TimeDependentText androidx.versionedparcelable.VersionedParcelable {
-    ctor public StatusPart();
+  public abstract static class Status.Part implements androidx.wear.ongoing.TimeDependentText {
   }
 
-  public class TextStatusPart extends androidx.wear.ongoing.StatusPart {
-    ctor public TextStatusPart(String);
+  public static final class Status.StopwatchPart extends androidx.wear.ongoing.Status.TimerOrStopwatchPart {
+    ctor public Status.StopwatchPart(long, long, long);
+    ctor public Status.StopwatchPart(long, long);
+    ctor public Status.StopwatchPart(long);
+  }
+
+  public static final class Status.TextPart extends androidx.wear.ongoing.Status.Part {
+    ctor public Status.TextPart(String);
     method public long getNextChangeTimeMillis(long);
     method public CharSequence getText(android.content.Context, long);
   }
 
-  public interface TimeDependentText {
-    method public long getNextChangeTimeMillis(long);
-    method public CharSequence getText(android.content.Context, long);
-  }
-
-  public class TimerStatusPart extends androidx.wear.ongoing.StatusPart {
-    ctor public TimerStatusPart(long, boolean, long, long);
-    ctor public TimerStatusPart(long, boolean, long);
-    ctor public TimerStatusPart(long, boolean);
-    ctor public TimerStatusPart(long);
+  public abstract static class Status.TimerOrStopwatchPart extends androidx.wear.ongoing.Status.Part {
     method public long getNextChangeTimeMillis(long);
     method public long getPausedAtMillis();
     method public CharSequence getText(android.content.Context, long);
@@ -95,5 +83,16 @@
     method public boolean isPaused();
   }
 
+  public static final class Status.TimerPart extends androidx.wear.ongoing.Status.TimerOrStopwatchPart {
+    ctor public Status.TimerPart(long, long, long);
+    ctor public Status.TimerPart(long, long);
+    ctor public Status.TimerPart(long);
+  }
+
+  public interface TimeDependentText {
+    method public long getNextChangeTimeMillis(long);
+    method public CharSequence getText(android.content.Context, long);
+  }
+
 }
 
diff --git a/wear/wear-ongoing/api/public_plus_experimental_current.txt b/wear/wear-ongoing/api/public_plus_experimental_current.txt
index 3c959c3..edd5ae4 100644
--- a/wear/wear-ongoing/api/public_plus_experimental_current.txt
+++ b/wear/wear-ongoing/api/public_plus_experimental_current.txt
@@ -9,14 +9,14 @@
     method public int getNotificationId();
     method public int getOngoingActivityId();
     method public android.graphics.drawable.Icon getStaticIcon();
-    method public androidx.wear.ongoing.OngoingActivityStatus? getStatus();
+    method public androidx.wear.ongoing.Status? getStatus();
     method public String? getTag();
     method public long getTimestamp();
     method public android.app.PendingIntent getTouchIntent();
-    method public static androidx.wear.ongoing.OngoingActivity? recoverOngoingActivity(android.content.Context, java.util.function.Predicate<androidx.wear.ongoing.OngoingActivityData!>);
+    method public static androidx.wear.ongoing.OngoingActivity? recoverOngoingActivity(android.content.Context, java.util.function.Predicate<androidx.wear.ongoing.OngoingActivity!>);
     method public static androidx.wear.ongoing.OngoingActivity? recoverOngoingActivity(android.content.Context);
     method public static androidx.wear.ongoing.OngoingActivity? recoverOngoingActivity(android.content.Context, int);
-    method public void update(android.content.Context, androidx.wear.ongoing.OngoingActivityStatus);
+    method public void update(android.content.Context, androidx.wear.ongoing.Status);
   }
 
   public static final class OngoingActivity.Builder {
@@ -30,61 +30,49 @@
     method public androidx.wear.ongoing.OngoingActivity.Builder setOngoingActivityId(int);
     method public androidx.wear.ongoing.OngoingActivity.Builder setStaticIcon(android.graphics.drawable.Icon);
     method public androidx.wear.ongoing.OngoingActivity.Builder setStaticIcon(@DrawableRes int);
-    method public androidx.wear.ongoing.OngoingActivity.Builder setStatus(androidx.wear.ongoing.OngoingActivityStatus);
+    method public androidx.wear.ongoing.OngoingActivity.Builder setStatus(androidx.wear.ongoing.Status);
     method public androidx.wear.ongoing.OngoingActivity.Builder setTouchIntent(android.app.PendingIntent);
   }
 
-  @androidx.versionedparcelable.VersionedParcelize public class OngoingActivityData implements androidx.versionedparcelable.VersionedParcelable {
+  public class SerializationHelper {
     method public static void copy(android.os.Bundle, android.os.Bundle);
-    method public static androidx.wear.ongoing.OngoingActivityData? create(android.app.Notification);
-    method public static androidx.wear.ongoing.OngoingActivityData? create(android.os.Bundle);
-    method public android.graphics.drawable.Icon? getAnimatedIcon();
-    method public String? getCategory();
-    method public androidx.core.content.LocusIdCompat? getLocusId();
-    method public int getOngoingActivityId();
-    method public android.graphics.drawable.Icon getStaticIcon();
-    method public androidx.wear.ongoing.OngoingActivityStatus? getStatus();
-    method public long getTimestamp();
-    method public android.app.PendingIntent getTouchIntent();
+    method public static androidx.wear.ongoing.OngoingActivity? create(android.app.Notification);
+    method public static androidx.wear.ongoing.OngoingActivity? create(android.os.Bundle);
     method public static boolean hasOngoingActivity(android.app.Notification);
   }
 
-  @androidx.versionedparcelable.VersionedParcelize(isCustom=true) public class OngoingActivityStatus extends androidx.versionedparcelable.CustomVersionedParcelable implements androidx.wear.ongoing.TimeDependentText {
-    method public static androidx.wear.ongoing.OngoingActivityStatus forPart(androidx.wear.ongoing.StatusPart);
+  public final class Status implements androidx.wear.ongoing.TimeDependentText {
+    method public static androidx.wear.ongoing.Status forPart(androidx.wear.ongoing.Status.Part);
     method public long getNextChangeTimeMillis(long);
-    method public androidx.wear.ongoing.StatusPart? getPart(String);
+    method public androidx.wear.ongoing.Status.Part? getPart(String);
     method public java.util.Set<java.lang.String!> getPartNames();
     method public java.util.List<java.lang.CharSequence!> getTemplates();
     method public CharSequence getText(android.content.Context, long);
   }
 
-  public static final class OngoingActivityStatus.Builder {
-    ctor public OngoingActivityStatus.Builder();
-    method public androidx.wear.ongoing.OngoingActivityStatus.Builder addPart(String, androidx.wear.ongoing.StatusPart);
-    method public androidx.wear.ongoing.OngoingActivityStatus.Builder addTemplate(CharSequence);
-    method public androidx.wear.ongoing.OngoingActivityStatus build();
+  public static final class Status.Builder {
+    ctor public Status.Builder();
+    method public androidx.wear.ongoing.Status.Builder addPart(String, androidx.wear.ongoing.Status.Part);
+    method public androidx.wear.ongoing.Status.Builder addTemplate(CharSequence);
+    method public androidx.wear.ongoing.Status build();
   }
 
-  public abstract class StatusPart implements androidx.wear.ongoing.TimeDependentText androidx.versionedparcelable.VersionedParcelable {
-    ctor public StatusPart();
+  public abstract static class Status.Part implements androidx.wear.ongoing.TimeDependentText {
   }
 
-  @androidx.versionedparcelable.VersionedParcelize public class TextStatusPart extends androidx.wear.ongoing.StatusPart {
-    ctor public TextStatusPart(String);
+  public static final class Status.StopwatchPart extends androidx.wear.ongoing.Status.TimerOrStopwatchPart {
+    ctor public Status.StopwatchPart(long, long, long);
+    ctor public Status.StopwatchPart(long, long);
+    ctor public Status.StopwatchPart(long);
+  }
+
+  public static final class Status.TextPart extends androidx.wear.ongoing.Status.Part {
+    ctor public Status.TextPart(String);
     method public long getNextChangeTimeMillis(long);
     method public CharSequence getText(android.content.Context, long);
   }
 
-  public interface TimeDependentText {
-    method public long getNextChangeTimeMillis(long);
-    method public CharSequence getText(android.content.Context, long);
-  }
-
-  @androidx.versionedparcelable.VersionedParcelize public class TimerStatusPart extends androidx.wear.ongoing.StatusPart {
-    ctor public TimerStatusPart(long, boolean, long, long);
-    ctor public TimerStatusPart(long, boolean, long);
-    ctor public TimerStatusPart(long, boolean);
-    ctor public TimerStatusPart(long);
+  public abstract static class Status.TimerOrStopwatchPart extends androidx.wear.ongoing.Status.Part {
     method public long getNextChangeTimeMillis(long);
     method public long getPausedAtMillis();
     method public CharSequence getText(android.content.Context, long);
@@ -95,5 +83,16 @@
     method public boolean isPaused();
   }
 
+  public static final class Status.TimerPart extends androidx.wear.ongoing.Status.TimerOrStopwatchPart {
+    ctor public Status.TimerPart(long, long, long);
+    ctor public Status.TimerPart(long, long);
+    ctor public Status.TimerPart(long);
+  }
+
+  public interface TimeDependentText {
+    method public long getNextChangeTimeMillis(long);
+    method public CharSequence getText(android.content.Context, long);
+  }
+
 }
 
diff --git a/wear/wear-ongoing/api/restricted_current.txt b/wear/wear-ongoing/api/restricted_current.txt
index 3c959c3..edd5ae4 100644
--- a/wear/wear-ongoing/api/restricted_current.txt
+++ b/wear/wear-ongoing/api/restricted_current.txt
@@ -9,14 +9,14 @@
     method public int getNotificationId();
     method public int getOngoingActivityId();
     method public android.graphics.drawable.Icon getStaticIcon();
-    method public androidx.wear.ongoing.OngoingActivityStatus? getStatus();
+    method public androidx.wear.ongoing.Status? getStatus();
     method public String? getTag();
     method public long getTimestamp();
     method public android.app.PendingIntent getTouchIntent();
-    method public static androidx.wear.ongoing.OngoingActivity? recoverOngoingActivity(android.content.Context, java.util.function.Predicate<androidx.wear.ongoing.OngoingActivityData!>);
+    method public static androidx.wear.ongoing.OngoingActivity? recoverOngoingActivity(android.content.Context, java.util.function.Predicate<androidx.wear.ongoing.OngoingActivity!>);
     method public static androidx.wear.ongoing.OngoingActivity? recoverOngoingActivity(android.content.Context);
     method public static androidx.wear.ongoing.OngoingActivity? recoverOngoingActivity(android.content.Context, int);
-    method public void update(android.content.Context, androidx.wear.ongoing.OngoingActivityStatus);
+    method public void update(android.content.Context, androidx.wear.ongoing.Status);
   }
 
   public static final class OngoingActivity.Builder {
@@ -30,61 +30,49 @@
     method public androidx.wear.ongoing.OngoingActivity.Builder setOngoingActivityId(int);
     method public androidx.wear.ongoing.OngoingActivity.Builder setStaticIcon(android.graphics.drawable.Icon);
     method public androidx.wear.ongoing.OngoingActivity.Builder setStaticIcon(@DrawableRes int);
-    method public androidx.wear.ongoing.OngoingActivity.Builder setStatus(androidx.wear.ongoing.OngoingActivityStatus);
+    method public androidx.wear.ongoing.OngoingActivity.Builder setStatus(androidx.wear.ongoing.Status);
     method public androidx.wear.ongoing.OngoingActivity.Builder setTouchIntent(android.app.PendingIntent);
   }
 
-  @androidx.versionedparcelable.VersionedParcelize public class OngoingActivityData implements androidx.versionedparcelable.VersionedParcelable {
+  public class SerializationHelper {
     method public static void copy(android.os.Bundle, android.os.Bundle);
-    method public static androidx.wear.ongoing.OngoingActivityData? create(android.app.Notification);
-    method public static androidx.wear.ongoing.OngoingActivityData? create(android.os.Bundle);
-    method public android.graphics.drawable.Icon? getAnimatedIcon();
-    method public String? getCategory();
-    method public androidx.core.content.LocusIdCompat? getLocusId();
-    method public int getOngoingActivityId();
-    method public android.graphics.drawable.Icon getStaticIcon();
-    method public androidx.wear.ongoing.OngoingActivityStatus? getStatus();
-    method public long getTimestamp();
-    method public android.app.PendingIntent getTouchIntent();
+    method public static androidx.wear.ongoing.OngoingActivity? create(android.app.Notification);
+    method public static androidx.wear.ongoing.OngoingActivity? create(android.os.Bundle);
     method public static boolean hasOngoingActivity(android.app.Notification);
   }
 
-  @androidx.versionedparcelable.VersionedParcelize(isCustom=true) public class OngoingActivityStatus extends androidx.versionedparcelable.CustomVersionedParcelable implements androidx.wear.ongoing.TimeDependentText {
-    method public static androidx.wear.ongoing.OngoingActivityStatus forPart(androidx.wear.ongoing.StatusPart);
+  public final class Status implements androidx.wear.ongoing.TimeDependentText {
+    method public static androidx.wear.ongoing.Status forPart(androidx.wear.ongoing.Status.Part);
     method public long getNextChangeTimeMillis(long);
-    method public androidx.wear.ongoing.StatusPart? getPart(String);
+    method public androidx.wear.ongoing.Status.Part? getPart(String);
     method public java.util.Set<java.lang.String!> getPartNames();
     method public java.util.List<java.lang.CharSequence!> getTemplates();
     method public CharSequence getText(android.content.Context, long);
   }
 
-  public static final class OngoingActivityStatus.Builder {
-    ctor public OngoingActivityStatus.Builder();
-    method public androidx.wear.ongoing.OngoingActivityStatus.Builder addPart(String, androidx.wear.ongoing.StatusPart);
-    method public androidx.wear.ongoing.OngoingActivityStatus.Builder addTemplate(CharSequence);
-    method public androidx.wear.ongoing.OngoingActivityStatus build();
+  public static final class Status.Builder {
+    ctor public Status.Builder();
+    method public androidx.wear.ongoing.Status.Builder addPart(String, androidx.wear.ongoing.Status.Part);
+    method public androidx.wear.ongoing.Status.Builder addTemplate(CharSequence);
+    method public androidx.wear.ongoing.Status build();
   }
 
-  public abstract class StatusPart implements androidx.wear.ongoing.TimeDependentText androidx.versionedparcelable.VersionedParcelable {
-    ctor public StatusPart();
+  public abstract static class Status.Part implements androidx.wear.ongoing.TimeDependentText {
   }
 
-  @androidx.versionedparcelable.VersionedParcelize public class TextStatusPart extends androidx.wear.ongoing.StatusPart {
-    ctor public TextStatusPart(String);
+  public static final class Status.StopwatchPart extends androidx.wear.ongoing.Status.TimerOrStopwatchPart {
+    ctor public Status.StopwatchPart(long, long, long);
+    ctor public Status.StopwatchPart(long, long);
+    ctor public Status.StopwatchPart(long);
+  }
+
+  public static final class Status.TextPart extends androidx.wear.ongoing.Status.Part {
+    ctor public Status.TextPart(String);
     method public long getNextChangeTimeMillis(long);
     method public CharSequence getText(android.content.Context, long);
   }
 
-  public interface TimeDependentText {
-    method public long getNextChangeTimeMillis(long);
-    method public CharSequence getText(android.content.Context, long);
-  }
-
-  @androidx.versionedparcelable.VersionedParcelize public class TimerStatusPart extends androidx.wear.ongoing.StatusPart {
-    ctor public TimerStatusPart(long, boolean, long, long);
-    ctor public TimerStatusPart(long, boolean, long);
-    ctor public TimerStatusPart(long, boolean);
-    ctor public TimerStatusPart(long);
+  public abstract static class Status.TimerOrStopwatchPart extends androidx.wear.ongoing.Status.Part {
     method public long getNextChangeTimeMillis(long);
     method public long getPausedAtMillis();
     method public CharSequence getText(android.content.Context, long);
@@ -95,5 +83,16 @@
     method public boolean isPaused();
   }
 
+  public static final class Status.TimerPart extends androidx.wear.ongoing.Status.TimerOrStopwatchPart {
+    ctor public Status.TimerPart(long, long, long);
+    ctor public Status.TimerPart(long, long);
+    ctor public Status.TimerPart(long);
+  }
+
+  public interface TimeDependentText {
+    method public long getNextChangeTimeMillis(long);
+    method public CharSequence getText(android.content.Context, long);
+  }
+
 }
 
diff --git a/wear/wear-ongoing/lint-baseline.xml b/wear/wear-ongoing/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/wear/wear-ongoing/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/wear/wear-ongoing/src/main/java/androidx/wear/ongoing/OngoingActivity.java b/wear/wear-ongoing/src/main/java/androidx/wear/ongoing/OngoingActivity.java
index 6c53230..db86223 100644
--- a/wear/wear-ongoing/src/main/java/androidx/wear/ongoing/OngoingActivity.java
+++ b/wear/wear-ongoing/src/main/java/androidx/wear/ongoing/OngoingActivity.java
@@ -30,6 +30,7 @@
 import androidx.annotation.RequiresApi;
 import androidx.core.app.NotificationCompat;
 import androidx.core.content.LocusIdCompat;
+import androidx.core.util.Preconditions;
 
 import java.util.function.Predicate;
 
@@ -55,7 +56,7 @@
  * them in both the {@link Builder#Builder(Context, String, int, NotificationCompat.Builder)} and
  * {@link NotificationManager#notify(String, int, Notification)}
  * <p>
- * Afterward, {@link OngoingActivity#update(Context, OngoingActivityStatus) update} can be used to
+ * Afterward, {@link OngoingActivity#update(Context, Status) update} can be used to
  * update the status.
  * <p>
  * If saving the {@link OngoingActivity} instance is not convenient, it can be recovered (after the
@@ -66,6 +67,7 @@
     @Nullable
     private final String mTag;
     private final int mNotificationId;
+    @Nullable
     private final NotificationCompat.Builder mNotificationBuilder;
     private final OngoingActivityData mData;
 
@@ -79,6 +81,14 @@
         this.mData = data;
     }
 
+    // Used when reconstructing an OngoingActivity form a bundle.
+    OngoingActivity(@NonNull OngoingActivityData data) {
+        this.mTag = null;
+        this.mNotificationId = 0;
+        this.mNotificationBuilder = null;
+        this.mData = data;
+    }
+
     /**
      * Builder used to build an {@link OngoingActivity}
      */
@@ -91,12 +101,14 @@
         // Ongoing Activity Data
         private Icon mAnimatedIcon;
         private Icon mStaticIcon;
-        private OngoingActivityStatus mStatus;
+        private Status mStatus;
         private PendingIntent mTouchIntent;
         private LocusIdCompat mLocusId;
-        private int mOngoingActivityId = OngoingActivityData.DEFAULT_ID;
+        private int mOngoingActivityId = DEFAULT_ID;
         private String mCategory;
 
+        static final int DEFAULT_ID = -1;
+
         /**
          * Construct a new empty {@link Builder}, associated with the given notification.
          *
@@ -181,7 +193,7 @@
          * show progress of the Ongoing Activity.
          */
         @NonNull
-        public Builder setStatus(@NonNull OngoingActivityStatus status) {
+        public Builder setStatus(@NonNull Status status) {
             mStatus = status;
             return this;
         }
@@ -248,11 +260,12 @@
                 throw new IllegalArgumentException("Touch intent should be specified.");
             }
 
-            OngoingActivityStatus status = mStatus;
+            OngoingActivityStatus status = mStatus == null ? null : mStatus.toVersionedParcelable();
             if (status == null) {
                 String text = notification.extras.getString(Notification.EXTRA_TEXT);
                 if (text != null) {
-                    status = OngoingActivityStatus.forPart(new TextStatusPart(text));
+                    status = Status.forPart(new Status.TextPart(text))
+                        .toVersionedParcelable();
                 }
             }
 
@@ -318,8 +331,9 @@
      * corresponding Notification.
      */
     @Nullable
-    public OngoingActivityStatus getStatus() {
-        return mData.getStatus();
+    public Status getStatus() {
+        return mData.getStatus() == null ? null :
+                Status.fromVersionedParcelable(mData.getStatus());
     }
 
     /**
@@ -376,7 +390,8 @@
      *                this call returns.
      */
     public void apply(@NonNull @SuppressWarnings("unused") Context context) {
-        mData.extend(mNotificationBuilder);
+        Preconditions.checkNotNull(mNotificationBuilder);
+        SerializationHelper.extend(mNotificationBuilder, mData);
     }
 
     /**
@@ -388,9 +403,10 @@
      *                this call returns.
      * @param status  The new status of this Ongoing Activity.
      */
-    public void update(@NonNull Context context, @NonNull OngoingActivityStatus status) {
-        mData.setStatus(status);
-        Notification notification = mData.extendAndBuild(mNotificationBuilder);
+    public void update(@NonNull Context context, @NonNull Status status) {
+        Preconditions.checkNotNull(mNotificationBuilder);
+        mData.setStatus(status.toVersionedParcelable());
+        Notification notification = SerializationHelper.extendAndBuild(mNotificationBuilder, mData);
 
         NotificationManager manager = context.getSystemService(NotificationManager.class);
         if (mTag == null) {
@@ -412,20 +428,23 @@
     @Nullable
     public static OngoingActivity recoverOngoingActivity(
             @NonNull Context context,
-            @NonNull Predicate<OngoingActivityData> filter
+            @NonNull Predicate<OngoingActivity> filter
     ) {
         StatusBarNotification[] notifications =
                 context.getSystemService(NotificationManager.class).getActiveNotifications();
         for (StatusBarNotification statusBarNotification : notifications) {
             OngoingActivityData data =
-                    OngoingActivityData.create(statusBarNotification.getNotification());
-            if (data != null && filter.test(data)) {
-                return new OngoingActivity(
+                    SerializationHelper.createInternal(statusBarNotification.getNotification());
+            if (data != null) {
+                OngoingActivity oa = new OngoingActivity(
                         statusBarNotification.getTag(),
                         statusBarNotification.getId(),
                         new NotificationCompat.Builder(context,
                                 statusBarNotification.getNotification()),
                         data);
+                if (filter.test(oa)) {
+                    return oa;
+                }
             }
         }
         return null;
diff --git a/wear/wear-ongoing/src/main/java/androidx/wear/ongoing/OngoingActivityData.java b/wear/wear-ongoing/src/main/java/androidx/wear/ongoing/OngoingActivityData.java
index 96831bb..9e06859 100644
--- a/wear/wear-ongoing/src/main/java/androidx/wear/ongoing/OngoingActivityData.java
+++ b/wear/wear-ongoing/src/main/java/androidx/wear/ongoing/OngoingActivityData.java
@@ -15,26 +15,26 @@
  */
 package androidx.wear.ongoing;
 
-import android.app.Notification;
 import android.app.PendingIntent;
 import android.graphics.drawable.Icon;
-import android.os.Bundle;
 import android.os.SystemClock;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
-import androidx.core.app.NotificationCompat;
+import androidx.annotation.RestrictTo;
 import androidx.core.content.LocusIdCompat;
 import androidx.versionedparcelable.ParcelField;
-import androidx.versionedparcelable.ParcelUtils;
 import androidx.versionedparcelable.VersionedParcelable;
 import androidx.versionedparcelable.VersionedParcelize;
 
 /**
- * This class is used internally by the library to represent the data of an OngoingActivity.
+ * This class is used internally by the library to represent the data of an OngoingActivity and
+ * serialize/deserialize using VersionedParcelable.
+ * @hide
  */
+@RestrictTo(RestrictTo.Scope.LIBRARY)
 @VersionedParcelize
-public class OngoingActivityData implements VersionedParcelable {
+class OngoingActivityData implements VersionedParcelable {
     @Nullable
     @ParcelField(value = 1, defaultValue = "null")
     Icon mAnimatedIcon;
@@ -89,67 +89,6 @@
         mTimestamp = timestamp;
     }
 
-    @NonNull
-    NotificationCompat.Builder extend(@NonNull NotificationCompat.Builder builder) {
-        ParcelUtils.putVersionedParcelable(builder.getExtras(), EXTRA_ONGOING_ACTIVITY,
-                this);
-        return builder;
-    }
-
-    @NonNull Notification extendAndBuild(@NonNull NotificationCompat.Builder builder) {
-        Notification notification = extend(builder).build();
-        // TODO(http://b/169394642): Undo this if/when the bug is fixed.
-        notification.extras.putBundle(
-                EXTRA_ONGOING_ACTIVITY,
-                builder.getExtras().getBundle(EXTRA_ONGOING_ACTIVITY)
-        );
-        return notification;
-    }
-
-    /**
-     * Checks if the given notification represents an ongoing activity.
-     */
-    public static boolean hasOngoingActivity(@NonNull Notification notification) {
-        return notification.extras.getBundle(EXTRA_ONGOING_ACTIVITY) != null;
-    }
-
-    /**
-     * Deserializes the {@link OngoingActivityData} from a notification.
-     *
-     * @param notification the notification that may contain information about a Ongoing
-     *                     Activity.
-     * @return the data, or null of the notification doesn't contain Ongoing Activity data.
-     */
-    @Nullable
-    public static OngoingActivityData create(@NonNull Notification notification) {
-        return create(notification.extras);
-    }
-
-    /**
-     * Deserializes the {@link OngoingActivityData} from a Bundle.
-     *
-     * @param bundle the bundle that may contain information about a Ongoing Activity.
-     * @return the data, or null of the Bundle doesn't contain Ongoing Activity data.
-     */
-    @Nullable
-    public static OngoingActivityData create(@NonNull Bundle bundle) {
-        return ParcelUtils.getVersionedParcelable(bundle, EXTRA_ONGOING_ACTIVITY);
-    }
-
-
-    /**
-     * Copies an Ongoing Activity information from a bundle to another, without deserializing
-     * and serializing (Note that Bundle instance is shared, not copied and deserializing the
-     * Ongoing activity information somewhere else negates the advantages of using this)
-     *
-     * @param sourceBundle The bundle to get the Ongoing Activity data from
-     * @param destinationBundle The bundle to put the Ongoing Activity data into.
-     */
-    public static void copy(@NonNull Bundle sourceBundle, @NonNull Bundle destinationBundle) {
-        destinationBundle.putBundle(EXTRA_ONGOING_ACTIVITY,
-                sourceBundle.getBundle(EXTRA_ONGOING_ACTIVITY));
-    }
-
     /**
      * Get the animated icon that can be used on some surfaces to represent this
      * {@link OngoingActivity}. For example, in the WatchFace.
@@ -227,11 +166,5 @@
     void setStatus(@NonNull OngoingActivityStatus status) {
         mStatus = status;
     }
-
-    /** Notification action extra which contains ongoing activity extensions */
-    private static final String EXTRA_ONGOING_ACTIVITY =
-            "android.wearable.ongoingactivities.EXTENSIONS";
-
-    static final int DEFAULT_ID = -1;
 }
 
diff --git a/wear/wear-ongoing/src/main/java/androidx/wear/ongoing/OngoingActivityStatus.java b/wear/wear-ongoing/src/main/java/androidx/wear/ongoing/OngoingActivityStatus.java
index dd07bd5..ffd3e7f 100644
--- a/wear/wear-ongoing/src/main/java/androidx/wear/ongoing/OngoingActivityStatus.java
+++ b/wear/wear-ongoing/src/main/java/androidx/wear/ongoing/OngoingActivityStatus.java
@@ -15,14 +15,11 @@
  */
 package androidx.wear.ongoing;
 
-import android.content.Context;
 import android.os.Bundle;
-import android.text.SpannableStringBuilder;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.annotation.RestrictTo;
-import androidx.annotation.VisibleForTesting;
 import androidx.versionedparcelable.CustomVersionedParcelable;
 import androidx.versionedparcelable.NonParcelField;
 import androidx.versionedparcelable.ParcelField;
@@ -30,55 +27,35 @@
 import androidx.versionedparcelable.VersionedParcelize;
 
 import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
-import java.util.Set;
 
 /**
- * Base class to serialize / deserialize {@link OngoingActivityStatus} into / from a Notification.
- * <p>
- * A status is composed of Parts, and they are joined together with a template.
- * <p>
- * Note that for backwards compatibility reasons the code rendering this status message may not
- * have all of the [StatusPart] classes that are available in later versions of the library.
- * Templates that do not have values for all of the named parts will not be used.
- * The template list will be iterated through looking for the first template with all matching named
- * parts available, this will be selected for rendering the status.
- * <p>
- * To provide for backwards compatibility, you should provide one (or more) fallback templates which
- * use status parts from earlier versions of the API. e.g. TextStatusPart & TimerStatusPart
- * <p>
- * The status and part classes here use timestamps for updating the displayed representation of the
- * status, in cases when this is needed (chronometers), as returned by
- * {@link android.os.SystemClock#elapsedRealtime()}
+ * Class used internally by the library to represent the status of and ongoing activity, and to
+ * serialize / deserialize.
+ * @hide
  */
+@RestrictTo(RestrictTo.Scope.LIBRARY)
 @VersionedParcelize(isCustom = true)
-public class OngoingActivityStatus extends CustomVersionedParcelable
-        implements TimeDependentText {
+class OngoingActivityStatus extends CustomVersionedParcelable {
     @NonNull
     @ParcelField(value = 1)
     List<CharSequence> mTemplates = new ArrayList<>();
 
     @NonParcelField
     @NonNull
-    private Map<String, StatusPart> mParts = new HashMap<>();
+    Map<String, StatusPart> mParts = new HashMap<>();
 
     // Used to serialize/deserialize mParts to avoid http://b/132619460
     @ParcelField(value = 2)
     Bundle mPartsAsBundle;
 
-    // Name of the {@link StatusPart} created when using {@link OngoingActivityStatus.forPart()}
-    private static final String DEFAULT_STATUS_PART_NAME = "defaultStatusPartName";
-
     // Needed By VersionedParcelables.
     OngoingActivityStatus() {
     }
 
-    // Basic constructor used by the Builder
-    @VisibleForTesting
+    // Basic constructor used by OngoingActivityStatusApi
     OngoingActivityStatus(
             @Nullable List<CharSequence> templates,
             @NonNull Map<String, StatusPart> parts
@@ -87,225 +64,13 @@
         mParts = parts;
     }
 
-    /**
-     * Convenience method for creating a Status with no template and a single Part.
-     *
-     * @param part The only Part that composes this status.
-     * @return A new {@link OngoingActivityStatus} with just one Part.
-     */
-    @NonNull
-    public static OngoingActivityStatus forPart(@NonNull StatusPart part) {
-        // Create an OngoingActivityStatus using only this part and the default template.
-        return new OngoingActivityStatus.Builder().addPart(DEFAULT_STATUS_PART_NAME, part).build();
-    }
-
-    /**
-     * Helper to Build OngoingActivityStatus instances.
-     *
-     * Templates can be specified, to specify how to render the parts and any surrounding
-     * text/format.
-     * If no template is specified, a default template that concatenates all parts separated
-     * by space is used.
-     */
-    public static final class Builder {
-        private List<CharSequence> mTemplates = new ArrayList<>();
-        private CharSequence mDefaultTemplate = "";
-        private Map<String, StatusPart> mParts = new HashMap<>();
-
-        public Builder() {
-        }
-
-        /**
-         * Add a template to use for this status. Placeholders can be defined with #name#
-         * To produce a '#', use '##' in the template.
-         * If multiple templates are specified, the first one (in the order they where added by
-         * calling this method) that has all required fields is used.
-         * If no template is specified, a default template that concatenates all parts separated
-         * by space is used.
-         *
-         * @param template the template to be added
-         * @return this builder, to chain calls.
-         */
-        @NonNull
-        public Builder addTemplate(@NonNull CharSequence template) {
-            mTemplates.add(template);
-            return this;
-        }
-
-        /**
-         * Add a part to be inserted in the placeholders.
-         *
-         * @param name the name of this part. In the template, use this name surrounded by '#'
-         *             to reference it, e.g. here "track" and in the template "#track#"
-         * @param part The part that will be rendered in the specified position/s in the template.
-         * @return this builder, to chain calls.
-         */
-        @NonNull
-        @SuppressWarnings("MissingGetterMatchingBuilder")
-        // We don't want a getter getParts()
-        public Builder addPart(@NonNull String name, @NonNull StatusPart part) {
-            mParts.put(name, part);
-            mDefaultTemplate += (mDefaultTemplate.length() > 0 ? " " : "") + "#" + name + "#";
-            return this;
-        }
-
-        /**
-         * Build an OngoingActivityStatus with the given parameters.
-         * @return the built OngoingActivityStatus
-         */
-        @NonNull
-        public OngoingActivityStatus build() {
-            List<CharSequence> templates = mTemplates.isEmpty() ? Arrays.asList(mDefaultTemplate)
-                    : mTemplates;
-
-            // Verify that the last template can be rendered by every SysUI.
-            // Verify that all templates have all required parts.
-            Map<String, CharSequence> base = new HashMap<>();
-            Map<String, CharSequence> all = new HashMap<>();
-            for (Map.Entry<String, StatusPart> me : mParts.entrySet()) {
-                if (me.getValue() instanceof TextStatusPart
-                        || me.getValue() instanceof TimerStatusPart) {
-                    base.put(me.getKey(), "");
-                }
-                all.put(me.getKey(), "");
-            }
-            if (processTemplate(templates.get(templates.size() - 1), base) == null) {
-                throw new IllegalStateException("For backwards compatibility reasons the last "
-                        + "templateThe should only use TextStatusPart & TimerStatusPart");
-            }
-            for (CharSequence template: templates) {
-                if (processTemplate(template, all) == null) {
-                    throw new IllegalStateException("The template \"" + template + "\" is missing"
-                            + " some parts for rendering.");
-                }
-            }
-
-            return new OngoingActivityStatus(templates, mParts);
-        }
-    }
-
-    /**
-     * @return the list of templates that this status has.
-     */
-    @NonNull
-    public List<CharSequence> getTemplates() {
-        return mTemplates;
-    }
-
-    /**
-     * @return the names of the parts provide to this status.
-     */
-    @NonNull
-    public Set<String> getPartNames() {
-        return Collections.unmodifiableSet(mParts.keySet());
-    }
-
-    /**
-     * Returns the value of the part with the given name.
-     * @param name the name to lookup.
-     * @return the part with the given name, can be null.
-     */
-    @Nullable
-    public StatusPart getPart(@NonNull String name) {
-        return mParts.get(name);
-    }
-
-    /**
-     * Process a template and replace placeholders with the provided values.
-     * Placeholders are named, delimited by '#'. For example: '#name#'
-     * To produce a '#' in the output, use '##' in the template.
-     *
-     * @param template The template to use as base.
-     * @param values The values to replace the placeholders in the template with.
-     * @return The template with the placeholders replaced, or null if the template references a
-     * value that it's not present (or null).
-     */
-    @Nullable
-    static CharSequence processTemplate(@NonNull CharSequence template,
-            @NonNull Map<String, CharSequence> values) {
-        SpannableStringBuilder ssb = new SpannableStringBuilder(template);
-
-        int opening = -1;
-        for (int i = 0; i < ssb.length(); i++) {
-            if (ssb.charAt(i) == '#') {
-                if (opening >= 0) {
-                    // Replace '##' with '#'
-                    // Replace '#varName#' with the value from the map.
-                    CharSequence replaceWith =
-                            opening == i - 1 ? "#" :
-                            values.get(ssb.subSequence(opening + 1, i).toString());
-                    if (replaceWith == null) {
-                        return null;
-                    }
-                    ssb.replace(opening, i + 1, replaceWith);
-                    i = opening + replaceWith.length() - 1;
-                    opening = -1;
-                } else {
-                    opening = i;
-                }
-            }
-        }
-
-        return ssb;
-    }
-
-    /**
-     * Returns a textual representation of this status at the given time. The first template that
-     * has all required information will be used, and each part will be used in their respective
-     * placeholder/s.
-     *
-     * @param context       may be used for internationalization. Only used while this method
-     *                      executed.
-     * @param timeNowMillis the timestamp of the time we want to display, usually now, as
-     * @return the rendered text, for best compatibility, display using a TextView.
-     */
-    @NonNull
-    @Override
-    public CharSequence getText(@NonNull Context context, long timeNowMillis) {
-        Map<String, CharSequence> texts = new HashMap<>();
-        for (Map.Entry<String, StatusPart> me : mParts.entrySet()) {
-            CharSequence text = me.getValue().getText(context, timeNowMillis);
-            texts.put(me.getKey(), text);
-        }
-
-        for (CharSequence template : mTemplates) {
-            CharSequence ret = processTemplate(template, texts);
-            if (ret != null) {
-                return ret;
-            }
-        }
-
-        return "";
-    }
-
-    /**
-     * Returns the next time this status could have a different rendering.
-     * There is no guarantee that the rendering will change at the returned time (for example, if
-     * some information in the status is not rendered).
-     *
-     * @param fromTimeMillis current time, usually now as returned by
-     *                       {@link android.os.SystemClock#elapsedRealtime()}. In most cases
-     *                       {@code getText} and {@code getNextChangeTimeMillis} should be called
-     *                       with the exact same timestamp, so changes are not missed.
-     * @return the next time (counting from fromTimeMillis) that this status may produce a
-     * different result when calling getText().
-     */
-    @Override
-    public long getNextChangeTimeMillis(long fromTimeMillis) {
-        long ret = Long.MAX_VALUE;
-        for (StatusPart part : mParts.values()) {
-            ret = Math.min(ret, part.getNextChangeTimeMillis(fromTimeMillis));
-        }
-        return ret;
-    }
-
     // Implementation of CustomVersionedParcelable
     /**
-     * See {@link androidx.versionedparcelable.CustomVersionedParcelable.onPreParceling()}
+     * See {@link androidx.versionedparcelable.CustomVersionedParcelable#onPreParceling(boolean)}
      * @hide
      */
     @Override
-    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
+    @RestrictTo(RestrictTo.Scope.LIBRARY)
     public void onPreParceling(boolean isStream) {
         mPartsAsBundle = new Bundle();
         for (Map.Entry<String, StatusPart> me : mParts.entrySet()) {
@@ -314,11 +79,11 @@
     }
 
     /**
-     * See {@link androidx.versionedparcelable.CustomVersionedParcelable.onPostParceling()}
+     * See {@link androidx.versionedparcelable.CustomVersionedParcelable#onPostParceling()}
      * @hide
      */
     @Override
-    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
+    @RestrictTo(RestrictTo.Scope.LIBRARY)
     public void onPostParceling() {
         mParts = new HashMap<>();
         for (String key : mPartsAsBundle.keySet()) {
diff --git a/wear/wear-ongoing/src/main/java/androidx/wear/ongoing/SerializationHelper.java b/wear/wear-ongoing/src/main/java/androidx/wear/ongoing/SerializationHelper.java
new file mode 100644
index 0000000..5e6d10b
--- /dev/null
+++ b/wear/wear-ongoing/src/main/java/androidx/wear/ongoing/SerializationHelper.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.wear.ongoing;
+
+import android.app.Notification;
+import android.os.Bundle;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.RestrictTo;
+import androidx.core.app.NotificationCompat;
+import androidx.versionedparcelable.ParcelUtils;
+
+/**
+ * Class used to manage Ongoing Activity information as part of a Bundle or Notification.
+ */
+public class SerializationHelper {
+    // Utility class, avoid instantiation
+    private SerializationHelper() {
+    }
+
+    /**
+     * Checks if the given notification contains information of an ongoing activity.
+     */
+    public static boolean hasOngoingActivity(@NonNull Notification notification) {
+        return notification.extras.getBundle(EXTRA_ONGOING_ACTIVITY) != null;
+    }
+
+    /**
+     * Deserializes the {@link OngoingActivity} from a notification.
+     *
+     * @param notification the notification that may contain information about a Ongoing
+     *                     Activity.
+     * @return the data, or null of the notification doesn't contain Ongoing Activity data.
+     */
+    @Nullable
+    public static OngoingActivity create(@NonNull Notification notification) {
+        return create(notification.extras);
+    }
+
+    /**
+     * Deserializes the {@link OngoingActivity} from a Bundle.
+     *
+     * @param bundle the bundle that may contain information about a Ongoing Activity.
+     * @return the data, or null of the Bundle doesn't contain Ongoing Activity data.
+     */
+    @Nullable
+    public static OngoingActivity create(@NonNull Bundle bundle) {
+        OngoingActivityData data = createInternal(bundle);
+        return data == null ? null : new OngoingActivity(data);
+    }
+
+    /**
+     * Copies an Ongoing Activity information from a bundle to another, without deserializing
+     * and serializing (Note that Bundle instance is shared, not copied and deserializing the
+     * Ongoing activity information somewhere else negates the advantages of using this)
+     *
+     * @param sourceBundle The bundle to get the Ongoing Activity data from
+     * @param destinationBundle The bundle to put the Ongoing Activity data into.
+     */
+    public static void copy(@NonNull Bundle sourceBundle, @NonNull Bundle destinationBundle) {
+        destinationBundle.putBundle(EXTRA_ONGOING_ACTIVITY,
+                sourceBundle.getBundle(EXTRA_ONGOING_ACTIVITY));
+    }
+
+    /**
+     * Add the information from the given OngoingActivityData into the notification builder.
+     * @hide
+     */
+    @RestrictTo(RestrictTo.Scope.LIBRARY)
+    @NonNull
+    static NotificationCompat.Builder extend(@NonNull NotificationCompat.Builder builder,
+            @NonNull OngoingActivityData data) {
+        ParcelUtils.putVersionedParcelable(builder.getExtras(), EXTRA_ONGOING_ACTIVITY,
+                data);
+        return builder;
+    }
+
+    /**
+     * Add the information from the given OngoingActivityData into the notification builder
+     * and build the notification.
+     * @hide
+     */
+    @RestrictTo(RestrictTo.Scope.LIBRARY)
+    @NonNull
+    static Notification extendAndBuild(@NonNull NotificationCompat.Builder builder,
+            @NonNull OngoingActivityData data) {
+        Notification notification = extend(builder, data).build();
+        // TODO(http://b/169394642): Undo this if/when the bug is fixed.
+        notification.extras.putBundle(
+                EXTRA_ONGOING_ACTIVITY,
+                builder.getExtras().getBundle(EXTRA_ONGOING_ACTIVITY)
+        );
+        return notification;
+    }
+
+    /**
+     * Deserialize a OngoingActivityData from a notification.
+     * @hide
+     */
+    @RestrictTo(RestrictTo.Scope.LIBRARY)
+    @Nullable
+    static OngoingActivityData createInternal(@NonNull Notification notification) {
+        return createInternal(notification.extras);
+    }
+
+    /**
+     * Deserialize a OngoingActivityData from a bundle.
+     * @hide
+     */
+    @RestrictTo(RestrictTo.Scope.LIBRARY)
+    @Nullable
+    static OngoingActivityData createInternal(@NonNull Bundle bundle) {
+        return ParcelUtils.getVersionedParcelable(bundle, EXTRA_ONGOING_ACTIVITY);
+    }
+
+    /** Notification action extra which contains ongoing activity extensions */
+    private static final String EXTRA_ONGOING_ACTIVITY =
+            "android.wearable.ongoingactivities.EXTENSIONS";
+}
diff --git a/wear/wear-ongoing/src/main/java/androidx/wear/ongoing/Status.java b/wear/wear-ongoing/src/main/java/androidx/wear/ongoing/Status.java
new file mode 100644
index 0000000..0100562
--- /dev/null
+++ b/wear/wear-ongoing/src/main/java/androidx/wear/ongoing/Status.java
@@ -0,0 +1,584 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package androidx.wear.ongoing;
+
+import android.content.Context;
+import android.text.SpannableStringBuilder;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Base class to represent the status of an Ongoing Activity and render it.
+ * <p>
+ * A status is composed of Parts, and they are joined together with a template.
+ * <p>
+ * Note that for backwards compatibility reasons the code rendering this status message may not
+ * have all of the [Part] classes that are available in later versions of the library.
+ * Templates that do not have values for all of the named parts will not be used.
+ * The template list will be iterated through looking for the first template with all matching named
+ * parts available, this will be selected for rendering the status.
+ * <p>
+ * To provide for backwards compatibility, you should provide one (or more) fallback templates which
+ * use status parts from earlier versions of the API. e.g. TextPart, TimerPart & StopwatchPart
+ * <p>
+ * The status and part classes here use timestamps for updating the displayed representation of the
+ * status, in cases when this is needed (chronometers), as returned by
+ * {@link android.os.SystemClock#elapsedRealtime()}
+ */
+public final class Status implements TimeDependentText {
+    @NonNull
+    final List<CharSequence> mTemplates;
+
+    @NonNull
+    private final Map<String, StatusPart> mParts;
+
+    /**
+     * Abstract class to represent An Ongoing activity status or part of it.
+     * <p>
+     * Parts are used to create complex statuses, that may contain several timers, placeholders for
+     * text, etc. They may also be used to convey information to the system about this Ongoing
+     * Activity.
+     */
+    public abstract static class Part implements TimeDependentText {
+        // Hide constructor.
+        Part() {
+        }
+
+        @Nullable
+        StatusPart toVersionedParcelable() {
+            return null;
+        }
+
+        @Nullable
+        static Part fromVersionedParcelable(@Nullable StatusPart vp) {
+            if (vp == null) {
+                return null;
+            }
+            if (vp instanceof TextStatusPart) {
+                return new TextPart((TextStatusPart) vp);
+            } else if (vp instanceof TimerStatusPart) {
+                TimerStatusPart tsp = (TimerStatusPart) vp;
+                return tsp.mCountDown ? new TimerPart(tsp) : new StopwatchPart(tsp);
+            } else {
+                return null;
+            }
+        }
+    }
+
+    /**
+     * An Ongoing activity status (or part of it) representing a plain, static text.
+     * <p>
+     * Available since wear-ongoing:1.0.0
+     */
+    public static final class TextPart extends Part {
+        @NonNull
+        private final TextStatusPart mPart;
+
+        TextPart(@NonNull TextStatusPart part) {
+            mPart = part;
+        }
+
+        /**
+         * Create a Part representing a static text.
+         */
+        public TextPart(@NonNull String str) {
+            mPart = new TextStatusPart(str);
+        }
+
+        @Override
+        @NonNull
+        StatusPart toVersionedParcelable() {
+            return mPart;
+        }
+
+        /**
+         * See {@link TimeDependentText#getText(Context, long)}
+         */
+        @NonNull
+        @Override
+        public CharSequence getText(@NonNull Context context, long timeNowMillis) {
+            return mPart.getText(context, timeNowMillis);
+        }
+
+        /**
+         * See {@link TimeDependentText#getNextChangeTimeMillis(long)}
+         */
+        @Override
+        public long getNextChangeTimeMillis(long fromTimeMillis) {
+            return mPart.getNextChangeTimeMillis(fromTimeMillis);
+        }
+
+        @Override
+        public int hashCode() {
+            return mPart.hashCode();
+        }
+
+        @Override
+        public boolean equals(@Nullable Object obj) {
+            if (!(obj instanceof TextPart)) return false;
+            return mPart.equals(((TextPart) obj).mPart);
+        }
+    }
+
+    /**
+     * Base class for {@link TimerPart} and {@link StopwatchPart}, defines the getters but can't
+     * be created directly, create one of those instead.
+     */
+    public abstract static class TimerOrStopwatchPart extends Part {
+        @NonNull
+        private final TimerStatusPart mPart;
+
+        TimerOrStopwatchPart(@NonNull TimerStatusPart part) {
+            mPart = part;
+        }
+
+        /**
+         * @return the time at which this Timer or Stopwatch will display 0, will usually be in the
+         * past for a stopwatch and in the future for timers.
+         */
+        public long getTimeZeroMillis() {
+            return mPart.mTimeZeroMillis;
+        }
+
+        /**
+         * @return {@code false} if this is a stopwatch or {@code true} if this is a timer.
+         */
+        public boolean isCountDown() {
+            return mPart.mCountDown;
+        }
+
+        /**
+         * Determines if this Timer or Stopwatch is paused. i.e. the display representation will
+         * not change over time.
+         *
+         * @return {@code true} if this is paused, {@code false} if it's running.
+         */
+        public boolean isPaused() {
+            return mPart.isPaused();
+        }
+
+        /**
+         * @return the timestamp of the time when this was paused. Use
+         * {@link #isPaused()} to determine if this is paused or not.
+         */
+        public long getPausedAtMillis() {
+            return mPart.mPausedAtMillis;
+        }
+
+        /**
+         * Determines if this has a total duration set.
+         *
+         * @return {@code true} if this the total duration was set, {@code false} if not.
+         */
+        public boolean hasTotalDuration() {
+            return mPart.mTotalDurationMillis >= 0L;
+        }
+
+        /**
+         * @return the total duration of this timer/stopwatch, if set. Use
+         * {@link #hasTotalDuration()} to determine if this has a duration set.
+         */
+        public long getTotalDurationMillis() {
+            return mPart.mTotalDurationMillis;
+        }
+
+        @Override
+        @NonNull
+        StatusPart toVersionedParcelable() {
+            return mPart;
+        }
+
+        @Override
+        public int hashCode() {
+            return mPart.hashCode();
+        }
+
+        @Override
+        public boolean equals(@Nullable Object obj) {
+            if (!(obj instanceof TimerOrStopwatchPart)) return false;
+            return mPart.equals(((TimerOrStopwatchPart) obj).mPart);
+        }
+
+        /**
+         * See {@link TimeDependentText#getText(Context, long)}
+         */
+        @NonNull
+        @Override
+        public CharSequence getText(@NonNull Context context, long timeNowMillis) {
+            return mPart.getText(context, timeNowMillis);
+        }
+
+        /**
+         * See {@link TimeDependentText#getNextChangeTimeMillis(long)}
+         */
+        @Override
+        public long getNextChangeTimeMillis(long fromTimeMillis) {
+            return mPart.getNextChangeTimeMillis(fromTimeMillis);
+        }
+    }
+
+    /**
+     * An Ongoing activity status (or part of it) representing a timer.
+     * <p>
+     * Available since wear-ongoing:1.0.0
+     */
+    public static final class TimerPart extends TimerOrStopwatchPart {
+        TimerPart(@NonNull TimerStatusPart part) {
+            super(part);
+        }
+
+        /**
+         * Create a Part representing a timer.
+         *
+         * @param timeZeroMillis      timestamp of the time at the future in which this Timer
+         *                            should display 0.
+         * @param pausedAtMillis      timestamp of the time when this timer was paused. Or
+         *                            {@code -1L} if this timer is running.
+         * @param totalDurationMillis total duration of this timer, useful to display as a
+         *                            progress bar or similar.
+         */
+        public TimerPart(long timeZeroMillis, long pausedAtMillis,
+                long totalDurationMillis) {
+            super(new TimerStatusPart(
+                    timeZeroMillis,
+                    /* countDown = */ true,
+                    pausedAtMillis,
+                    totalDurationMillis
+            ));
+        }
+
+        /**
+         * Create a Part representing a timer.
+         *
+         * @param timeZeroMillis timestamp of the time at the future in which this Timer
+         *                       should display 0.
+         * @param pausedAtMillis timestamp of the time when this timer was paused. Or
+         *                       {@code -1L} if this timer is running.
+         */
+        public TimerPart(long timeZeroMillis, long pausedAtMillis) {
+            this(timeZeroMillis, pausedAtMillis, TimerStatusPart.LONG_DEFAULT);
+        }
+
+        /**
+         * Create a Part representing a timer.
+         *
+         * @param timeZeroMillis timestamp of the time at the future in which this Timer
+         *                       should display 0.
+         */
+        public TimerPart(long timeZeroMillis) {
+            this(timeZeroMillis, TimerStatusPart.LONG_DEFAULT);
+        }
+    }
+
+    /**
+     * An Ongoing activity status (or part of it) representing a stopwatch
+     * <p>
+     * Available since wear-ongoing:1.0.0
+     */
+    public static final class StopwatchPart extends TimerOrStopwatchPart {
+        StopwatchPart(@NonNull TimerStatusPart part) {
+            super(part);
+        }
+
+        /**
+         * Create a Part representing a stopwatch.
+         *
+         * @param timeZeroMillis      timestamp of the time at which this stopwatch started
+         *                            running.
+         * @param pausedAtMillis      timestamp of the time when this stopwatch was paused. Or
+         *                            {@code -1L} if this stopwatch is running.
+         * @param totalDurationMillis total duration of this stopwatch, useful to display as a
+         *                            progress bar or similar.
+         */
+        public StopwatchPart(long timeZeroMillis, long pausedAtMillis, long totalDurationMillis) {
+            super(new TimerStatusPart(
+                    timeZeroMillis,
+                    /* countDown = */ false,
+                    pausedAtMillis,
+                    totalDurationMillis
+            ));
+        }
+
+        /**
+         * Create a Part representing a stopwatch.
+         *
+         * @param timeZeroMillis timestamp of the time at which this stopwatch started
+         *                       running.
+         * @param pausedAtMillis timestamp of the time when this stopwatch was paused. Or
+         *                       {@code -1L} if this stopwatch is running.
+         */
+        public StopwatchPart(long timeZeroMillis, long pausedAtMillis) {
+            this(timeZeroMillis, pausedAtMillis, TimerStatusPart.LONG_DEFAULT);
+        }
+
+
+        /**
+         * Create a Part representing a stopwatch.
+         *
+         * @param timeZeroMillis timestamp of the time at which this stopwatch started
+         *                       running.
+         */
+        public StopwatchPart(long timeZeroMillis) {
+            this(timeZeroMillis, TimerStatusPart.LONG_DEFAULT);
+        }
+    }
+
+    // Name of the {@link StatusPart} created when using {@link OngoingActivityStatus.forPart()}
+    private static final String DEFAULT_STATUS_PART_NAME = "defaultStatusPartName";
+
+    // Basic constructor used by the Builder
+    @VisibleForTesting
+    Status(
+            @Nullable List<CharSequence> templates,
+            @NonNull Map<String, StatusPart> parts
+    ) {
+        mTemplates = templates;
+        mParts = parts;
+    }
+
+    OngoingActivityStatus toVersionedParcelable() {
+        return new OngoingActivityStatus(mTemplates, mParts);
+    }
+
+    static Status fromVersionedParcelable(OngoingActivityStatus vp) {
+        return new Status(vp.mTemplates, vp.mParts);
+    }
+
+    /**
+     * Convenience method for creating a Status with no template and a single Part.
+     *
+     * @param part The only Part that composes this status.
+     * @return A new {@link Status} with just one Part.
+     */
+    @NonNull
+    public static Status forPart(@NonNull Part part) {
+        // Create an OngoingActivityStatus using only this part and the default template.
+        return new Status.Builder().addPart(DEFAULT_STATUS_PART_NAME, part).build();
+    }
+
+    /**
+     * Helper to Build OngoingActivityStatus instances.
+     *
+     * Templates can be specified, to specify how to render the parts and any surrounding
+     * text/format.
+     * If no template is specified, a default template that concatenates all parts separated
+     * by space is used.
+     */
+    public static final class Builder {
+        private List<CharSequence> mTemplates = new ArrayList<>();
+        private CharSequence mDefaultTemplate = "";
+        private Map<String, StatusPart> mParts = new HashMap<>();
+
+        public Builder() {
+        }
+
+        /**
+         * Add a template to use for this status. Placeholders can be defined with #name#
+         * To produce a '#', use '##' in the template.
+         * If multiple templates are specified, the first one (in the order they where added by
+         * calling this method) that has all required fields is used.
+         * If no template is specified, a default template that concatenates all parts separated
+         * by space is used.
+         *
+         * @param template the template to be added
+         * @return this builder, to chain calls.
+         */
+        @NonNull
+        public Builder addTemplate(@NonNull CharSequence template) {
+            mTemplates.add(template);
+            return this;
+        }
+
+        /**
+         * Add a part to be inserted in the placeholders.
+         *
+         * @param name the name of this part. In the template, use this name surrounded by '#'
+         *             to reference it, e.g. here "track" and in the template "#track#"
+         * @param part The part that will be rendered in the specified position/s in the template.
+         * @return this builder, to chain calls.
+         */
+        @NonNull
+        @SuppressWarnings("MissingGetterMatchingBuilder")
+        // We don't want a getter getParts()
+        public Builder addPart(@NonNull String name, @NonNull Part part) {
+            mParts.put(name, part.toVersionedParcelable());
+            mDefaultTemplate += (mDefaultTemplate.length() > 0 ? " " : "") + "#" + name + "#";
+            return this;
+        }
+
+        /**
+         * Build an OngoingActivityStatus with the given parameters.
+         *
+         * @return the built OngoingActivityStatus
+         */
+        @NonNull
+        public Status build() {
+            List<CharSequence> templates = mTemplates.isEmpty() ? Arrays.asList(mDefaultTemplate)
+                    : mTemplates;
+
+            // Verify that the last template can be rendered by every SysUI.
+            // Verify that all templates have all required parts.
+            Map<String, CharSequence> base = new HashMap<>();
+            Map<String, CharSequence> all = new HashMap<>();
+            for (Map.Entry<String, StatusPart> me : mParts.entrySet()) {
+                if (me.getValue() instanceof TextStatusPart
+                        || me.getValue() instanceof TimerStatusPart) {
+                    base.put(me.getKey(), "");
+                }
+                all.put(me.getKey(), "");
+            }
+            if (processTemplate(templates.get(templates.size() - 1), base) == null) {
+                throw new IllegalStateException("For backwards compatibility reasons the last "
+                        + "templateThe should only use TextStatusPart & TimerStatusPart");
+            }
+            for (CharSequence template : templates) {
+                if (processTemplate(template, all) == null) {
+                    throw new IllegalStateException("The template \"" + template + "\" is missing"
+                            + " some parts for rendering.");
+                }
+            }
+
+            return new Status(templates, mParts);
+        }
+    }
+
+    /**
+     * @return the list of templates that this status has.
+     */
+    @NonNull
+    public List<CharSequence> getTemplates() {
+        return mTemplates;
+    }
+
+    /**
+     * @return the names of the parts provide to this status.
+     */
+    @NonNull
+    public Set<String> getPartNames() {
+        return Collections.unmodifiableSet(mParts.keySet());
+    }
+
+    /**
+     * Returns the value of the part with the given name.
+     *
+     * @param name the name to lookup.
+     * @return the part with the given name, can be null.
+     */
+    @Nullable
+    public Part getPart(@NonNull String name) {
+        return Part.fromVersionedParcelable(mParts.get(name));
+    }
+
+    /**
+     * Process a template and replace placeholders with the provided values.
+     * Placeholders are named, delimited by '#'. For example: '#name#'
+     * To produce a '#' in the output, use '##' in the template.
+     *
+     * @param template The template to use as base.
+     * @param values   The values to replace the placeholders in the template with.
+     * @return The template with the placeholders replaced, or null if the template references a
+     * value that it's not present (or null).
+     */
+    @Nullable
+    static CharSequence processTemplate(@NonNull CharSequence template,
+            @NonNull Map<String, CharSequence> values) {
+        SpannableStringBuilder ssb = new SpannableStringBuilder(template);
+
+        int opening = -1;
+        for (int i = 0; i < ssb.length(); i++) {
+            if (ssb.charAt(i) == '#') {
+                if (opening >= 0) {
+                    // Replace '##' with '#'
+                    // Replace '#varName#' with the value from the map.
+                    CharSequence replaceWith =
+                            opening == i - 1 ? "#" :
+                                    values.get(ssb.subSequence(opening + 1, i).toString());
+                    if (replaceWith == null) {
+                        return null;
+                    }
+                    ssb.replace(opening, i + 1, replaceWith);
+                    i = opening + replaceWith.length() - 1;
+                    opening = -1;
+                } else {
+                    opening = i;
+                }
+            }
+        }
+
+        return ssb;
+    }
+
+    /**
+     * Returns a textual representation of this status at the given time. The first template that
+     * has all required information will be used, and each part will be used in their respective
+     * placeholder/s.
+     *
+     * @param context       may be used for internationalization. Only used while this method
+     *                      executed.
+     * @param timeNowMillis the timestamp of the time we want to display, usually now, as
+     * @return the rendered text, for best compatibility, display using a TextView.
+     */
+    @NonNull
+    @Override
+    public CharSequence getText(@NonNull Context context, long timeNowMillis) {
+        Map<String, CharSequence> texts = new HashMap<>();
+        for (Map.Entry<String, StatusPart> me : mParts.entrySet()) {
+            CharSequence text = me.getValue().getText(context, timeNowMillis);
+            texts.put(me.getKey(), text);
+        }
+
+        for (CharSequence template : mTemplates) {
+            CharSequence ret = processTemplate(template, texts);
+            if (ret != null) {
+                return ret;
+            }
+        }
+
+        return "";
+    }
+
+    /**
+     * Returns the next time this status could have a different rendering.
+     * There is no guarantee that the rendering will change at the returned time (for example, if
+     * some information in the status is not rendered).
+     *
+     * @param fromTimeMillis current time, usually now as returned by
+     *                       {@link android.os.SystemClock#elapsedRealtime()}. In most cases
+     *                       {@code getText} and {@code getNextChangeTimeMillis} should be called
+     *                       with the exact same timestamp, so changes are not missed.
+     * @return the next time (counting from fromTimeMillis) that this status may produce a
+     * different result when calling getText().
+     */
+    @Override
+    public long getNextChangeTimeMillis(long fromTimeMillis) {
+        long ret = Long.MAX_VALUE;
+        for (StatusPart part : mParts.values()) {
+            ret = Math.min(ret, part.getNextChangeTimeMillis(fromTimeMillis));
+        }
+        return ret;
+    }
+}
+
diff --git a/wear/wear-ongoing/src/main/java/androidx/wear/ongoing/StatusPart.java b/wear/wear-ongoing/src/main/java/androidx/wear/ongoing/StatusPart.java
index bd2019c..ca0e81b 100644
--- a/wear/wear-ongoing/src/main/java/androidx/wear/ongoing/StatusPart.java
+++ b/wear/wear-ongoing/src/main/java/androidx/wear/ongoing/StatusPart.java
@@ -16,14 +16,13 @@
 
 package androidx.wear.ongoing;
 
+import androidx.annotation.RestrictTo;
 import androidx.versionedparcelable.VersionedParcelable;
 
 /**
- * Abstract class to represent An Ongoing activity status or part of it.
- * <p>
- * Parts are used to create complex statuses, that may contain several timers, placeholders for
- * text, etc.
- * They may also be used to convey information to the system about this Ongoing Activity.
+ * Base class for objects that serialize/deserialize {@link Status.Part} objects.
+ * @hide
  */
-public abstract class StatusPart implements VersionedParcelable, TimeDependentText {
+@RestrictTo(RestrictTo.Scope.LIBRARY)
+abstract class StatusPart implements VersionedParcelable, TimeDependentText {
 }
diff --git a/wear/wear-ongoing/src/main/java/androidx/wear/ongoing/TextStatusPart.java b/wear/wear-ongoing/src/main/java/androidx/wear/ongoing/TextStatusPart.java
index 4da03ca..e1379d98 100644
--- a/wear/wear-ongoing/src/main/java/androidx/wear/ongoing/TextStatusPart.java
+++ b/wear/wear-ongoing/src/main/java/androidx/wear/ongoing/TextStatusPart.java
@@ -19,18 +19,21 @@
 import android.content.Context;
 
 import androidx.annotation.NonNull;
+import androidx.annotation.RestrictTo;
 import androidx.versionedparcelable.ParcelField;
 import androidx.versionedparcelable.VersionedParcelize;
 
 import java.util.Objects;
 
 /**
- * An Ongoing activity status (or part of it) representing a plain, static text.
+ * Implementation and internal representation of {@link Status.TextPart}.
  * <p>
  * Available since wear-ongoing:1.0.0
+ * @hide
  */
+@RestrictTo(RestrictTo.Scope.LIBRARY)
 @VersionedParcelize
-public class TextStatusPart extends StatusPart {
+class TextStatusPart extends StatusPart {
     @NonNull
     @ParcelField(value = 1, defaultValue = "")
     String mStr = "";
@@ -39,7 +42,7 @@
     TextStatusPart() {
     }
 
-    public TextStatusPart(@NonNull String str) {
+    TextStatusPart(@NonNull String str) {
         this.mStr = str;
     }
 
diff --git a/wear/wear-ongoing/src/main/java/androidx/wear/ongoing/TimerStatusPart.java b/wear/wear-ongoing/src/main/java/androidx/wear/ongoing/TimerStatusPart.java
index 6336499..7d5b663 100644
--- a/wear/wear-ongoing/src/main/java/androidx/wear/ongoing/TimerStatusPart.java
+++ b/wear/wear-ongoing/src/main/java/androidx/wear/ongoing/TimerStatusPart.java
@@ -20,6 +20,7 @@
 import android.text.format.DateUtils;
 
 import androidx.annotation.NonNull;
+import androidx.annotation.RestrictTo;
 import androidx.versionedparcelable.NonParcelField;
 import androidx.versionedparcelable.ParcelField;
 import androidx.versionedparcelable.VersionedParcelize;
@@ -27,12 +28,15 @@
 import java.util.Objects;
 
 /**
- * An Ongoing activity status (or part of it) representing a timer or stopwatch.
+ * Implementation and internal representation of {@link Status.TimerPart} and
+ * {@link Status.StopwatchPart}
  * <p>
  * Available since wear-ongoing:1.0.0
+ * @hide
  */
+@RestrictTo(RestrictTo.Scope.LIBRARY)
 @VersionedParcelize
-public class TimerStatusPart extends StatusPart {
+class TimerStatusPart extends StatusPart {
     @ParcelField(value = 1, defaultValue = "0")
     long mTimeZeroMillis;
 
@@ -68,7 +72,7 @@
      * @param totalDurationMillis total duration of this timer/stopwatch, useful to display as a
      *                            progress bar or similar.
      */
-    public TimerStatusPart(long timeZeroMillis, boolean countDown, long pausedAtMillis,
+    TimerStatusPart(long timeZeroMillis, boolean countDown, long pausedAtMillis,
             long totalDurationMillis) {
         this.mTimeZeroMillis = timeZeroMillis;
         this.mCountDown = countDown;
@@ -77,43 +81,6 @@
     }
 
     /**
-     * Create a Status representing a timer or stopwatch.
-     *
-     * @param timeZeroMillis      timestamp of the time at which this Timer should display 0,
-     *                            will be in the
-     *                            past for a stopwatch and usually in the future for timers.
-     * @param countDown           indicates if this is a stopwatch (when {@code false}) or timer
-     *                            (when {@code true}).
-     * @param pausedAtMillis      timestamp of the time when this timer was paused. Or
-     *                            {@code -1L} if this timer is running.
-     */
-    public TimerStatusPart(long timeZeroMillis, boolean countDown, long pausedAtMillis) {
-        this(timeZeroMillis, countDown, pausedAtMillis, LONG_DEFAULT);
-    }
-
-    /**
-     * Create a Status representing a timer or stopwatch.
-     *
-     * @param timeZeroMillis      timestamp of the time at which this Timer should display 0,
-     *                            will be in the
-     *                            past for a stopwatch and usually in the future for timers.
-     * @param countDown           indicates if this is a stopwatch (when {@code false}) or timer
-     *                            (when {@code true}).
-     */
-    public TimerStatusPart(long timeZeroMillis, boolean countDown) {
-        this(timeZeroMillis, countDown, LONG_DEFAULT);
-    }
-
-    /**
-     * Create a Status representing stopwatch.
-     *
-     * @param timeZeroMillis      timestamp of the time at which this Stopwatch started.
-     */
-    public TimerStatusPart(long timeZeroMillis) {
-        this(timeZeroMillis, false);
-    }
-
-    /**
      * See {@link TimeDependentText#getText(Context, long)}]
      */
     @NonNull
@@ -152,57 +119,10 @@
                 fromTimeMillis + ((mTimeZeroMillis - fromTimeMillis) % 1000 + 1999) % 1000 + 1;
     }
 
-    /**
-     * @return the time at which this Timer will display 0, will be in the past for a stopwatch
-     * and usually in the future for timers.
-     */
-    public long getTimeZeroMillis() {
-        return mTimeZeroMillis;
-    }
-
-    /**
-     * @return {@code false} if this is a stopwatch or {@code true} if this is a timer.
-     */
-    public boolean isCountDown() {
-        return mCountDown;
-    }
-
-    /**
-     * Determines if this timer is paused. i.e. the display representation will not change over
-     * time.
-     *
-     * @return {@code true} if this timer is paused, {@code false} if it's running.
-     */
     public boolean isPaused() {
         return mPausedAtMillis >= 0L;
     }
 
-    /**
-     * @return the timestamp of the time when this timer was paused. Use
-     * {@link TimerStatusPart#isPaused()} to determine if this timer is paused or not.
-     */
-    public long getPausedAtMillis() {
-        return mPausedAtMillis;
-    }
-
-    /**
-     * Determines if this timer has a total duration set.
-     *
-     * @return {@code true} if this the total duration was set, {@code false} if not.
-     */
-    public boolean hasTotalDuration() {
-        return mTotalDurationMillis >= 0L;
-    }
-
-    /**
-     * @return the total duration of this timer/stopwatch, if set. Use
-     * {@link TimerStatusPart#hasTotalDuration()} to determine if this timer has a
-     * duration set.
-     */
-    public long getTotalDurationMillis() {
-        return mTotalDurationMillis;
-    }
-
     @Override
     public boolean equals(Object o) {
         if (!(o instanceof TimerStatusPart)) return false;
diff --git a/wear/wear-ongoing/src/test/java/androidx/wear/ongoing/OngoingActivityPartTest.kt b/wear/wear-ongoing/src/test/java/androidx/wear/ongoing/OngoingActivityPartTest.kt
index 7c19cda..1110cee 100644
--- a/wear/wear-ongoing/src/test/java/androidx/wear/ongoing/OngoingActivityPartTest.kt
+++ b/wear/wear-ongoing/src/test/java/androidx/wear/ongoing/OngoingActivityPartTest.kt
@@ -30,86 +30,86 @@
     @Test
     fun testTextOngoingActivityStatusBasic() {
         val text = "Text"
-        val textStatus = TextStatusPart(text)
+        val textStatus = Status.TextPart(text)
 
         assertEquals(Long.MAX_VALUE, textStatus.getNextChangeTimeMillis(0))
 
         assertEquals(text, textStatus.getText(context, 0))
 
-        assertEquals(TextStatusPart(text), textStatus)
-        assertNotEquals(TextStatusPart("Other"), textStatus)
+        assertEquals(Status.TextPart(text), textStatus)
+        assertNotEquals(Status.TextPart("Other"), textStatus)
 
-        assertEquals(TextStatusPart(text).hashCode(), textStatus.hashCode())
+        assertEquals(Status.TextPart(text).hashCode(), textStatus.hashCode())
         assertEquals(
-            TextStatusPart("Other").hashCode(),
-            TextStatusPart("Other").hashCode()
+            Status.TextPart("Other").hashCode(),
+            Status.TextPart("Other").hashCode()
         )
     }
 
     @Test
     fun testTimerOngoingActivityStatusBasic() {
         assertEquals(
-            TimerStatusPart(1234L),
-            TimerStatusPart(1234L)
+            Status.StopwatchPart(1234L),
+            Status.StopwatchPart(1234L)
         )
         assertEquals(
-            TimerStatusPart(1234L).hashCode(),
-            TimerStatusPart(1234L).hashCode()
+            Status.StopwatchPart(1234L).hashCode(),
+            Status.StopwatchPart(1234L).hashCode()
         )
         assertNotEquals(
-            TimerStatusPart(1234L),
-            TimerStatusPart(1235L)
+            Status.StopwatchPart(1234L),
+            Status.StopwatchPart(1235L)
         )
 
         assertEquals(
-            TimerStatusPart(1234L, true),
-            TimerStatusPart(1234L, true)
+            Status.TimerPart(1234L),
+            Status.TimerPart(1234L)
         )
         assertEquals(
-            TimerStatusPart(1234L, true).hashCode(),
-            TimerStatusPart(1234L, true).hashCode()
+            Status.TimerPart(1234L).hashCode(),
+            Status.TimerPart(1234L).hashCode()
         )
         assertNotEquals(
-            TimerStatusPart(1234L, false),
-            TimerStatusPart(1234L, true)
+            Status.StopwatchPart(1234L),
+            Status.TimerPart(1234L)
         )
 
         assertEquals(
-            TimerStatusPart(1234L, true, 5678L),
-            TimerStatusPart(1234L, true, 5678L)
+            Status.TimerPart(1234L, 5678L),
+            Status.TimerPart(1234L, 5678L)
         )
         assertEquals(
-            TimerStatusPart(1234L, true, 5678L).hashCode(),
-            TimerStatusPart(1234L, true, 5678L).hashCode()
+            Status.TimerPart(1234L, 5678L).hashCode(),
+            Status.TimerPart(1234L, 5678L).hashCode()
         )
         assertNotEquals(
-            TimerStatusPart(1234L, true, 5678L),
-            TimerStatusPart(1234L, true, 5679L)
+            Status.TimerPart(1234L, 5678L),
+            Status.TimerPart(1234L, 5679L)
         )
         assertNotEquals(
-            TimerStatusPart(1234L, true, 5678L),
-            TimerStatusPart(1234L, true)
+            Status.TimerPart(1234L, 5678L),
+            Status.TimerPart(1234L)
         )
 
         assertEquals(
-            TimerStatusPart(1234L, true, 5678L, 100L),
-            TimerStatusPart(1234L, true, 5678L, 100L)
+            Status.TimerPart(1234L, 5678L, 100L),
+            Status.TimerPart(1234L, 5678L, 100L)
         )
         assertEquals(
-            TimerStatusPart(1234L, true, 5678L, 100L).hashCode(),
-            TimerStatusPart(1234L, true, 5678L, 100L).hashCode()
+            Status.TimerPart(1234L, 5678L, 100L).hashCode(),
+            Status.TimerPart(1234L, 5678L, 100L).hashCode()
         )
         assertNotEquals(
-            TimerStatusPart(1234L, true, 5678L, 100L),
-            TimerStatusPart(1234L, true, 5678L, 101L)
+            Status.TimerPart(1234L, 5678L, 100L),
+            Status.TimerPart(1234L, 5678L, 101L)
         )
         assertNotEquals(
-            TimerStatusPart(1234L, true, 5678L, 100L),
-            TimerStatusPart(1234L, true, 5678L)
+            Status.TimerPart(1234L, 5678L, 100L),
+            Status.TimerPart(1234L, 5678L)
         )
         assertNotEquals(
-            TimerStatusPart(1234L, true, 5678L, 100L),
-            TimerStatusPart(1234L, true)
+            Status.TimerPart(1234L, 5678L, 100L),
+            Status.TimerPart(1234L)
         )
     }
 
@@ -117,16 +117,15 @@
     fun testOngoingActivityStatusSerialization() {
         val key = "KEY"
         listOf(
-            TimerStatusPart(1234L),
-            TextStatusPart("Text1"),
-            TimerStatusPart(1234L, false),
-            TimerStatusPart(1234L, true),
-            TimerStatusPart(1234L, true, 5678L),
-            TextStatusPart("Text2"),
-            TimerStatusPart(1234L, false, 5678L, 100L)
+            Status.StopwatchPart(1234L),
+            Status.TextPart("Text1"),
+            Status.TimerPart(1234L),
+            Status.TimerPart(1234L, 5678L),
+            Status.TextPart("Text2"),
+            Status.StopwatchPart(1234L, 5678L, 100L)
         ).forEach { original ->
             val bundle = Bundle()
-            ParcelUtils.putVersionedParcelable(bundle, key, original)
+            ParcelUtils.putVersionedParcelable(bundle, key, original.toVersionedParcelable())
 
             val p = Parcel.obtain()
             p.writeParcelable(bundle, 0)
@@ -134,8 +133,9 @@
 
             val receivedBundle = p.readParcelable<Bundle>(Bundle::class.java.classLoader)!!
 
-            val received = ParcelUtils.getVersionedParcelable<StatusPart>(receivedBundle, key)!!
-            assertEquals(original::class, received::class)
+            val received = Status.Part.fromVersionedParcelable(
+                ParcelUtils.getVersionedParcelable<StatusPart>(receivedBundle, key)
+            )!!
             assertEquals(original, received)
             assertEquals(original.hashCode(), received.hashCode())
         }
@@ -143,27 +143,21 @@
 
     @Test
     fun testTimerOngoingActivityStatusGetters() {
-        TimerStatusPart(123L).also {
+        Status.StopwatchPart(123L).also {
             assertEquals(123L, it.timeZeroMillis)
-            assertFalse(it.isPaused)
-            assertFalse(it.hasTotalDuration())
-        }
-
-        TimerStatusPart(1234L, false).also {
-            assertEquals(1234L, it.timeZeroMillis)
             assertFalse(it.isCountDown)
             assertFalse(it.isPaused)
             assertFalse(it.hasTotalDuration())
         }
 
-        TimerStatusPart(12345L, true).also {
+        Status.TimerPart(12345L).also {
             assertEquals(12345L, it.timeZeroMillis)
             assertTrue(it.isCountDown)
             assertFalse(it.isPaused)
             assertFalse(it.hasTotalDuration())
         }
 
-        TimerStatusPart(2345L, false, 3456L).also {
+        Status.StopwatchPart(2345L, 3456L).also {
             assertEquals(2345L, it.timeZeroMillis)
             assertFalse(it.isCountDown)
             assertTrue(it.isPaused)
@@ -171,7 +165,7 @@
             assertFalse(it.hasTotalDuration())
         }
 
-        TimerStatusPart(4567L, true, 7890L, 12000L).also {
+        Status.TimerPart(4567L, 7890L, 12000L).also {
             assertEquals(4567L, it.timeZeroMillis)
             assertTrue(it.isCountDown)
             assertTrue(it.isPaused)
@@ -187,10 +181,10 @@
         // timestamp 0).
         val t0 = 123456L
         val timerStatus =
-            TimerStatusPart(/* timeZeroMillis = */ t0)
+            Status.StopwatchPart(/* timeZeroMillis = */ t0)
 
         // The chronometer is not paused.
-        assertFalse(timerStatus.isPaused())
+        assertFalse(timerStatus.isPaused)
 
         // The chronometer will always change at timestamps ending in 456.
         assertEquals(456L, timerStatus.getNextChangeTimeMillis(0L))
@@ -219,13 +213,10 @@
         // Create a timer, set to expire at the given timestamp (around 2 minutes after
         // timestamp 0).
         val t0 = 123456L
-        val timerStatus = TimerStatusPart(
-            /* timeZeroMillis = */ t0,
-            /* countDown = */ true
-        )
+        val timerStatus = Status.TimerPart(t0)
 
         // The Timer is not paused.
-        assertFalse(timerStatus.isPaused())
+        assertFalse(timerStatus.isPaused)
 
         // The timer will always change at timestamps ending in 456.
         assertEquals(456L, timerStatus.getNextChangeTimeMillis(0L))
@@ -251,9 +242,8 @@
     @Test
     fun testTimerOngoingActivityStatusChronometerPaused() {
         val t0 = 123456L
-        var timerStatus = TimerStatusPart(
+        var timerStatus = Status.StopwatchPart(
             /* timeZeroMillis = */ t0,
-            /* countDown = */ false,
             /* pausedAt = */ t0 + 1999
         )
 
@@ -276,9 +266,8 @@
     @Test
     fun testTimerOngoingActivityStatusTimerPaused() {
         val t0 = 123456L
-        var timerStatus = TimerStatusPart(
+        var timerStatus = Status.TimerPart(
             /* timeZeroMillis = */ t0,
-            /* countDown = */ true,
             /* pausedAt = */ t0 + 1999
         )
 
diff --git a/wear/wear-ongoing/src/test/java/androidx/wear/ongoing/OngoingActivityStatusTest.kt b/wear/wear-ongoing/src/test/java/androidx/wear/ongoing/OngoingActivityStatusTest.kt
index 0ec5a9c..31c702f 100644
--- a/wear/wear-ongoing/src/test/java/androidx/wear/ongoing/OngoingActivityStatusTest.kt
+++ b/wear/wear-ongoing/src/test/java/androidx/wear/ongoing/OngoingActivityStatusTest.kt
@@ -25,9 +25,9 @@
 
     @Test
     fun testTextOngoingActivityStatusText() {
-        val status = OngoingActivityStatus.Builder()
-            .addPart("1", TextStatusPart("First Text"))
-            .addPart("2", TextStatusPart("Second Text"))
+        val status = Status.Builder()
+            .addPart("1", Status.TextPart("First Text"))
+            .addPart("2", Status.TextPart("Second Text"))
             .build()
 
         assertEquals(Long.MAX_VALUE, status.getNextChangeTimeMillis(0))
@@ -37,9 +37,9 @@
 
     @Test
     fun testTextOngoingActivityStatusTextAndTemplate() {
-        val status = OngoingActivityStatus.Builder()
-            .addPart("one", TextStatusPart("First Text"))
-            .addPart("two", TextStatusPart("Second Text"))
+        val status = Status.Builder()
+            .addPart("one", Status.TextPart("First Text"))
+            .addPart("two", Status.TextPart("Second Text"))
             .addTemplate("#one# | #two#").build()
 
         assertEquals(Long.MAX_VALUE, status.getNextChangeTimeMillis(0))
@@ -52,9 +52,9 @@
 
     @Test
     fun testTextOngoingActivityStatusTextAndTemplate2() {
-        val status = OngoingActivityStatus.Builder()
-            .addPart("a", TextStatusPart("A"))
-            .addPart("b", TextStatusPart("B"))
+        val status = Status.Builder()
+            .addPart("a", Status.TextPart("A"))
+            .addPart("b", Status.TextPart("B"))
             .addTemplate("#a##b#").build()
 
         assertEquals(Long.MAX_VALUE, status.getNextChangeTimeMillis(0))
@@ -68,9 +68,9 @@
     @Test
     fun testTextOngoingActivityStatusMixed() {
         val t0 = 123456L
-        val status = OngoingActivityStatus.Builder()
-            .addPart("type", TextStatusPart("Workout"))
-            .addPart("time", TimerStatusPart(t0, /* countDown = */ false))
+        val status = Status.Builder()
+            .addPart("type", Status.TextPart("Workout"))
+            .addPart("time", Status.StopwatchPart(t0))
             .addTemplate("The time on your #type# is #time#")
             .build()
 
@@ -103,12 +103,12 @@
         ).forEach { (template, expected) ->
             assertEquals(
                 expected,
-                OngoingActivityStatus.processTemplate(template, values).toString()
+                Status.processTemplate(template, values).toString()
             )
         }
 
         // Check that when undefined values are used, returns null
-        assertNull(OngoingActivityStatus.processTemplate("#NOT#", values))
+        assertNull(Status.processTemplate("#NOT#", values))
     }
 
     @Test
@@ -127,7 +127,7 @@
             listOf("#5#", "#4#", "#3#", "#2#") to "Three",
             listOf("#1##11#", "2=#2#") to "2=Two",
         ).forEach { (templates, expected) ->
-            val status = OngoingActivityStatus(
+            val status = Status(
                 templates,
                 values.map { (k, v) ->
                     k to TextStatusPart(v)
@@ -141,10 +141,10 @@
     @Test(expected = IllegalStateException::class)
     fun testVerifyBuildCheckBaseTemplate() {
         // We verify on build() that the last template uses only base parts (Text & Timer)
-        OngoingActivityStatus.Builder()
+        Status.Builder()
             .addTemplate("#1#")
             .addTemplate("#2#")
-            .addPart("1", TextStatusPart("text"))
+            .addPart("1", Status.TextPart("text"))
             .addPart("2", SampleStatusPart())
             .build()
     }
@@ -152,15 +152,25 @@
     @Test(expected = IllegalStateException::class)
     fun testVerifyBuildCheckTemplates() {
         // We verify on build() that all parts used on templates are present
-        OngoingActivityStatus.Builder()
+        Status.Builder()
             .addTemplate("#1##2##3#")
-            .addPart("1", TextStatusPart("text"))
-            .addPart("2", TimerStatusPart(12345L))
+            .addPart("1", Status.TextPart("text"))
+            .addPart("2", Status.StopwatchPart(12345L))
             .build()
     }
 
-    class SampleStatusPart : StatusPart() {
+    private class SampleStatusPartImpl : StatusPart() {
         override fun getText(context: Context, timeNowMillis: Long) = "Sample"
         override fun getNextChangeTimeMillis(fromTimeMillis: Long) = Long.MAX_VALUE
     }
+
+    private class SampleStatusPart : Status.Part() {
+        val mPart = SampleStatusPartImpl()
+
+        override fun getText(context: Context, timeNowMillis: Long) =
+            mPart.getText(context, timeNowMillis)
+        override fun getNextChangeTimeMillis(fromTimeMillis: Long) =
+            mPart.getNextChangeTimeMillis(fromTimeMillis)
+        override fun toVersionedParcelable(): StatusPart = mPart
+    }
 }
diff --git a/wear/wear-ongoing/src/test/java/androidx/wear/ongoing/OngoingActivityTest.kt b/wear/wear-ongoing/src/test/java/androidx/wear/ongoing/OngoingActivityTest.kt
index 4f7f098..1519bf6 100644
--- a/wear/wear-ongoing/src/test/java/androidx/wear/ongoing/OngoingActivityTest.kt
+++ b/wear/wear-ongoing/src/test/java/androidx/wear/ongoing/OngoingActivityTest.kt
@@ -27,7 +27,11 @@
     private val StaticIconResourceId = 456
     private val LocusIdValue = LocusIdCompat("TestLocusId")
     private val OaId = 123456
-    private val Status = OngoingActivityStatus.forPart(TextStatusPart("Basic Status"))
+    private val BasicStatus = androidx.wear.ongoing.Status.forPart(
+        Status.TextPart(
+            "Basic Status"
+        )
+    )
     private val NotificationId = 4321
     private val ChannelId = "ChannelId"
 
@@ -55,7 +59,7 @@
         val notification = builder.build()
 
         // check that the Notification contains the information needed.
-        val received = OngoingActivityData.create(notification)!!
+        val received = SerializationHelper.create(notification)!!
         assertEquals(StaticIconResourceId, received.staticIcon.resId)
         assertEquals(PendingIntentValue, received.touchIntent)
         // Also ensure that unset fields are null
@@ -72,7 +76,7 @@
             .setStaticIcon(StaticIconResourceId)
             .setLocusId(LocusIdValue)
             .setOngoingActivityId(OaId)
-            .setStatus(Status)
+            .setStatus(BasicStatus)
             .setTouchIntent(PendingIntentValue)
             .build()
         oa.apply(context)
@@ -80,12 +84,12 @@
         val notification = builder.build()
 
         // check that the Notification contains the information needed.
-        val received = OngoingActivityData.create(notification)!!
+        val received = SerializationHelper.create(notification)!!
         assertEquals(AnimatedIconResourceId, received.animatedIcon!!.resId)
         assertEquals(StaticIconResourceId, received.staticIcon.resId)
         assertEquals(LocusIdValue, received.locusId)
         assertEquals(OaId, received.ongoingActivityId)
-        assertEquals(Status, received.status)
+        // TODO(ssancho): check status
         assertEquals(PendingIntentValue, received.touchIntent)
     }
 
@@ -97,26 +101,30 @@
             .setStaticIcon(StaticIconResourceId)
             .setLocusId(LocusIdValue)
             .setOngoingActivityId(OaId)
-            .setStatus(Status)
+            .setStatus(BasicStatus)
             .setTouchIntent(PendingIntentValue)
             .build()
         oa.apply(context)
         notificationManager.notify(NotificationId, builder.build())
 
         // After posting, send an update.
-        val newStatus = OngoingActivityStatus.forPart(TimerStatusPart(12345))
+        val newStatus = androidx.wear.ongoing.Status.forPart(
+            Status.StopwatchPart(
+                12345
+            )
+        )
         oa.update(context, newStatus)
 
         // Get the notification and check that the status, and only the status has been updated.
         val notifications = notificationManager.activeNotifications
         assertEquals(1, notifications.size)
 
-        val received = OngoingActivityData.create(notifications[0].notification)!!
+        val received = SerializationHelper.create(notifications[0].notification)!!
         assertEquals(AnimatedIconResourceId, received.animatedIcon!!.resId)
         assertEquals(StaticIconResourceId, received.staticIcon.resId)
         assertEquals(LocusIdValue, received.locusId)
         assertEquals(OaId, received.ongoingActivityId)
-        assertEquals(newStatus, received.status)
+        // TODO(ssancho): check status
         assertEquals(PendingIntentValue, received.touchIntent)
 
         notificationManager.cancel(NotificationId)
@@ -143,7 +151,7 @@
         for (i in 1..n) {
             val builder = NotificationCompat.Builder(context, ChannelId)
             OngoingActivity.Builder(context, NotificationId + i, builder)
-                .setStatus(OngoingActivityStatus.forPart(TextStatusPart("Ongoing Activity")))
+                .setStatus(Status.forPart(Status.TextPart("Ongoing Activity")))
                 .setOngoingActivityId(i)
                 .setStaticIcon(StaticIconResourceId)
                 .setTouchIntent(PendingIntentValue)
@@ -159,13 +167,13 @@
             val status = "New Status $i"
             statuses.add(status)
             OngoingActivity.recoverOngoingActivity(context, i)!!
-                .update(context, OngoingActivityStatus.forPart(TextStatusPart(status)))
+                .update(context, Status.forPart(Status.TextPart(status)))
         }
         assertEquals(n, statuses.size) // Just in case.
 
         // Get status from notifications.
         val notificationStatuses = notificationManager.activeNotifications.mapNotNull { sbn ->
-            OngoingActivityData.create(sbn.notification)?.status?.getText(context, 0).toString()
+            SerializationHelper.create(sbn.notification)?.status?.getText(context, 0).toString()
         }.toSet()
 
         // Check.
@@ -183,7 +191,7 @@
             .setStaticIcon(StaticIconResourceId)
             .setLocusId(LocusIdValue)
             .setOngoingActivityId(OaId)
-            .setStatus(Status)
+            .setStatus(BasicStatus)
             .setTouchIntent(PendingIntentValue)
             .build()
         oa.apply(context)
@@ -191,15 +199,15 @@
 
         // Copy the data.
         val newBundle = Bundle()
-        OngoingActivityData.copy(notification.extras, newBundle)
+        SerializationHelper.copy(notification.extras, newBundle)
 
         // check that the information was copied.
-        val received = OngoingActivityData.create(newBundle)!!
+        val received = SerializationHelper.create(newBundle)!!
         assertEquals(AnimatedIconResourceId, received.animatedIcon!!.resId)
         assertEquals(StaticIconResourceId, received.staticIcon.resId)
         assertEquals(LocusIdValue, received.locusId)
         assertEquals(OaId, received.ongoingActivityId)
-        assertEquals(Status, received.status)
+        // TODO(ssancho): check status
         assertEquals(PendingIntentValue, received.touchIntent)
     }
 
@@ -211,26 +219,30 @@
             .setStaticIcon(StaticIconResourceId)
             .setLocusId(LocusIdValue)
             .setOngoingActivityId(OaId)
-            .setStatus(Status)
+            .setStatus(BasicStatus)
             .setTouchIntent(PendingIntentValue)
             .build()
         oa.apply(context)
         notificationManager.notify(NotificationId, builder.build())
 
         // After posting, send an update.
-        val newStatus = OngoingActivityStatus.forPart(TimerStatusPart(12345))
+        val newStatus = androidx.wear.ongoing.Status.forPart(
+            Status.StopwatchPart(
+                12345
+            )
+        )
         OngoingActivity.recoverOngoingActivity(context)!!.update(context, newStatus)
 
         // Get the notification and check that the status, and only the status has been updated.
         val notifications = notificationManager.activeNotifications
         assertEquals(1, notifications.size)
 
-        val received = OngoingActivityData.create(notifications[0].notification)!!
+        val received = SerializationHelper.create(notifications[0].notification)!!
         assertEquals(AnimatedIconResourceId, received.animatedIcon!!.resId)
         assertEquals(StaticIconResourceId, received.staticIcon.resId)
         assertEquals(LocusIdValue, received.locusId)
         assertEquals(OaId, received.ongoingActivityId)
-        assertEquals(newStatus, received.status)
+        // TODO(ssancho): check status
         assertEquals(PendingIntentValue, received.touchIntent)
 
         // Clean up.
@@ -252,7 +264,7 @@
 
         val notifications = notificationManager.activeNotifications
         assertEquals(1, notifications.size)
-        val received = OngoingActivityData.create(notifications[0].notification)!!
+        val received = SerializationHelper.create(notifications[0].notification)!!
         assertNull(received.animatedIcon)
         assertEquals(StaticIconResourceId, received.staticIcon.resId)
         assertEquals(contentText, received.status!!.getText(context, 0).toString())
@@ -278,7 +290,7 @@
         val oa = OngoingActivity.Builder(context, NotificationId, builder)
             .setAnimatedIcon(newAnimatedIconResourceId)
             .setStaticIcon(newStaticIconResourceId)
-            .setStatus(Status)
+            .setStatus(BasicStatus)
             .setTouchIntent(newPendingIntentValue)
             .build()
         oa.apply(context)
@@ -286,10 +298,10 @@
 
         val notifications = notificationManager.activeNotifications
         assertEquals(1, notifications.size)
-        val received = OngoingActivityData.create(notifications[0].notification)!!
+        val received = SerializationHelper.create(notifications[0].notification)!!
         assertEquals(newAnimatedIconResourceId, received.animatedIcon!!.resId)
         assertEquals(newStaticIconResourceId, received.staticIcon.resId)
-        assertEquals(Status, received.status)
+        // TODO(ssancho): check status
         assertEquals(newPendingIntentValue, received.touchIntent)
 
         // Clean up.
@@ -304,14 +316,14 @@
             .setContentText("Text")
         var notification = builder.build()
 
-        assertFalse(OngoingActivityData.hasOngoingActivity(notification))
+        assertFalse(SerializationHelper.hasOngoingActivity(notification))
 
         OngoingActivity.Builder(context, NotificationId, builder)
             .build()
             .apply(context)
 
         notification = builder.build()
-        assertTrue(OngoingActivityData.hasOngoingActivity(notification))
+        assertTrue(SerializationHelper.hasOngoingActivity(notification))
     }
 
     @Test
@@ -320,7 +332,7 @@
         val builder1 = NotificationCompat.Builder(context, ChannelId)
         val oa1 = OngoingActivity.Builder(context, NotificationId, builder1)
             .setStaticIcon(StaticIconResourceId)
-            .setStatus(OngoingActivityStatus.forPart(TextStatusPart("status1")))
+            .setStatus(Status.forPart(Status.TextPart("status1")))
             .setOngoingActivityId(1)
             .setTouchIntent(PendingIntentValue)
             .build()
@@ -330,7 +342,7 @@
         val builder2 = NotificationCompat.Builder(context, ChannelId)
         val oa2 = OngoingActivity.Builder(context, tag, NotificationId, builder2)
             .setStaticIcon(StaticIconResourceId)
-            .setStatus(OngoingActivityStatus.forPart(TextStatusPart("status2")))
+            .setStatus(Status.forPart(Status.TextPart("status2")))
             .setOngoingActivityId(2)
             .setTouchIntent(PendingIntentValue)
             .build()
@@ -340,13 +352,21 @@
         assertEquals(2, notificationManager.activeNotifications.size)
 
         // After posting, send an update to the second OA and check the statuses.
-        val newStatus2 = OngoingActivityStatus.forPart(TextStatusPart("update2"))
+        val newStatus2 = androidx.wear.ongoing.Status.forPart(
+            Status.TextPart(
+                "update2"
+            )
+        )
         OngoingActivity.recoverOngoingActivity(context, 2)?.update(context, newStatus2)
 
         assertEquals("status1, update2", getStatuses())
 
         // Update the first OA, and check the statuses.
-        val newStatus1 = OngoingActivityStatus.forPart(TextStatusPart("updated-one"))
+        val newStatus1 = androidx.wear.ongoing.Status.forPart(
+            Status.TextPart(
+                "updated-one"
+            )
+        )
         oa1.update(context, newStatus1)
 
         assertEquals("updated-one, update2", getStatuses())
@@ -360,7 +380,7 @@
 
     private fun getStatuses(): String =
         notificationManager.activeNotifications
-            .mapNotNull { OngoingActivityData.create(it.notification) }
-            .sortedBy { it.mOngoingActivityId }
+            .mapNotNull { SerializationHelper.create(it.notification) }
+            .sortedBy { it.ongoingActivityId }
             .joinToString { it.status?.getText(context, 0L).toString() }
 }
diff --git a/wear/wear-phone-interactions/lint-baseline.xml b/wear/wear-phone-interactions/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/wear/wear-phone-interactions/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/wear/wear-remote-interactions/lint-baseline.xml b/wear/wear-remote-interactions/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/wear/wear-remote-interactions/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/wear/wear-watchface-client/guava/api/current.txt b/wear/wear-watchface-client/guava/api/current.txt
index eb19704..bf268ec 100644
--- a/wear/wear-watchface-client/guava/api/current.txt
+++ b/wear/wear-watchface-client/guava/api/current.txt
@@ -2,10 +2,10 @@
 package androidx.wear.watchface.client {
 
   public class ListenableWatchFaceControlClient implements androidx.wear.watchface.client.WatchFaceControlClient {
-    ctor public ListenableWatchFaceControlClient(androidx.wear.watchface.client.WatchFaceControlClient watchFaceControlClient, java.util.concurrent.Executor executor);
+    ctor public ListenableWatchFaceControlClient(androidx.wear.watchface.client.WatchFaceControlClient watchFaceControlClient);
     method public void close();
     method public androidx.wear.watchface.client.HeadlessWatchFaceClient? createHeadlessWatchFaceClient(android.content.ComponentName watchFaceName, androidx.wear.watchface.client.DeviceConfig deviceConfig, int surfaceWidth, int surfaceHeight);
-    method public static final com.google.common.util.concurrent.ListenableFuture<androidx.wear.watchface.client.ListenableWatchFaceControlClient> createWatchFaceControlClient(android.content.Context context, String watchFacePackageName, java.util.concurrent.Executor executor);
+    method public static final com.google.common.util.concurrent.ListenableFuture<androidx.wear.watchface.client.ListenableWatchFaceControlClient> createWatchFaceControlClient(android.content.Context context, String watchFacePackageName);
     method public androidx.wear.watchface.client.EditorServiceClient getEditorServiceClient();
     method public androidx.wear.watchface.client.InteractiveWatchFaceClient? getInteractiveWatchFaceClientInstance(String instanceId);
     method public suspend Object? getOrCreateInteractiveWatchFaceClient(String p, androidx.wear.watchface.client.DeviceConfig p1, androidx.wear.watchface.client.WatchUiState p2, androidx.wear.watchface.style.UserStyleData? p3, java.util.Map<java.lang.Integer,? extends androidx.wear.complications.data.ComplicationData>? p4, kotlin.coroutines.Continuation<? super androidx.wear.watchface.client.InteractiveWatchFaceClient> $completion);
@@ -14,7 +14,7 @@
   }
 
   public static final class ListenableWatchFaceControlClient.Companion {
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.wear.watchface.client.ListenableWatchFaceControlClient> createWatchFaceControlClient(android.content.Context context, String watchFacePackageName, java.util.concurrent.Executor executor);
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.wear.watchface.client.ListenableWatchFaceControlClient> createWatchFaceControlClient(android.content.Context context, String watchFacePackageName);
   }
 
 }
diff --git a/wear/wear-watchface-client/guava/api/public_plus_experimental_current.txt b/wear/wear-watchface-client/guava/api/public_plus_experimental_current.txt
index eb19704..bf268ec 100644
--- a/wear/wear-watchface-client/guava/api/public_plus_experimental_current.txt
+++ b/wear/wear-watchface-client/guava/api/public_plus_experimental_current.txt
@@ -2,10 +2,10 @@
 package androidx.wear.watchface.client {
 
   public class ListenableWatchFaceControlClient implements androidx.wear.watchface.client.WatchFaceControlClient {
-    ctor public ListenableWatchFaceControlClient(androidx.wear.watchface.client.WatchFaceControlClient watchFaceControlClient, java.util.concurrent.Executor executor);
+    ctor public ListenableWatchFaceControlClient(androidx.wear.watchface.client.WatchFaceControlClient watchFaceControlClient);
     method public void close();
     method public androidx.wear.watchface.client.HeadlessWatchFaceClient? createHeadlessWatchFaceClient(android.content.ComponentName watchFaceName, androidx.wear.watchface.client.DeviceConfig deviceConfig, int surfaceWidth, int surfaceHeight);
-    method public static final com.google.common.util.concurrent.ListenableFuture<androidx.wear.watchface.client.ListenableWatchFaceControlClient> createWatchFaceControlClient(android.content.Context context, String watchFacePackageName, java.util.concurrent.Executor executor);
+    method public static final com.google.common.util.concurrent.ListenableFuture<androidx.wear.watchface.client.ListenableWatchFaceControlClient> createWatchFaceControlClient(android.content.Context context, String watchFacePackageName);
     method public androidx.wear.watchface.client.EditorServiceClient getEditorServiceClient();
     method public androidx.wear.watchface.client.InteractiveWatchFaceClient? getInteractiveWatchFaceClientInstance(String instanceId);
     method public suspend Object? getOrCreateInteractiveWatchFaceClient(String p, androidx.wear.watchface.client.DeviceConfig p1, androidx.wear.watchface.client.WatchUiState p2, androidx.wear.watchface.style.UserStyleData? p3, java.util.Map<java.lang.Integer,? extends androidx.wear.complications.data.ComplicationData>? p4, kotlin.coroutines.Continuation<? super androidx.wear.watchface.client.InteractiveWatchFaceClient> $completion);
@@ -14,7 +14,7 @@
   }
 
   public static final class ListenableWatchFaceControlClient.Companion {
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.wear.watchface.client.ListenableWatchFaceControlClient> createWatchFaceControlClient(android.content.Context context, String watchFacePackageName, java.util.concurrent.Executor executor);
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.wear.watchface.client.ListenableWatchFaceControlClient> createWatchFaceControlClient(android.content.Context context, String watchFacePackageName);
   }
 
 }
diff --git a/wear/wear-watchface-client/guava/api/restricted_current.txt b/wear/wear-watchface-client/guava/api/restricted_current.txt
index eb19704..bf268ec 100644
--- a/wear/wear-watchface-client/guava/api/restricted_current.txt
+++ b/wear/wear-watchface-client/guava/api/restricted_current.txt
@@ -2,10 +2,10 @@
 package androidx.wear.watchface.client {
 
   public class ListenableWatchFaceControlClient implements androidx.wear.watchface.client.WatchFaceControlClient {
-    ctor public ListenableWatchFaceControlClient(androidx.wear.watchface.client.WatchFaceControlClient watchFaceControlClient, java.util.concurrent.Executor executor);
+    ctor public ListenableWatchFaceControlClient(androidx.wear.watchface.client.WatchFaceControlClient watchFaceControlClient);
     method public void close();
     method public androidx.wear.watchface.client.HeadlessWatchFaceClient? createHeadlessWatchFaceClient(android.content.ComponentName watchFaceName, androidx.wear.watchface.client.DeviceConfig deviceConfig, int surfaceWidth, int surfaceHeight);
-    method public static final com.google.common.util.concurrent.ListenableFuture<androidx.wear.watchface.client.ListenableWatchFaceControlClient> createWatchFaceControlClient(android.content.Context context, String watchFacePackageName, java.util.concurrent.Executor executor);
+    method public static final com.google.common.util.concurrent.ListenableFuture<androidx.wear.watchface.client.ListenableWatchFaceControlClient> createWatchFaceControlClient(android.content.Context context, String watchFacePackageName);
     method public androidx.wear.watchface.client.EditorServiceClient getEditorServiceClient();
     method public androidx.wear.watchface.client.InteractiveWatchFaceClient? getInteractiveWatchFaceClientInstance(String instanceId);
     method public suspend Object? getOrCreateInteractiveWatchFaceClient(String p, androidx.wear.watchface.client.DeviceConfig p1, androidx.wear.watchface.client.WatchUiState p2, androidx.wear.watchface.style.UserStyleData? p3, java.util.Map<java.lang.Integer,? extends androidx.wear.complications.data.ComplicationData>? p4, kotlin.coroutines.Continuation<? super androidx.wear.watchface.client.InteractiveWatchFaceClient> $completion);
@@ -14,7 +14,7 @@
   }
 
   public static final class ListenableWatchFaceControlClient.Companion {
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.wear.watchface.client.ListenableWatchFaceControlClient> createWatchFaceControlClient(android.content.Context context, String watchFacePackageName, java.util.concurrent.Executor executor);
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.wear.watchface.client.ListenableWatchFaceControlClient> createWatchFaceControlClient(android.content.Context context, String watchFacePackageName);
   }
 
 }
diff --git a/wear/wear-watchface-client/guava/src/androidTest/java/androidx/wear/watchface/ListenableWatchFaceControlClientTest.kt b/wear/wear-watchface-client/guava/src/androidTest/java/androidx/wear/watchface/ListenableWatchFaceControlClientTest.kt
index e42d7ad..1a39e34 100644
--- a/wear/wear-watchface-client/guava/src/androidTest/java/androidx/wear/watchface/ListenableWatchFaceControlClientTest.kt
+++ b/wear/wear-watchface-client/guava/src/androidTest/java/androidx/wear/watchface/ListenableWatchFaceControlClientTest.kt
@@ -42,8 +42,6 @@
 import org.mockito.Mock
 import org.mockito.Mockito
 import org.mockito.MockitoAnnotations
-import java.util.concurrent.Executor
-import java.util.concurrent.Executors
 import java.util.concurrent.TimeUnit
 
 private const val TIMEOUT_MS = 500L
@@ -57,16 +55,12 @@
     @Mock
     private lateinit var surface: Surface
 
-    private lateinit var executor: Executor
-
     @Before
     public fun setUp() {
         MockitoAnnotations.initMocks(this)
         Mockito.`when`(surfaceHolder.surfaceFrame)
             .thenReturn(Rect(0, 0, 400, 400))
         Mockito.`when`(surfaceHolder.surface).thenReturn(surface)
-
-        executor = Executors.newSingleThreadExecutor()
     }
 
     @Test
@@ -74,8 +68,7 @@
         val context = ApplicationProvider.getApplicationContext<Context>()
         val client = ListenableWatchFaceControlClient.createWatchFaceControlClient(
             context,
-            context.packageName,
-            executor
+            context.packageName
         ).get(TIMEOUT_MS, TimeUnit.MILLISECONDS)
 
         val headlessInstance = client.createHeadlessWatchFaceClient(
@@ -107,8 +100,7 @@
         val context = ApplicationProvider.getApplicationContext<Context>()
         val client = ListenableWatchFaceControlClient.createWatchFaceControlClient(
             context,
-            context.packageName,
-            executor
+            context.packageName
         ).get(TIMEOUT_MS, TimeUnit.MILLISECONDS)
 
         assertNull(
@@ -132,8 +124,7 @@
         val context = ApplicationProvider.getApplicationContext<Context>()
         val client = ListenableWatchFaceControlClient.createWatchFaceControlClient(
             context,
-            context.packageName,
-            executor
+            context.packageName
         ).get(TIMEOUT_MS, TimeUnit.MILLISECONDS)
 
         val interactiveInstanceFuture =
@@ -180,8 +171,7 @@
         val context = ApplicationProvider.getApplicationContext<Context>()
         val client = ListenableWatchFaceControlClient.createWatchFaceControlClient(
             context,
-            context.packageName,
-            executor
+            context.packageName
         ).get(TIMEOUT_MS, TimeUnit.MILLISECONDS)
 
         val headlessInstance1 = client.createHeadlessWatchFaceClient(
@@ -231,8 +221,7 @@
         val context = ApplicationProvider.getApplicationContext<Context>()
         val client = ListenableWatchFaceControlClient.createWatchFaceControlClient(
             context,
-            context.packageName,
-            executor
+            context.packageName
         ).get(TIMEOUT_MS, TimeUnit.MILLISECONDS)
 
         val interactiveInstanceFuture =
@@ -284,8 +273,7 @@
         val context = ApplicationProvider.getApplicationContext<Context>()
         val client = ListenableWatchFaceControlClient.createWatchFaceControlClient(
             context,
-            context.packageName,
-            executor
+            context.packageName
         ).get(TIMEOUT_MS, TimeUnit.MILLISECONDS)
 
         assertNull(client.getInteractiveWatchFaceClientInstance("I do not exist"))
@@ -296,15 +284,13 @@
         val context = ApplicationProvider.getApplicationContext<Context>()
         ListenableWatchFaceControlClient.createWatchFaceControlClient(
             context,
-            context.packageName,
-            executor
+            context.packageName
         ).cancel(true)
 
         // Canceling should not prevent a subsequent createWatchFaceControlClient.
         val client = ListenableWatchFaceControlClient.createWatchFaceControlClient(
             context,
-            context.packageName,
-            executor
+            context.packageName
         ).get(TIMEOUT_MS, TimeUnit.MILLISECONDS)
         assertThat(client).isNotNull()
         client.close()
@@ -315,8 +301,7 @@
         val context = ApplicationProvider.getApplicationContext<Context>()
         val client = ListenableWatchFaceControlClient.createWatchFaceControlClient(
             context,
-            context.packageName,
-            executor
+            context.packageName
         ).get(TIMEOUT_MS, TimeUnit.MILLISECONDS)
 
         client.listenableGetOrCreateInteractiveWatchFaceClient(
diff --git a/wear/wear-watchface-client/guava/src/main/java/androidx/wear/watchface/client/ListenableWatchFaceControlClient.kt b/wear/wear-watchface-client/guava/src/main/java/androidx/wear/watchface/client/ListenableWatchFaceControlClient.kt
index ea686a2..f93f87d 100644
--- a/wear/wear-watchface-client/guava/src/main/java/androidx/wear/watchface/client/ListenableWatchFaceControlClient.kt
+++ b/wear/wear-watchface-client/guava/src/main/java/androidx/wear/watchface/client/ListenableWatchFaceControlClient.kt
@@ -25,19 +25,19 @@
 import androidx.wear.watchface.client.WatchFaceControlClient.ServiceNotBoundException
 import androidx.wear.watchface.style.UserStyleData
 import com.google.common.util.concurrent.ListenableFuture
+import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.asCoroutineDispatcher
+import kotlinx.coroutines.Runnable
 import kotlinx.coroutines.cancel
 import kotlinx.coroutines.launch
-import java.util.concurrent.Executor
+import kotlin.coroutines.CoroutineContext
 
 /**
  * [ListenableFuture]-based compatibility wrapper around [WatchFaceControlClient]'s suspending
  * methods. This class is open to allow mocking.
  */
 public open class ListenableWatchFaceControlClient(
-    private val watchFaceControlClient: WatchFaceControlClient,
-    private val executor: Executor
+    private val watchFaceControlClient: WatchFaceControlClient
 ) : WatchFaceControlClient {
     override fun getInteractiveWatchFaceClientInstance(
         instanceId: String
@@ -45,6 +45,13 @@
         watchFaceControlClient.getInteractiveWatchFaceClientInstance(instanceId)
 
     public companion object {
+        internal fun createImmediateCoroutineScope() = CoroutineScope(
+            object : CoroutineDispatcher() {
+                override fun dispatch(context: CoroutineContext, block: Runnable) {
+                    block.run()
+                }
+            }
+        )
 
         /**
          * Launches a coroutine with a new scope and returns a future that correctly handles
@@ -53,22 +60,21 @@
         // TODO(flerda): Move this to a location where it can be shared.
         internal fun <T> launchFutureCoroutine(
             traceTag: String,
-            scopeFactory: () -> CoroutineScope,
             block: suspend CoroutineScope.() -> T
         ): ListenableFuture<T> {
             val traceEvent = AsyncTraceEvent(traceTag)
             val future = ResolvableFuture.create<T>()
-            val coroutineScope = scopeFactory()
-            // Propagate future cancellation.
-            future.addListener(
-                {
-                    if (future.isCancelled) {
-                        coroutineScope.cancel()
-                    }
-                },
-                { runner -> runner.run() }
-            )
+            val coroutineScope = createImmediateCoroutineScope()
             coroutineScope.launch {
+                // Propagate future cancellation.
+                future.addListener(
+                    {
+                        if (future.isCancelled) {
+                            coroutineScope.cancel()
+                        }
+                    },
+                    { runner -> runner.run() }
+                )
                 try {
                     future.set(block())
                 } catch (e: Exception) {
@@ -86,30 +92,30 @@
          * Resolves as [ServiceNotBoundException] if the watch face control service can not
          * be bound.
          *
+         * Note the returned future may resolve immediately on the calling thread or it may resolve
+         * asynchronously when the service is connected on a background thread.
+         *
          * @param context Calling application's [Context].
-         * @param watchFacePackageName The name of the package containing the watch face control
+         * @param watchFacePackageName Name of the package containing the watch face control
          * service to bind to.
-         * @param executor The [Executor] on which any coroutine tasks are to run.
+         * @return [ListenableFuture]<[ListenableWatchFaceControlClient]> which on success resolves
+         * to a [ListenableWatchFaceControlClient] or throws a [ServiceNotBoundException] if the
+         * watch face control service can not be bound.
          */
         @SuppressLint("NewApi") // For ACTION_WATCHFACE_CONTROL_SERVICE
         @JvmStatic
         public fun createWatchFaceControlClient(
             context: Context,
-            watchFacePackageName: String,
-            executor: Executor
+            watchFacePackageName: String
         ): ListenableFuture<ListenableWatchFaceControlClient> =
             launchFutureCoroutine(
                 "ListenableWatchFaceControlClient.createWatchFaceControlClient",
-                {
-                    CoroutineScope(executor.asCoroutineDispatcher())
-                },
             ) {
                 ListenableWatchFaceControlClient(
                     WatchFaceControlClient.createWatchFaceControlClient(
                         context,
                         watchFacePackageName
-                    ),
-                    executor
+                    )
                 )
             }
     }
@@ -141,9 +147,6 @@
     ): ListenableFuture<InteractiveWatchFaceClient> =
         launchFutureCoroutine(
             "ListenableWatchFaceControlClient.listenableGetOrCreateInteractiveWatchFaceClient",
-            {
-                CoroutineScope(executor.asCoroutineDispatcher())
-            },
         ) {
             watchFaceControlClient.getOrCreateInteractiveWatchFaceClient(
                 id,
diff --git a/wear/wear-watchface-client/lint-baseline.xml b/wear/wear-watchface-client/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/wear/wear-watchface-client/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/wear/wear-watchface-client/src/androidTest/java/androidx/wear/watchface/client/test/SerializationTest.kt b/wear/wear-watchface-client/src/androidTest/java/androidx/wear/watchface/client/test/SerializationTest.kt
index 0ce3355..d9f8180 100644
--- a/wear/wear-watchface-client/src/androidTest/java/androidx/wear/watchface/client/test/SerializationTest.kt
+++ b/wear/wear-watchface-client/src/androidTest/java/androidx/wear/watchface/client/test/SerializationTest.kt
@@ -56,7 +56,7 @@
 
     private fun <T> loadGoldenResource(resourceId: Int, readFromParcel: (p: Parcel) -> T): T {
         val resource = context.resources.openRawResource(resourceId)
-        val bytes = Base64.getDecoder().decode(resource.readBytes())
+        val bytes = Base64.getDecoder().decode(String(resource.readBytes()))
         val p = Parcel.obtain()
         p.unmarshall(bytes, 0, bytes.size)
         p.setDataPosition(0)
@@ -236,4 +236,11 @@
         assertThat(complicationB.text.getTextAt(context.resources, 0)).isEqualTo("Example")
         assertThat(complicationB.title!!.getTextAt(context.resources, 0)).isEqualTo("complication")
     }
-}
\ No newline at end of file
+
+    @Test
+    public fun userStyleSchema() {
+        // TODO(b/187498135): Implement a golden test. This is harder than it sounds because the raw
+        // bytes of  a serialized Parcel is not portable and can differ between architectures even
+        // on the same API level.  The tests above possibly only pass on current bots by luck.
+    }
+}
diff --git a/wear/wear-watchface-complications-rendering/api/current.txt b/wear/wear-watchface-complications-rendering/api/current.txt
index 085dab4..2093fe8 100644
--- a/wear/wear-watchface-complications-rendering/api/current.txt
+++ b/wear/wear-watchface-complications-rendering/api/current.txt
@@ -20,15 +20,15 @@
 
   public final class ComplicationDrawable extends android.graphics.drawable.Drawable {
     ctor public ComplicationDrawable();
-    ctor public ComplicationDrawable(android.content.Context);
-    ctor public ComplicationDrawable(androidx.wear.watchface.complications.rendering.ComplicationDrawable);
-    method public void draw(android.graphics.Canvas);
+    ctor public ComplicationDrawable(android.content.Context context);
+    ctor public ComplicationDrawable(androidx.wear.watchface.complications.rendering.ComplicationDrawable drawable);
+    method public void draw(android.graphics.Canvas canvas);
     method public androidx.wear.watchface.complications.rendering.ComplicationStyle getActiveStyle();
     method public androidx.wear.watchface.complications.rendering.ComplicationStyle getAmbientStyle();
     method public androidx.wear.complications.data.ComplicationData? getComplicationData();
     method public android.content.Context? getContext();
     method public long getCurrentTimeMillis();
-    method public static androidx.wear.watchface.complications.rendering.ComplicationDrawable? getDrawable(android.content.Context, int);
+    method public static androidx.wear.watchface.complications.rendering.ComplicationDrawable? getDrawable(android.content.Context context, int id);
     method public long getHighlightDuration();
     method public CharSequence? getNoDataText();
     method @Deprecated public int getOpacity();
@@ -37,19 +37,36 @@
     method public boolean isInAmbientMode();
     method public boolean isLowBitAmbient();
     method public boolean isRangedValueProgressHidden();
-    method public boolean onTap(@Px int, @Px int);
-    method public void setAlpha(int);
-    method public void setBurnInProtection(boolean);
-    method public void setColorFilter(android.graphics.ColorFilter?);
-    method public void setComplicationData(androidx.wear.complications.data.ComplicationData?, boolean);
-    method public void setContext(android.content.Context);
-    method public void setCurrentTimeMillis(long);
-    method public void setHighlightDuration(@IntRange(from=0) long);
-    method public void setHighlighted(boolean);
-    method public void setInAmbientMode(boolean);
-    method public void setLowBitAmbient(boolean);
-    method public void setNoDataText(CharSequence?);
-    method public void setRangedValueProgressHidden(boolean);
+    method public boolean onTap(@Px int x, @Px int y);
+    method public void setAlpha(@IntRange(from=0, to=255) int alpha);
+    method public void setBurnInProtectionOn(boolean p);
+    method public void setColorFilter(android.graphics.ColorFilter? colorFilter);
+    method public void setComplicationData(androidx.wear.complications.data.ComplicationData? complicationData, boolean loadDrawablesAsync);
+    method public void setContext(android.content.Context context);
+    method public void setCurrentTimeMillis(long p);
+    method public void setHighlightDuration(@IntRange(from=0) long highlightDurationMillis);
+    method public void setHighlighted(boolean p);
+    method public void setInAmbientMode(boolean p);
+    method public void setLowBitAmbient(boolean p);
+    method public void setNoDataText(CharSequence? noDataText);
+    method public void setRangedValueProgressHidden(boolean rangedValueProgressHidden);
+    property public final androidx.wear.watchface.complications.rendering.ComplicationStyle activeStyle;
+    property public final androidx.wear.watchface.complications.rendering.ComplicationStyle ambientStyle;
+    property public final androidx.wear.complications.data.ComplicationData? complicationData;
+    property public final android.content.Context? context;
+    property public final long currentTimeMillis;
+    property public final long highlightDuration;
+    property public final boolean isBurnInProtectionOn;
+    property public final boolean isHighlighted;
+    property public final boolean isInAmbientMode;
+    property public final boolean isLowBitAmbient;
+    property public final boolean isRangedValueProgressHidden;
+    property public final CharSequence? noDataText;
+    field public static final androidx.wear.watchface.complications.rendering.ComplicationDrawable.Companion Companion;
+  }
+
+  public static final class ComplicationDrawable.Companion {
+    method public androidx.wear.watchface.complications.rendering.ComplicationDrawable? getDrawable(android.content.Context context, int id);
   }
 
   public final class ComplicationHighlightRenderer {
diff --git a/wear/wear-watchface-complications-rendering/api/public_plus_experimental_current.txt b/wear/wear-watchface-complications-rendering/api/public_plus_experimental_current.txt
index 085dab4..2093fe8 100644
--- a/wear/wear-watchface-complications-rendering/api/public_plus_experimental_current.txt
+++ b/wear/wear-watchface-complications-rendering/api/public_plus_experimental_current.txt
@@ -20,15 +20,15 @@
 
   public final class ComplicationDrawable extends android.graphics.drawable.Drawable {
     ctor public ComplicationDrawable();
-    ctor public ComplicationDrawable(android.content.Context);
-    ctor public ComplicationDrawable(androidx.wear.watchface.complications.rendering.ComplicationDrawable);
-    method public void draw(android.graphics.Canvas);
+    ctor public ComplicationDrawable(android.content.Context context);
+    ctor public ComplicationDrawable(androidx.wear.watchface.complications.rendering.ComplicationDrawable drawable);
+    method public void draw(android.graphics.Canvas canvas);
     method public androidx.wear.watchface.complications.rendering.ComplicationStyle getActiveStyle();
     method public androidx.wear.watchface.complications.rendering.ComplicationStyle getAmbientStyle();
     method public androidx.wear.complications.data.ComplicationData? getComplicationData();
     method public android.content.Context? getContext();
     method public long getCurrentTimeMillis();
-    method public static androidx.wear.watchface.complications.rendering.ComplicationDrawable? getDrawable(android.content.Context, int);
+    method public static androidx.wear.watchface.complications.rendering.ComplicationDrawable? getDrawable(android.content.Context context, int id);
     method public long getHighlightDuration();
     method public CharSequence? getNoDataText();
     method @Deprecated public int getOpacity();
@@ -37,19 +37,36 @@
     method public boolean isInAmbientMode();
     method public boolean isLowBitAmbient();
     method public boolean isRangedValueProgressHidden();
-    method public boolean onTap(@Px int, @Px int);
-    method public void setAlpha(int);
-    method public void setBurnInProtection(boolean);
-    method public void setColorFilter(android.graphics.ColorFilter?);
-    method public void setComplicationData(androidx.wear.complications.data.ComplicationData?, boolean);
-    method public void setContext(android.content.Context);
-    method public void setCurrentTimeMillis(long);
-    method public void setHighlightDuration(@IntRange(from=0) long);
-    method public void setHighlighted(boolean);
-    method public void setInAmbientMode(boolean);
-    method public void setLowBitAmbient(boolean);
-    method public void setNoDataText(CharSequence?);
-    method public void setRangedValueProgressHidden(boolean);
+    method public boolean onTap(@Px int x, @Px int y);
+    method public void setAlpha(@IntRange(from=0, to=255) int alpha);
+    method public void setBurnInProtectionOn(boolean p);
+    method public void setColorFilter(android.graphics.ColorFilter? colorFilter);
+    method public void setComplicationData(androidx.wear.complications.data.ComplicationData? complicationData, boolean loadDrawablesAsync);
+    method public void setContext(android.content.Context context);
+    method public void setCurrentTimeMillis(long p);
+    method public void setHighlightDuration(@IntRange(from=0) long highlightDurationMillis);
+    method public void setHighlighted(boolean p);
+    method public void setInAmbientMode(boolean p);
+    method public void setLowBitAmbient(boolean p);
+    method public void setNoDataText(CharSequence? noDataText);
+    method public void setRangedValueProgressHidden(boolean rangedValueProgressHidden);
+    property public final androidx.wear.watchface.complications.rendering.ComplicationStyle activeStyle;
+    property public final androidx.wear.watchface.complications.rendering.ComplicationStyle ambientStyle;
+    property public final androidx.wear.complications.data.ComplicationData? complicationData;
+    property public final android.content.Context? context;
+    property public final long currentTimeMillis;
+    property public final long highlightDuration;
+    property public final boolean isBurnInProtectionOn;
+    property public final boolean isHighlighted;
+    property public final boolean isInAmbientMode;
+    property public final boolean isLowBitAmbient;
+    property public final boolean isRangedValueProgressHidden;
+    property public final CharSequence? noDataText;
+    field public static final androidx.wear.watchface.complications.rendering.ComplicationDrawable.Companion Companion;
+  }
+
+  public static final class ComplicationDrawable.Companion {
+    method public androidx.wear.watchface.complications.rendering.ComplicationDrawable? getDrawable(android.content.Context context, int id);
   }
 
   public final class ComplicationHighlightRenderer {
diff --git a/wear/wear-watchface-complications-rendering/api/restricted_current.txt b/wear/wear-watchface-complications-rendering/api/restricted_current.txt
index 110cc54..8b9e974 100644
--- a/wear/wear-watchface-complications-rendering/api/restricted_current.txt
+++ b/wear/wear-watchface-complications-rendering/api/restricted_current.txt
@@ -20,15 +20,15 @@
 
   public final class ComplicationDrawable extends android.graphics.drawable.Drawable {
     ctor public ComplicationDrawable();
-    ctor public ComplicationDrawable(android.content.Context);
-    ctor public ComplicationDrawable(androidx.wear.watchface.complications.rendering.ComplicationDrawable);
-    method public void draw(android.graphics.Canvas);
+    ctor public ComplicationDrawable(android.content.Context context);
+    ctor public ComplicationDrawable(androidx.wear.watchface.complications.rendering.ComplicationDrawable drawable);
+    method public void draw(android.graphics.Canvas canvas);
     method public androidx.wear.watchface.complications.rendering.ComplicationStyle getActiveStyle();
     method public androidx.wear.watchface.complications.rendering.ComplicationStyle getAmbientStyle();
     method public androidx.wear.complications.data.ComplicationData? getComplicationData();
     method public android.content.Context? getContext();
     method public long getCurrentTimeMillis();
-    method public static androidx.wear.watchface.complications.rendering.ComplicationDrawable? getDrawable(android.content.Context, int);
+    method public static androidx.wear.watchface.complications.rendering.ComplicationDrawable? getDrawable(android.content.Context context, int id);
     method public long getHighlightDuration();
     method public CharSequence? getNoDataText();
     method @Deprecated public int getOpacity();
@@ -37,19 +37,36 @@
     method public boolean isInAmbientMode();
     method public boolean isLowBitAmbient();
     method public boolean isRangedValueProgressHidden();
-    method public boolean onTap(@Px int, @Px int);
-    method public void setAlpha(int);
-    method public void setBurnInProtection(boolean);
-    method public void setColorFilter(android.graphics.ColorFilter?);
-    method public void setComplicationData(androidx.wear.complications.data.ComplicationData?, boolean);
-    method public void setContext(android.content.Context);
-    method public void setCurrentTimeMillis(long);
-    method public void setHighlightDuration(@IntRange(from=0) long);
-    method public void setHighlighted(boolean);
-    method public void setInAmbientMode(boolean);
-    method public void setLowBitAmbient(boolean);
-    method public void setNoDataText(CharSequence?);
-    method public void setRangedValueProgressHidden(boolean);
+    method public boolean onTap(@Px int x, @Px int y);
+    method public void setAlpha(@IntRange(from=0, to=255) int alpha);
+    method public void setBurnInProtectionOn(boolean p);
+    method public void setColorFilter(android.graphics.ColorFilter? colorFilter);
+    method public void setComplicationData(androidx.wear.complications.data.ComplicationData? complicationData, boolean loadDrawablesAsync);
+    method public void setContext(android.content.Context context);
+    method public void setCurrentTimeMillis(long p);
+    method public void setHighlightDuration(@IntRange(from=0) long highlightDurationMillis);
+    method public void setHighlighted(boolean p);
+    method public void setInAmbientMode(boolean p);
+    method public void setLowBitAmbient(boolean p);
+    method public void setNoDataText(CharSequence? noDataText);
+    method public void setRangedValueProgressHidden(boolean rangedValueProgressHidden);
+    property public final androidx.wear.watchface.complications.rendering.ComplicationStyle activeStyle;
+    property public final androidx.wear.watchface.complications.rendering.ComplicationStyle ambientStyle;
+    property public final androidx.wear.complications.data.ComplicationData? complicationData;
+    property public final android.content.Context? context;
+    property public final long currentTimeMillis;
+    property public final long highlightDuration;
+    property public final boolean isBurnInProtectionOn;
+    property public final boolean isHighlighted;
+    property public final boolean isInAmbientMode;
+    property public final boolean isLowBitAmbient;
+    property public final boolean isRangedValueProgressHidden;
+    property public final CharSequence? noDataText;
+    field public static final androidx.wear.watchface.complications.rendering.ComplicationDrawable.Companion Companion;
+  }
+
+  public static final class ComplicationDrawable.Companion {
+    method public androidx.wear.watchface.complications.rendering.ComplicationDrawable? getDrawable(android.content.Context context, int id);
   }
 
   public final class ComplicationHighlightRenderer {
diff --git a/wear/wear-watchface-complications-rendering/lint-baseline.xml b/wear/wear-watchface-complications-rendering/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/wear/wear-watchface-complications-rendering/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/wear/wear-watchface-complications-rendering/src/main/java/androidx/wear/watchface/complications/rendering/CanvasComplicationDrawable.kt b/wear/wear-watchface-complications-rendering/src/main/java/androidx/wear/watchface/complications/rendering/CanvasComplicationDrawable.kt
index 05df52f..9222881 100644
--- a/wear/wear-watchface-complications-rendering/src/main/java/androidx/wear/watchface/complications/rendering/CanvasComplicationDrawable.kt
+++ b/wear/wear-watchface-complications-rendering/src/main/java/androidx/wear/watchface/complications/rendering/CanvasComplicationDrawable.kt
@@ -92,7 +92,7 @@
             field = value
             value.isInAmbientMode = watchState.isAmbient.value
             value.isLowBitAmbient = watchState.hasLowBitAmbient
-            value.setBurnInProtection(watchState.hasBurnInProtection)
+            value.isBurnInProtectionOn = watchState.hasBurnInProtection
 
             attachedComplication?.scheduleUpdateComplications()
         }
diff --git a/wear/wear-watchface-complications-rendering/src/main/java/androidx/wear/watchface/complications/rendering/ComplicationDrawable.java b/wear/wear-watchface-complications-rendering/src/main/java/androidx/wear/watchface/complications/rendering/ComplicationDrawable.java
deleted file mode 100644
index 55c1e16..0000000
--- a/wear/wear-watchface-complications-rendering/src/main/java/androidx/wear/watchface/complications/rendering/ComplicationDrawable.java
+++ /dev/null
@@ -1,870 +0,0 @@
-/*
- * Copyright 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.wear.watchface.complications.rendering;
-
-import android.app.PendingIntent.CanceledException;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.res.Resources;
-import android.content.res.Resources.Theme;
-import android.content.res.TypedArray;
-import android.graphics.Canvas;
-import android.graphics.ColorFilter;
-import android.graphics.PixelFormat;
-import android.graphics.Rect;
-import android.graphics.Typeface;
-import android.graphics.drawable.Drawable;
-import android.os.Handler;
-import android.os.Looper;
-import android.text.TextUtils;
-import android.util.AttributeSet;
-import android.util.Log;
-import android.util.Xml;
-
-import androidx.annotation.IntRange;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.annotation.Px;
-import androidx.annotation.VisibleForTesting;
-import androidx.wear.complications.ComplicationHelperActivity;
-import androidx.wear.complications.data.ComplicationData;
-import androidx.wear.complications.data.ComplicationType;
-import androidx.wear.complications.data.DataKt;
-import androidx.wear.watchface.complications.rendering.ComplicationRenderer.OnInvalidateListener;
-
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-
-import java.io.IOException;
-import java.util.Objects;
-
-/**
- * A styleable drawable object that draws complications. You can create a ComplicationDrawable from
- * XML inflation or by using one of the constructor methods.
- *
- * <h3>Constructing a ComplicationDrawable</h3>
- *
- * <p>To construct a ComplicationDrawable programmatically, use the {@link
- * #ComplicationDrawable(Context)} constructor. Afterwards, styling attributes you want to modify
- * can be set via set methods.
- *
- * <pre>
- * public void onCreate(SurfaceHolder holder) {
- *   ...
- *   ComplicationDrawable complicationDrawable = new ComplicationDrawable(WatchFaceService.this);
- *   complicationDrawable.setBackgroundColorActive(backgroundColor);
- *   complicationDrawable.setTextColorActive(textColor);
- *   ...
- * }</pre>
- *
- * <h3>Constructing a ComplicationDrawable from XML</h3>
- *
- * <p>Constructing a ComplicationDrawable from an XML file makes it easier to modify multiple
- * styling attributes at once without calling any set methods. You may also use different XML files
- * to switch between different styles your watch face supports.
- *
- * <p>To construct a ComplicationDrawable from a drawable XML file, you may create an XML file in
- * your project's {@code res/drawable} folder. A ComplicationDrawable with red text and white title
- * in active mode, and white text and white title in ambient mode would look like this:
- *
- * <pre>
- * &lt;?xml version="1.0" encoding="utf-8"?&gt;
- * &lt;android.support.wearable.complications.rendering.ComplicationDrawable
- *   xmlns:app="http://schemas.android.com/apk/res-auto"
- *   app:textColor="#FFFF0000"
- *   app:titleColor="#FFFFFFFF"&gt;
- *   &lt;ambient
- *     app:textColor="#FFFFFFFF" /&gt;
- * &lt;/android.support.wearable.complications.rendering.ComplicationDrawable&gt;
- * </pre>
- *
- * <p>A top-level {@code drawable} tag with the {@code class} attribute may also be used to
- * construct a ComplicationDrawable from an XML file:
- *
- * <pre>
- * &lt;?xml version="1.0" encoding="utf-8"?&gt;
- * &lt;drawable
- *   class="android.support.wearable.complications.rendering.ComplicationDrawable"
- *   xmlns:app="http://schemas.android.com/apk/res-auto"
- *   app:textColor="#FFFF0000"
- *   app:titleColor="#FFFFFFFF"&gt;
- *   &lt;ambient
- *     app:textColor="#FFFFFFFF" /&gt;
- * &lt;/drawable&gt;</pre>
- *
- * <p>To inflate a ComplicationDrawable from XML file, use the {@link #getDrawable(Context, int)}
- * method. ComplicationDrawable needs access to the current context in order to style and draw
- * the complication.
- *
- * <pre>
- * public void onCreate(SurfaceHolder holder) {
- *   ...
- *   ComplicationDrawable complicationDrawable = (ComplicationDrawable)
- *       getDrawable(R.drawable.complication);
- *   complicationDrawable.setContext(WatchFaceService.this);
- *   ...
- * }</pre>
- *
- * <h4>Syntax:</h4>
- *
- * <pre>
- * &lt;?xml version="1.0" encoding="utf-8"?&gt;
- * &lt;android.support.wearable.complications.rendering.ComplicationDrawable
- *   xmlns:app="http://schemas.android.com/apk/res-auto"
- *   app:backgroundColor="color"
- *   app:backgroundDrawable="drawable"
- *   app:borderColor="color"
- *   app:borderDashGap="dimension"
- *   app:borderDashWidth="dimension"
- *   app:borderRadius="dimension"
- *   app:borderStyle="none|solid|dashed"
- *   app:borderWidth="dimension"
- *   app:highlightColor="color"
- *   app:iconColor="color"
- *   app:rangedValuePrimaryColor="color"
- *   app:rangedValueProgressHidden="boolean"
- *   app:rangedValueRingWidth="dimension"
- *   app:rangedValueSecondaryColor="color"
- *   app:textColor="color"
- *   app:textSize="dimension"
- *   app:textTypeface="string"
- *   app:titleColor="color"
- *   app:titleSize="dimension"
- *   app:titleTypeface="string"&gt;
- *   &lt;ambient
- *     app:backgroundColor="color"
- *     app:backgroundDrawable="drawable"
- *     app:borderColor="color"
- *     app:borderDashGap="dimension"
- *     app:borderDashWidth="dimension"
- *     app:borderRadius="dimension"
- *     app:borderStyle="none|solid|dashed"
- *     app:borderWidth="dimension"
- *     app:highlightColor="color"
- *     app:iconColor="color"
- *     app:rangedValuePrimaryColor="color"
- *     app:rangedValueRingWidth="dimension"
- *     app:rangedValueSecondaryColor="color"
- *     app:textColor="color"
- *     app:textSize="dimension"
- *     app:textTypeface="string"
- *     app:titleColor="color"
- *     app:titleSize="dimension"
- *     app:titleTypeface="string" /&gt;
- * &lt;/android.support.wearable.complications.rendering.ComplicationDrawable&gt;
- * </pre>
- *
- * <p>Attributes of the top-level tag apply to both active and ambient modes while attributes of the
- * inner {@code ambient} tag only apply to ambient mode. As an exception, top-level only {@code
- * rangedValueProgressHidden} attribute applies to both modes, and cannot be overridden in ambient
- * mode. To hide ranged value in only one of the active or ambient modes, you may consider setting
- * {@code rangedValuePrimaryColor} and {@code rangedValueSecondaryColor} to {@link
- * android.graphics.Color#TRANSPARENT} instead.
- *
- * <h3>Drawing a ComplicationDrawable</h3>
- *
- * <p>Depending on the size and shape of the bounds, the layout of the complication may change. For
- * instance, a short text complication with an icon that is drawn on square bounds would draw the
- * icon above the short text, but a short text complication with an icon that is drawn on wide
- * rectangular bounds might draw the icon to the left of the short text instead.
- */
-public final class ComplicationDrawable extends Drawable {
-
-    private Context mContext;
-    private ComplicationRenderer mComplicationRenderer;
-
-    private final ComplicationStyle mActiveStyle;
-    private final ComplicationStyle mAmbientStyle;
-
-    private final Handler mMainThreadHandler = new Handler(Looper.getMainLooper());
-
-    private final Runnable mUnhighlightRunnable =
-            () -> {
-                setHighlighted(false);
-                invalidateSelf();
-            };
-
-    private final OnInvalidateListener mRendererInvalidateListener = () -> invalidateSelf();
-
-    private CharSequence mNoDataText;
-    private long mHighlightDuration;
-    private long mCurrentTimeMillis;
-    private boolean mInAmbientMode;
-    private boolean mLowBitAmbient;
-    private boolean mBurnInProtection;
-    private boolean mHighlighted;
-    private boolean mRangedValueProgressHidden;
-
-    private boolean mIsInflatedFromXml;
-    private boolean mAlreadyStyled;
-
-    /** Default constructor. */
-    public ComplicationDrawable() {
-        mActiveStyle = new ComplicationStyle();
-        mAmbientStyle = new ComplicationStyle();
-    }
-
-    /**
-     * Creates a ComplicationDrawable using the given context. If this constructor is used, calling
-     * {@link #setContext(Context)} may not be necessary.
-     */
-    public ComplicationDrawable(@NonNull Context context) {
-        this();
-        setContext(context);
-    }
-
-    public ComplicationDrawable(@NonNull ComplicationDrawable drawable) {
-        mActiveStyle = new ComplicationStyle(drawable.mActiveStyle);
-        mAmbientStyle = new ComplicationStyle(drawable.mAmbientStyle);
-        mNoDataText = drawable.mNoDataText.subSequence(0, drawable.mNoDataText.length());
-        mHighlightDuration = drawable.mHighlightDuration;
-        mCurrentTimeMillis = drawable.mCurrentTimeMillis;
-        setBounds(drawable.getBounds());
-
-        mInAmbientMode = drawable.mInAmbientMode;
-        mLowBitAmbient = drawable.mLowBitAmbient;
-        mBurnInProtection = drawable.mBurnInProtection;
-        mHighlighted = false;
-        mRangedValueProgressHidden = drawable.mRangedValueProgressHidden;
-        mIsInflatedFromXml = drawable.mIsInflatedFromXml;
-        mAlreadyStyled = true;
-    }
-
-    /**
-     * Creates a ComplicationDrawable from a resource.
-     *
-     * @param context The {@link Context} to load the resource from
-     * @param id      The id of the resource to load
-     * @return The {@link ComplicationDrawable} loaded from the specified resource id or null if it
-     * doesn't exist.
-     */
-    @Nullable
-    public static ComplicationDrawable getDrawable(@NonNull Context context, int id) {
-        if (context == null) {
-            throw new IllegalArgumentException("Argument \"context\" should not be null.");
-        }
-        ComplicationDrawable drawable = (ComplicationDrawable) context.getDrawable(id);
-        if (drawable == null) {
-            return null;
-        }
-
-        drawable.setContext(context);
-        return drawable;
-    }
-
-    /** Sets the style to default values using resources. */
-    private static void setStyleToDefaultValues(ComplicationStyle style, Resources r) {
-        style.setBackgroundColor(
-                r.getColor(R.color.complicationDrawable_backgroundColor, null));
-        style.setTextColor(r.getColor(R.color.complicationDrawable_textColor, null));
-        style.setTitleColor(r.getColor(R.color.complicationDrawable_titleColor, null));
-        style.setTextTypeface(
-                Typeface.create(
-                        r.getString(R.string.complicationDrawable_textTypeface), Typeface.NORMAL));
-        style.setTitleTypeface(
-                Typeface.create(
-                        r.getString(R.string.complicationDrawable_titleTypeface), Typeface.NORMAL));
-        style.setTextSize(r.getDimensionPixelSize(R.dimen.complicationDrawable_textSize));
-        style.setTitleSize(r.getDimensionPixelSize(R.dimen.complicationDrawable_titleSize));
-        style.setIconColor(r.getColor(R.color.complicationDrawable_iconColor, null));
-        style.setBorderColor(r.getColor(R.color.complicationDrawable_borderColor, null));
-        style.setBorderWidth(r.getDimensionPixelSize(R.dimen.complicationDrawable_borderWidth));
-        style.setBorderRadius(r.getDimensionPixelSize(R.dimen.complicationDrawable_borderRadius));
-        style.setBorderStyle(r.getInteger(R.integer.complicationDrawable_borderStyle));
-        style.setBorderDashWidth(
-                r.getDimensionPixelSize(R.dimen.complicationDrawable_borderDashWidth));
-        style.setBorderDashGap(r.getDimensionPixelSize(R.dimen.complicationDrawable_borderDashGap));
-        style.setRangedValueRingWidth(
-                r.getDimensionPixelSize(R.dimen.complicationDrawable_rangedValueRingWidth));
-        style.setRangedValuePrimaryColor(
-                r.getColor(R.color.complicationDrawable_rangedValuePrimaryColor, null));
-        style.setRangedValueSecondaryColor(
-                r.getColor(R.color.complicationDrawable_rangedValueSecondaryColor, null));
-        style.setHighlightColor(r.getColor(R.color.complicationDrawable_highlightColor, null));
-    }
-
-    /**
-     * Sets the context used to render the complication. If a context is not set,
-     * ComplicationDrawable will throw an {@link IllegalStateException} if one of
-     * {@link #draw(Canvas)}, {@link #setBounds(Rect)}, or {@link
-     * #setComplicationData(ComplicationData, boolean)} is called.
-     *
-     * <p>While this can be called from any context, ideally, a
-     * androidx.wear.watchface.WatchFaceService object should be passed here to allow creating
-     * permission dialogs by the {@link #onTap(int, int)} method, in case current watch face
-     * doesn't have the permission to receive complication data.
-     *
-     * <p>If this ComplicationDrawable is retrieved using {@link Resources#getDrawable(int, Theme)},
-     * this method must be called before calling any of the methods mentioned above.
-     *
-     * <p>If this ComplicationDrawable is not inflated from an XML file, this method will reset the
-     * style to match the default values, so if {@link #ComplicationDrawable()} is used to construct
-     * a ComplicationDrawable, this method should be called right after.
-     */
-    public void setContext(@NonNull Context context) {
-        if (context == null) {
-            throw new IllegalArgumentException("Argument \"context\" should not be null.");
-        }
-        if (Objects.equals(context, mContext)) {
-            return;
-        }
-        mContext = context;
-
-        if (!mIsInflatedFromXml && !mAlreadyStyled) {
-            setStyleToDefaultValues(mActiveStyle, context.getResources());
-            setStyleToDefaultValues(mAmbientStyle, context.getResources());
-        }
-
-        if (!mAlreadyStyled) {
-            mHighlightDuration = context.getResources()
-                    .getInteger(R.integer.complicationDrawable_highlightDurationMs);
-        }
-
-        mComplicationRenderer = new ComplicationRenderer(mContext, mActiveStyle, mAmbientStyle);
-        mComplicationRenderer.setOnInvalidateListener(mRendererInvalidateListener);
-        if (mNoDataText == null) {
-            setNoDataText(context.getString(R.string.complicationDrawable_noDataText));
-        } else {
-            mComplicationRenderer.setNoDataText(mNoDataText);
-        }
-        mComplicationRenderer.setRangedValueProgressHidden(mRangedValueProgressHidden);
-        mComplicationRenderer.setBounds(getBounds());
-    }
-
-    /**
-     * Returns the {@link Context} used to render the complication.
-     */
-    @Nullable public Context getContext() {
-        return mContext;
-    }
-
-    private void inflateAttributes(Resources r, XmlPullParser parser) {
-        TypedArray a =
-                r.obtainAttributes(Xml.asAttributeSet(parser), R.styleable.ComplicationDrawable);
-        setRangedValueProgressHidden(
-                a.getBoolean(R.styleable.ComplicationDrawable_rangedValueProgressHidden, false));
-        a.recycle();
-    }
-
-    private void inflateStyle(boolean isAmbient, Resources r, XmlPullParser parser) {
-        TypedArray a =
-                r.obtainAttributes(Xml.asAttributeSet(parser), R.styleable.ComplicationDrawable);
-        ComplicationStyle complicationStyle = isAmbient ? mAmbientStyle : mActiveStyle;
-        if (a.hasValue(R.styleable.ComplicationDrawable_backgroundColor)) {
-            complicationStyle.setBackgroundColor(
-                    a.getColor(
-                            R.styleable.ComplicationDrawable_backgroundColor,
-                            r.getColor(R.color.complicationDrawable_backgroundColor, null)));
-        }
-        if (a.hasValue(R.styleable.ComplicationDrawable_backgroundDrawable)) {
-            complicationStyle.setBackgroundDrawable(
-                    a.getDrawable(R.styleable.ComplicationDrawable_backgroundDrawable));
-        }
-        if (a.hasValue(R.styleable.ComplicationDrawable_textColor)) {
-            complicationStyle.setTextColor(
-                    a.getColor(
-                            R.styleable.ComplicationDrawable_textColor,
-                            r.getColor(R.color.complicationDrawable_textColor, null)));
-        }
-        if (a.hasValue(R.styleable.ComplicationDrawable_titleColor)) {
-            complicationStyle.setTitleColor(
-                    a.getColor(
-                            R.styleable.ComplicationDrawable_titleColor,
-                            r.getColor(R.color.complicationDrawable_titleColor, null)));
-        }
-        if (a.hasValue(R.styleable.ComplicationDrawable_textTypeface)) {
-            complicationStyle.setTextTypeface(
-                    Typeface.create(
-                            a.getString(R.styleable.ComplicationDrawable_textTypeface),
-                            Typeface.NORMAL));
-        }
-        if (a.hasValue(R.styleable.ComplicationDrawable_titleTypeface)) {
-            complicationStyle.setTitleTypeface(
-                    Typeface.create(
-                            a.getString(R.styleable.ComplicationDrawable_titleTypeface),
-                            Typeface.NORMAL));
-        }
-        if (a.hasValue(R.styleable.ComplicationDrawable_textSize)) {
-            complicationStyle.setTextSize(
-                    a.getDimensionPixelSize(
-                            R.styleable.ComplicationDrawable_textSize,
-                            r.getDimensionPixelSize(R.dimen.complicationDrawable_textSize)));
-        }
-        if (a.hasValue(R.styleable.ComplicationDrawable_titleSize)) {
-            complicationStyle.setTitleSize(
-                    a.getDimensionPixelSize(
-                            R.styleable.ComplicationDrawable_titleSize,
-                            r.getDimensionPixelSize(R.dimen.complicationDrawable_titleSize)));
-        }
-        if (a.hasValue(R.styleable.ComplicationDrawable_iconColor)) {
-            complicationStyle.setIconColor(
-                    a.getColor(
-                            R.styleable.ComplicationDrawable_iconColor,
-                            r.getColor(R.color.complicationDrawable_iconColor, null)));
-        }
-        if (a.hasValue(R.styleable.ComplicationDrawable_borderColor)) {
-            complicationStyle.setBorderColor(
-                    a.getColor(
-                            R.styleable.ComplicationDrawable_borderColor,
-                            r.getColor(R.color.complicationDrawable_borderColor, null)));
-        }
-        if (a.hasValue(R.styleable.ComplicationDrawable_borderRadius)) {
-            complicationStyle.setBorderRadius(
-                    a.getDimensionPixelSize(
-                            R.styleable.ComplicationDrawable_borderRadius,
-                            r.getDimensionPixelSize(R.dimen.complicationDrawable_borderRadius)));
-        }
-        if (a.hasValue(R.styleable.ComplicationDrawable_borderStyle)) {
-            complicationStyle.setBorderStyle(
-                    a.getInt(
-                            R.styleable.ComplicationDrawable_borderStyle,
-                            r.getInteger(R.integer.complicationDrawable_borderStyle)));
-        }
-        if (a.hasValue(R.styleable.ComplicationDrawable_borderDashWidth)) {
-            complicationStyle.setBorderDashWidth(
-                    a.getDimensionPixelSize(
-                            R.styleable.ComplicationDrawable_borderDashWidth,
-                            r.getDimensionPixelSize(R.dimen.complicationDrawable_borderDashWidth)));
-        }
-        if (a.hasValue(R.styleable.ComplicationDrawable_borderDashGap)) {
-            complicationStyle.setBorderDashGap(
-                    a.getDimensionPixelSize(
-                            R.styleable.ComplicationDrawable_borderDashGap,
-                            r.getDimensionPixelSize(R.dimen.complicationDrawable_borderDashGap)));
-        }
-        if (a.hasValue(R.styleable.ComplicationDrawable_borderWidth)) {
-            complicationStyle.setBorderWidth(
-                    a.getDimensionPixelSize(
-                            R.styleable.ComplicationDrawable_borderWidth,
-                            r.getDimensionPixelSize(R.dimen.complicationDrawable_borderWidth)));
-        }
-        if (a.hasValue(R.styleable.ComplicationDrawable_rangedValueRingWidth)) {
-            complicationStyle.setRangedValueRingWidth(
-                    a.getDimensionPixelSize(
-                            R.styleable.ComplicationDrawable_rangedValueRingWidth,
-                            r.getDimensionPixelSize(
-                                    R.dimen.complicationDrawable_rangedValueRingWidth)));
-        }
-        if (a.hasValue(R.styleable.ComplicationDrawable_rangedValuePrimaryColor)) {
-            complicationStyle.setRangedValuePrimaryColor(
-                    a.getColor(
-                            R.styleable.ComplicationDrawable_rangedValuePrimaryColor,
-                            r.getColor(
-                                    R.color.complicationDrawable_rangedValuePrimaryColor, null)));
-        }
-        if (a.hasValue(R.styleable.ComplicationDrawable_rangedValueSecondaryColor)) {
-            complicationStyle.setRangedValueSecondaryColor(
-                    a.getColor(
-                            R.styleable.ComplicationDrawable_rangedValueSecondaryColor,
-                            r.getColor(
-                                    R.color.complicationDrawable_rangedValueSecondaryColor, null)));
-        }
-        if (a.hasValue(R.styleable.ComplicationDrawable_highlightColor)) {
-            complicationStyle.setHighlightColor(
-                    a.getColor(
-                            R.styleable.ComplicationDrawable_highlightColor,
-                            r.getColor(R.color.complicationDrawable_highlightColor, null)));
-        }
-        a.recycle();
-    }
-
-    /**
-     * Inflates this ComplicationDrawable from an XML resource. This can't be called more than once
-     * for each ComplicationDrawable. Note that framework may have called this once to create the
-     * ComplicationDrawable instance from an XML resource.
-     *
-     * @param r      Resources used to resolve attribute values
-     * @param parser XML parser from which to inflate this ComplicationDrawable
-     * @param attrs  Base set of attribute values
-     * @param theme  Ignored by ComplicationDrawable
-     */
-    @Override
-    @SuppressWarnings("ObjectToString")
-    public void inflate(@NonNull Resources r, @NonNull XmlPullParser parser,
-            @NonNull AttributeSet attrs, @Nullable Theme theme)
-            throws XmlPullParserException, IOException {
-        if (mIsInflatedFromXml) {
-            throw new IllegalStateException("inflate may be called once only.");
-        }
-        mIsInflatedFromXml = true;
-        int type;
-        final int outerDepth = parser.getDepth();
-        // Inflate attributes always shared between active and ambient mode
-        inflateAttributes(r, parser);
-        // Reset both style builders to default values
-        setStyleToDefaultValues(mActiveStyle, r);
-        setStyleToDefaultValues(mAmbientStyle, r);
-        // Attributes of the outer tag applies to both active and ambient styles
-        inflateStyle(false, r, parser);
-        inflateStyle(true, r, parser);
-        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
-                && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
-            if (type != XmlPullParser.START_TAG) {
-                continue;
-            }
-
-            // Attributes of inner <ambient> tag applies to ambient style only
-            final String name = parser.getName();
-            if (TextUtils.equals(name, "ambient")) {
-                inflateStyle(true, r, parser);
-            } else {
-                Log.w(
-                        "ComplicationDrawable",
-                        "Unknown element: " + name + " for ComplicationDrawable " + this);
-            }
-        }
-    }
-
-    /**
-     * Draws the complication for the last known time. Last known time is derived from
-     * ComplicationDrawable#setCurrentTimeMillis(long)}.
-     *
-     * @param canvas Canvas for the complication to be drawn onto
-     */
-    @Override
-    public void draw(@NonNull Canvas canvas) {
-        assertInitialized();
-        updateStyleIfRequired();
-        mComplicationRenderer.draw(
-                canvas,
-                mCurrentTimeMillis,
-                mInAmbientMode,
-                mLowBitAmbient,
-                mBurnInProtection,
-                mHighlighted);
-    }
-
-    /** Does nothing. */
-    @Override
-    public void setAlpha(int alpha) {
-        // No op.
-    }
-
-    // TODO(b/186499115): In follow up patch, when this class is converted to Kotlin, make link
-    //  working.
-    /**
-     * Does nothing. Use {link ComplicationStyle#setImageColorFilter(ColorFilter)} instead to apply
-     * color filter to small and large images.
-     */
-    @Override
-    public void setColorFilter(@Nullable ColorFilter colorFilter) {
-        // No op.
-    }
-
-    /**
-     * {@inheritDoc}
-     *
-     * @deprecated This method is no longer used in graphics optimizations
-     */
-    @Override
-    @Deprecated
-    public int getOpacity() {
-        return PixelFormat.TRANSLUCENT;
-    }
-
-    @Override
-    protected void onBoundsChange(@NonNull Rect bounds) {
-        if (mComplicationRenderer != null) {
-            mComplicationRenderer.setBounds(bounds);
-        }
-    }
-
-    /**
-     * Sets the text to be rendered when {@link ComplicationData} is of type {@link
-     * ComplicationType#NO_DATA}. If {@code noDataText} is null, an empty text will be
-     * rendered.
-     */
-    public void setNoDataText(@Nullable CharSequence noDataText) {
-        if (noDataText == null) {
-            mNoDataText = "";
-        } else {
-            mNoDataText = noDataText.subSequence(0, noDataText.length());
-        }
-        if (mComplicationRenderer != null) {
-            mComplicationRenderer.setNoDataText(mNoDataText);
-        }
-    }
-
-    /**
-     * Sets if the ranged value progress should be hidden when {@link ComplicationData} is of type
-     * {@link ComplicationType#RANGED_VALUE}.
-     *
-     * @param rangedValueProgressHidden {@code true} if progress should be hidden, {@code false}
-     *                                  otherwise
-     * @attr ref androidx.wear.watchface.complications.rendering.R
-     *     .styleable#ComplicationDrawable_rangedValueProgressHidden
-     */
-    public void setRangedValueProgressHidden(boolean rangedValueProgressHidden) {
-        mRangedValueProgressHidden = rangedValueProgressHidden;
-        if (mComplicationRenderer != null) {
-            mComplicationRenderer.setRangedValueProgressHidden(rangedValueProgressHidden);
-        }
-    }
-
-    /** Returns {@code true} if the ranged value progress is hidden, {@code false} otherwise. */
-    public boolean isRangedValueProgressHidden() {
-        return mRangedValueProgressHidden;
-    }
-
-    /**
-     * Sets the complication data to be drawn. If {@code complicationData} is {@code null}, nothing
-     * will be drawn when {@link #draw(Canvas)} is called.
-     *
-     * @param complicationData The [ComplicationData] to set
-     * @param loadDrawablesAsync If true any drawables should be loaded asynchronously,
-     *      otherwise they will be loaded synchronously.
-     */
-    public void setComplicationData(
-            @Nullable ComplicationData complicationData,
-            boolean loadDrawablesAsync
-    ) {
-        assertInitialized();
-        mComplicationRenderer.setComplicationData(
-                complicationData != null ? complicationData.asWireComplicationData() : null,
-                loadDrawablesAsync
-        );
-    }
-
-    /**
-     * Returns the {@link ComplicationData} to be drawn by this ComplicationDrawable.
-     */
-    @Nullable
-    public ComplicationData getComplicationData() {
-        return (mComplicationRenderer.getComplicationData() != null)
-                ? DataKt.toApiComplicationData(mComplicationRenderer.getComplicationData()) : null;
-    }
-
-    /** Sets whether the complication should be rendered in ambient mode. */
-    public void setInAmbientMode(boolean inAmbientMode) {
-        mInAmbientMode = inAmbientMode;
-    }
-
-    /** Returns whether the complication is rendered in ambient mode. */
-    public boolean isInAmbientMode() {
-        return mInAmbientMode;
-    }
-
-    /**
-     * Sets whether the complication, when rendering in ambient mode, should apply a style suitable
-     * for low bit ambient mode.
-     */
-    public void setLowBitAmbient(boolean lowBitAmbient) {
-        mLowBitAmbient = lowBitAmbient;
-    }
-
-    /**
-     * Returns whether the complication, when rendering in ambient mode, should apply a style
-     * suitable for low bit ambient mode.
-     */
-    public boolean isLowBitAmbient() {
-        return mLowBitAmbient;
-    }
-
-    /**
-     * Sets whether the complication, when rendering in ambient mode, should apply a style suitable
-     * for display on devices with burn in protection.
-     */
-    public void setBurnInProtection(boolean burnInProtection) {
-        mBurnInProtection = burnInProtection;
-    }
-
-    /**
-     * Whether the complication, when rendering in ambient mode, should apply a style suitable for
-     * display on devices with burn in protection.
-     */
-    public boolean isBurnInProtectionOn() {
-        return mBurnInProtection;
-    }
-
-    /**
-     * Sets the current time in mulliseconds since the epoch. This will be used to render
-     * {@link ComplicationData} with time dependent text.
-     *
-     * @param currentTimeMillis time in milliseconds since the epoch
-     */
-    public void setCurrentTimeMillis(long currentTimeMillis) {
-        mCurrentTimeMillis = currentTimeMillis;
-    }
-
-    /**
-     * Returns the time in milliseconds since the epoch used for rendering {@link ComplicationData}
-     * with time dependent text.
-     */
-    public long getCurrentTimeMillis() {
-        return mCurrentTimeMillis;
-    }
-
-    /**
-     * Sets whether the complication is currently highlighted. This may be called by a watch face
-     * when a complication is tapped.
-     *
-     * <p>If watch face is in ambient mode, highlight will not be visible even if this is set to
-     * {@code true}, because it may cause burn-in or power inefficiency.
-     */
-    public void setHighlighted(boolean isHighlighted) {
-        mHighlighted = isHighlighted;
-    }
-
-    /**
-     * Returns whether the complication is currently highlighted.
-     */
-    public boolean isHighlighted() {
-        return mHighlighted;
-    }
-
-    /**
-     * Sends the tap action for the complication if tap coordinates are inside the complication
-     * bounds.
-     *
-     * <p>This method will also highlight the complication. The highlight duration is 300
-     * milliseconds by default but can be modified using the {@link #setHighlightDuration(long)}
-     * method.
-     *
-     * <p>If {@link ComplicationData} has the type {@link ComplicationType#NO_PERMISSION}, this
-     * method will launch an intent to request complication permission for the watch face. This will
-     * only work if the context set by {@link #getDrawable} or the constructor is an
-     * instance of WatchFaceService.
-     *
-     * @param x X coordinate of the tap relative to screen origin
-     * @param y Y coordinate of the tap relative to screen origin
-     * @return {@code true} if the action was successful, {@code false} if complication data is not
-     * set, the complication has no tap action, the tap action (i.e. {@link
-     * android.app.PendingIntent}) is cancelled, or the given x and y are not inside the
-     * complication bounds.
-     */
-    public boolean onTap(@Px int x, @Px int y) {
-        if (mComplicationRenderer == null) {
-            return false;
-        }
-        android.support.wearable.complications.ComplicationData data =
-                mComplicationRenderer.getComplicationData();
-        if (data == null) {
-            return false;
-        }
-        if (!data.hasTapAction() && data.getType()
-                != android.support.wearable.complications.ComplicationData.TYPE_NO_PERMISSION) {
-            return false;
-        }
-        if (!getBounds().contains(x, y)) {
-            return false;
-        }
-        if (data.getType()
-                == android.support.wearable.complications.ComplicationData.TYPE_NO_PERMISSION) {
-            // Check if mContext is an instance of WatchFaceService. We can't use the standard
-            // instanceof operator because WatchFaceService is defined in library which depends on
-            // this one, hence the reflection hack.
-            try {
-                if (Class.forName("androidx.wear.watchface.WatchFaceService")
-                        .isInstance(mContext)) {
-                    mContext.startActivity(
-                            ComplicationHelperActivity.createPermissionRequestHelperIntent(
-                                    mContext,
-                                    new ComponentName(mContext, mContext.getClass()))
-                                    .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
-                } else {
-                    return false;
-                }
-            } catch (ClassNotFoundException e) {
-                // If watchFaceServiceClass class isn't found we know mContext can't be an instance
-                // of WatchFaceService.
-                return false;
-            }
-        } else {
-            try {
-                data.getTapAction().send();
-            } catch (CanceledException e) {
-                return false;
-            }
-        }
-        if (getHighlightDuration() > 0) {
-            setHighlighted(true);
-            invalidateSelf();
-            mMainThreadHandler.removeCallbacks(mUnhighlightRunnable);
-            mMainThreadHandler.postDelayed(mUnhighlightRunnable, getHighlightDuration());
-        }
-        return true;
-    }
-
-    /**
-     * Sets the duration for the complication to stay highlighted after calling the {@link
-     * #onTap(int, int)} method. Default value is 300 milliseconds. Setting highlight duration to 0
-     * disables highlighting.
-     *
-     * @param highlightDurationMillis highlight duration in milliseconds
-     */
-    public void setHighlightDuration(@IntRange(from = 0) long highlightDurationMillis) {
-        if (highlightDurationMillis < 0) {
-            throw new IllegalArgumentException("Highlight duration should be non-negative.");
-        }
-        mHighlightDuration = highlightDurationMillis;
-    }
-
-    /** Returns the highlight duration. */
-    public long getHighlightDuration() {
-        return mHighlightDuration;
-    }
-
-    /** Builds styles and syncs them with the complication renderer. */
-    void updateStyleIfRequired() {
-        if (mActiveStyle.isDirty() || mAmbientStyle.isDirty()) {
-            mComplicationRenderer.updateStyle(mActiveStyle, mAmbientStyle);
-            mActiveStyle.clearDirtyFlag();
-            mAmbientStyle.clearDirtyFlag();
-        }
-    }
-
-    /**
-     * Throws an exception if the context is not set. This method should be called if any of the
-     * member methods do a context-dependent job.
-     */
-    private void assertInitialized() {
-        if (mContext == null) {
-            throw new IllegalStateException(
-                    "ComplicationDrawable does not have a context. Use setContext(Context) to set"
-                            + " it first.");
-        }
-    }
-
-    /** Returns complication style for active mode. */
-    @NonNull
-    public ComplicationStyle getActiveStyle() {
-        return mActiveStyle;
-    }
-
-    /** Returns complication style for ambient mode. */
-    @NonNull
-    public ComplicationStyle getAmbientStyle() {
-        return mAmbientStyle;
-    }
-
-    /** Returns complication renderer. */
-    @Nullable
-    @VisibleForTesting(otherwise = VisibleForTesting.NONE)
-    public ComplicationRenderer getComplicationRenderer() {
-        return mComplicationRenderer;
-    }
-
-    /**
-     * Returns the text to be rendered when {@link ComplicationData} is of type {@link
-     * ComplicationType#NO_DATA}.
-     */
-    @Nullable
-    public CharSequence getNoDataText() {
-        return mNoDataText;
-    }
-}
diff --git a/wear/wear-watchface-complications-rendering/src/main/java/androidx/wear/watchface/complications/rendering/ComplicationDrawable.kt b/wear/wear-watchface-complications-rendering/src/main/java/androidx/wear/watchface/complications/rendering/ComplicationDrawable.kt
new file mode 100644
index 0000000..0639d79
--- /dev/null
+++ b/wear/wear-watchface-complications-rendering/src/main/java/androidx/wear/watchface/complications/rendering/ComplicationDrawable.kt
@@ -0,0 +1,751 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package androidx.wear.watchface.complications.rendering
+
+import android.app.PendingIntent
+import android.content.ComponentName
+import android.content.Context
+import android.content.Intent
+import android.content.res.Resources
+import android.graphics.Canvas
+import android.graphics.ColorFilter
+import android.graphics.Rect
+import android.graphics.Typeface
+import android.graphics.drawable.Drawable
+import android.os.Handler
+import android.os.Looper
+import android.text.TextUtils
+import android.util.AttributeSet
+import android.util.Log
+import android.util.Xml
+import androidx.annotation.IntRange
+import androidx.annotation.Px
+import androidx.annotation.VisibleForTesting
+import androidx.wear.complications.ComplicationHelperActivity
+import androidx.wear.complications.data.ComplicationData
+import androidx.wear.complications.data.ComplicationType.NO_DATA
+import androidx.wear.complications.data.ComplicationType.NO_PERMISSION
+import androidx.wear.complications.data.ComplicationType.RANGED_VALUE
+import androidx.wear.complications.data.toApiComplicationData
+import androidx.wear.watchface.complications.rendering.ComplicationRenderer.OnInvalidateListener
+import org.xmlpull.v1.XmlPullParser
+import org.xmlpull.v1.XmlPullParserException
+import java.io.IOException
+
+/**
+ * A styleable drawable object that draws complications. You can create a ComplicationDrawable from
+ * XML inflation or by using one of the constructor methods.
+ *
+ * <h3>Constructing a ComplicationDrawable</h3>
+ *
+ * To construct a ComplicationDrawable programmatically, use the [ComplicationDrawable]
+ * constructor. Afterwards, styling attributes you want to modify
+ * can be set via set methods.
+ *
+ * ```
+ * public void onCreate(SurfaceHolder holder) {
+ * ...
+ * ComplicationDrawable complicationDrawable = new ComplicationDrawable(WatchFaceService.this);
+ * complicationDrawable.setBackgroundColorActive(backgroundColor);
+ * complicationDrawable.setTextColorActive(textColor);
+ * ...
+ * }
+ * ```
+ *
+ * <h3>Constructing a ComplicationDrawable from XML</h3>
+ *
+ * Constructing a ComplicationDrawable from an XML file makes it easier to modify multiple
+ * styling attributes at once without calling any set methods. You may also use different XML files
+ * to switch between different styles your watch face supports.
+ *
+ *
+ * To construct a ComplicationDrawable from a drawable XML file, you may create an XML file in
+ * your project's `res/drawable` folder. A ComplicationDrawable with red text and white title
+ * in active mode, and white text and white title in ambient mode would look like this:
+ *
+ * ```
+ * <?xml version="1.0" encoding="utf-8"?>
+ * <android.support.wearable.complications.rendering.ComplicationDrawable
+ * xmlns:app="http://schemas.android.com/apk/res-auto"
+ * app:textColor="#FFFF0000"
+ * app:titleColor="#FFFFFFFF">
+ * <ambient
+ * app:textColor="#FFFFFFFF" />
+ * </android.support.wearable.complications.rendering.ComplicationDrawable>
+ * ```
+ *
+ *
+ * A top-level `drawable` tag with the `class` attribute may also be used to
+ * construct a ComplicationDrawable from an XML file:
+ *
+ * ```
+ * <?xml version="1.0" encoding="utf-8"?>
+ * <drawable
+ * class="android.support.wearable.complications.rendering.ComplicationDrawable"
+ * xmlns:app="http://schemas.android.com/apk/res-auto"
+ * app:textColor="#FFFF0000"
+ * app:titleColor="#FFFFFFFF">
+ * <ambient
+ * app:textColor="#FFFFFFFF" />
+ * </drawable>
+ * ```
+ *
+ * To inflate a ComplicationDrawable from XML file, use the [.getDrawable]
+ * method. ComplicationDrawable needs access to the current context in order to style and draw
+ * the complication.
+ *
+ * ```
+ * public void onCreate(SurfaceHolder holder) {
+ * ...
+ * ComplicationDrawable complicationDrawable = (ComplicationDrawable)
+ * getDrawable(R.drawable.complication);
+ * complicationDrawable.setContext(WatchFaceService.this);
+ * ...
+ * }
+ * ```
+ *
+ * <h4>Syntax:</h4>
+ * ```
+ * <?xml version="1.0" encoding="utf-8"?>
+ * <android.support.wearable.complications.rendering.ComplicationDrawable
+ * xmlns:app="http://schemas.android.com/apk/res-auto"
+ * app:backgroundColor="color"
+ * app:backgroundDrawable="drawable"
+ * app:borderColor="color"
+ * app:borderDashGap="dimension"
+ * app:borderDashWidth="dimension"
+ * app:borderRadius="dimension"
+ * app:borderStyle="none|solid|dashed"
+ * app:borderWidth="dimension"
+ * app:highlightColor="color"
+ * app:iconColor="color"
+ * app:rangedValuePrimaryColor="color"
+ * app:rangedValueProgressHidden="boolean"
+ * app:rangedValueRingWidth="dimension"
+ * app:rangedValueSecondaryColor="color"
+ * app:textColor="color"
+ * app:textSize="dimension"
+ * app:textTypeface="string"
+ * app:titleColor="color"
+ * app:titleSize="dimension"
+ * app:titleTypeface="string">
+ * <ambient
+ * app:backgroundColor="color"
+ * app:backgroundDrawable="drawable"
+ * app:borderColor="color"
+ * app:borderDashGap="dimension"
+ * app:borderDashWidth="dimension"
+ * app:borderRadius="dimension"
+ * app:borderStyle="none|solid|dashed"
+ * app:borderWidth="dimension"
+ * app:highlightColor="color"
+ * app:iconColor="color"
+ * app:rangedValuePrimaryColor="color"
+ * app:rangedValueRingWidth="dimension"
+ * app:rangedValueSecondaryColor="color"
+ * app:textColor="color"
+ * app:textSize="dimension"
+ * app:textTypeface="string"
+ * app:titleColor="color"
+ * app:titleSize="dimension"
+ * app:titleTypeface="string" />
+ * </android.support.wearable.complications.rendering.ComplicationDrawable>
+ * ```
+ *
+ * Attributes of the top-level tag apply to both active and ambient modes while attributes of the
+ * inner `ambient` tag only apply to ambient mode. As an exception, top-level only
+ * `rangedValueProgressHidden` attribute applies to both modes, and cannot be overridden in ambient
+ * mode. To hide ranged value in only one of the active or ambient modes, you may consider setting
+ * `rangedValuePrimaryColor` and `rangedValueSecondaryColor` to [android.graphics.Color.TRANSPARENT]
+ * instead.
+ *
+ * <h3>Drawing a ComplicationDrawable</h3>
+ *
+ * Depending on the size and shape of the bounds, the layout of the complication may change. For
+ * instance, a short text complication with an icon that is drawn on square bounds would draw the
+ * icon above the short text, but a short text complication with an icon that is drawn on wide
+ * rectangular bounds might draw the icon to the left of the short text instead.
+ */
+public class ComplicationDrawable : Drawable {
+    /**
+     * Returns the [Context] used to render the complication.
+     */
+    public var context: Context? = null
+        private set
+
+    /** Returns complication renderer.  */
+    @VisibleForTesting(otherwise = VisibleForTesting.NONE)
+    @get:JvmName("getComplicationRenderer")
+    internal var complicationRenderer: ComplicationRenderer? = null
+        private set
+
+    /** Returns complication style for active mode.  */
+    public val activeStyle: ComplicationStyle
+
+    /** Returns complication style for ambient mode.  */
+    public val ambientStyle: ComplicationStyle
+
+    private val mainThreadHandler = Handler(Looper.getMainLooper())
+    private val unhighlightRunnable = Runnable {
+        isHighlighted = false
+        invalidateSelf()
+    }
+    private val rendererInvalidateListener = OnInvalidateListener { invalidateSelf() }
+
+    /**
+     * The time in milliseconds since the epoch used for rendering [ComplicationData]
+     * with time dependent text.
+     */
+    public var currentTimeMillis: Long = 0
+
+    /** Whether the complication is rendered in ambient mode.  */
+    public var isInAmbientMode: Boolean = false
+
+    /**
+     * Whether the complication, when rendering in ambient mode, should apply a style
+     * suitable for low bit ambient mode.
+     */
+    public var isLowBitAmbient: Boolean = false
+
+    /**
+     * Whether the complication, when rendering in ambient mode, should apply a style suitable for
+     * display on devices with burn in protection.
+     */
+    public var isBurnInProtectionOn: Boolean = false
+
+    /**
+     * Whether the complication is currently highlighted. This may be called by a watch face
+     * when a complication is tapped.
+     *
+     * If watch face is in ambient mode, highlight will not be visible even if this is set to
+     * `true`, because it may cause burn-in or power inefficiency.
+     */
+    public var isHighlighted: Boolean = false
+
+    private var isInflatedFromXml = false
+    private var alreadyStyled = false
+
+    /** Default constructor.  */
+    public constructor() {
+        activeStyle = ComplicationStyle()
+        ambientStyle = ComplicationStyle()
+    }
+
+    /**
+     * Creates a ComplicationDrawable using the given context. If this constructor is used, calling
+     * [.setContext] may not be necessary.
+     */
+    public constructor(context: Context) : this() {
+        setContext(context)
+    }
+
+    public constructor(drawable: ComplicationDrawable) {
+        activeStyle = ComplicationStyle(drawable.activeStyle)
+        ambientStyle = ComplicationStyle(drawable.ambientStyle)
+        noDataText = drawable.noDataText!!.subSequence(0, drawable.noDataText!!.length)
+        highlightDuration = drawable.highlightDuration
+        currentTimeMillis = drawable.currentTimeMillis
+        bounds = drawable.bounds
+        isInAmbientMode = drawable.isInAmbientMode
+        isLowBitAmbient = drawable.isLowBitAmbient
+        isBurnInProtectionOn = drawable.isBurnInProtectionOn
+        isHighlighted = false
+        isRangedValueProgressHidden = drawable.isRangedValueProgressHidden
+        isInflatedFromXml = drawable.isInflatedFromXml
+        alreadyStyled = true
+    }
+
+    /**
+     * Sets the context used to render the complication. If a context is not set,
+     * ComplicationDrawable will throw an [IllegalStateException] if one of [draw], [setBounds],
+     * or [setComplicationData] is called.
+     *
+     * While this can be called from any context, ideally, a
+     * androidx.wear.watchface.WatchFaceService object should be passed here to allow creating
+     * permission dialogs by the [onTap] method, in case current watch face doesn't have the
+     * permission to receive complication data.
+     *
+     * If this ComplicationDrawable is retrieved using [Resources.getDrawable], this method must
+     * be called before calling any of the methods mentioned above.
+     *
+     * If this ComplicationDrawable is not inflated from an XML file, this method will reset the
+     * style to match the default values, so if [ComplicationDrawable()] is used to construct a
+     * ComplicationDrawable, this method should be called right after.
+     */
+    public fun setContext(context: Context) {
+        if (context == this.context) {
+            return
+        }
+        this.context = context
+        if (!isInflatedFromXml && !alreadyStyled) {
+            setStyleToDefaultValues(activeStyle, context.resources)
+            setStyleToDefaultValues(ambientStyle, context.resources)
+        }
+        if (!alreadyStyled) {
+            highlightDuration = context.resources
+                .getInteger(R.integer.complicationDrawable_highlightDurationMs).toLong()
+        }
+        complicationRenderer = ComplicationRenderer(this.context, activeStyle, ambientStyle)
+        val nonNullComplicationRenderer = complicationRenderer!!
+        nonNullComplicationRenderer.setOnInvalidateListener(rendererInvalidateListener)
+        if (noDataText == null) {
+            noDataText = context.getString(R.string.complicationDrawable_noDataText)
+        } else {
+            nonNullComplicationRenderer.setNoDataText(noDataText)
+        }
+        nonNullComplicationRenderer.isRangedValueProgressHidden = isRangedValueProgressHidden
+        nonNullComplicationRenderer.bounds = bounds
+    }
+
+    private fun inflateAttributes(r: Resources, parser: XmlPullParser) {
+        val a = r.obtainAttributes(Xml.asAttributeSet(parser), R.styleable.ComplicationDrawable)
+        isRangedValueProgressHidden =
+            a.getBoolean(R.styleable.ComplicationDrawable_rangedValueProgressHidden, false)
+        a.recycle()
+    }
+
+    private fun inflateStyle(isAmbient: Boolean, r: Resources, parser: XmlPullParser) {
+        val a = r.obtainAttributes(Xml.asAttributeSet(parser), R.styleable.ComplicationDrawable)
+        val complicationStyle = if (isAmbient) ambientStyle else activeStyle
+        if (a.hasValue(R.styleable.ComplicationDrawable_backgroundColor)) {
+            complicationStyle.backgroundColor = a.getColor(
+                R.styleable.ComplicationDrawable_backgroundColor,
+                r.getColor(R.color.complicationDrawable_backgroundColor, null)
+            )
+        }
+        if (a.hasValue(R.styleable.ComplicationDrawable_backgroundDrawable)) {
+            complicationStyle.backgroundDrawable =
+                a.getDrawable(R.styleable.ComplicationDrawable_backgroundDrawable)
+        }
+        if (a.hasValue(R.styleable.ComplicationDrawable_textColor)) {
+            complicationStyle.textColor = a.getColor(
+                R.styleable.ComplicationDrawable_textColor,
+                r.getColor(R.color.complicationDrawable_textColor, null)
+            )
+        }
+        if (a.hasValue(R.styleable.ComplicationDrawable_titleColor)) {
+            complicationStyle.titleColor = a.getColor(
+                R.styleable.ComplicationDrawable_titleColor,
+                r.getColor(R.color.complicationDrawable_titleColor, null)
+            )
+        }
+        if (a.hasValue(R.styleable.ComplicationDrawable_textTypeface)) {
+            complicationStyle.setTextTypeface(
+                Typeface.create(
+                    a.getString(R.styleable.ComplicationDrawable_textTypeface),
+                    Typeface.NORMAL
+                )
+            )
+        }
+        if (a.hasValue(R.styleable.ComplicationDrawable_titleTypeface)) {
+            complicationStyle.setTitleTypeface(
+                Typeface.create(
+                    a.getString(R.styleable.ComplicationDrawable_titleTypeface),
+                    Typeface.NORMAL
+                )
+            )
+        }
+        if (a.hasValue(R.styleable.ComplicationDrawable_textSize)) {
+            complicationStyle.textSize = a.getDimensionPixelSize(
+                R.styleable.ComplicationDrawable_textSize,
+                r.getDimensionPixelSize(R.dimen.complicationDrawable_textSize)
+            )
+        }
+        if (a.hasValue(R.styleable.ComplicationDrawable_titleSize)) {
+            complicationStyle.titleSize = a.getDimensionPixelSize(
+                R.styleable.ComplicationDrawable_titleSize,
+                r.getDimensionPixelSize(R.dimen.complicationDrawable_titleSize)
+            )
+        }
+        if (a.hasValue(R.styleable.ComplicationDrawable_iconColor)) {
+            complicationStyle.iconColor = a.getColor(
+                R.styleable.ComplicationDrawable_iconColor,
+                r.getColor(R.color.complicationDrawable_iconColor, null)
+            )
+        }
+        if (a.hasValue(R.styleable.ComplicationDrawable_borderColor)) {
+            complicationStyle.borderColor = a.getColor(
+                R.styleable.ComplicationDrawable_borderColor,
+                r.getColor(R.color.complicationDrawable_borderColor, null)
+            )
+        }
+        if (a.hasValue(R.styleable.ComplicationDrawable_borderRadius)) {
+            complicationStyle.borderRadius = a.getDimensionPixelSize(
+                R.styleable.ComplicationDrawable_borderRadius,
+                r.getDimensionPixelSize(R.dimen.complicationDrawable_borderRadius)
+            )
+        }
+        if (a.hasValue(R.styleable.ComplicationDrawable_borderStyle)) {
+            complicationStyle.borderStyle = a.getInt(
+                R.styleable.ComplicationDrawable_borderStyle,
+                r.getInteger(R.integer.complicationDrawable_borderStyle)
+            )
+        }
+        if (a.hasValue(R.styleable.ComplicationDrawable_borderDashWidth)) {
+            complicationStyle.borderDashWidth = a.getDimensionPixelSize(
+                R.styleable.ComplicationDrawable_borderDashWidth,
+                r.getDimensionPixelSize(R.dimen.complicationDrawable_borderDashWidth)
+            )
+        }
+        if (a.hasValue(R.styleable.ComplicationDrawable_borderDashGap)) {
+            complicationStyle.borderDashGap = a.getDimensionPixelSize(
+                R.styleable.ComplicationDrawable_borderDashGap,
+                r.getDimensionPixelSize(R.dimen.complicationDrawable_borderDashGap)
+            )
+        }
+        if (a.hasValue(R.styleable.ComplicationDrawable_borderWidth)) {
+            complicationStyle.borderWidth = a.getDimensionPixelSize(
+                R.styleable.ComplicationDrawable_borderWidth,
+                r.getDimensionPixelSize(R.dimen.complicationDrawable_borderWidth)
+            )
+        }
+        if (a.hasValue(R.styleable.ComplicationDrawable_rangedValueRingWidth)) {
+            complicationStyle.rangedValueRingWidth = a.getDimensionPixelSize(
+                R.styleable.ComplicationDrawable_rangedValueRingWidth,
+                r.getDimensionPixelSize(
+                    R.dimen.complicationDrawable_rangedValueRingWidth
+                )
+            )
+        }
+        if (a.hasValue(R.styleable.ComplicationDrawable_rangedValuePrimaryColor)) {
+            complicationStyle.rangedValuePrimaryColor = a.getColor(
+                R.styleable.ComplicationDrawable_rangedValuePrimaryColor,
+                r.getColor(
+                    R.color.complicationDrawable_rangedValuePrimaryColor, null
+                )
+            )
+        }
+        if (a.hasValue(R.styleable.ComplicationDrawable_rangedValueSecondaryColor)) {
+            complicationStyle.rangedValueSecondaryColor = a.getColor(
+                R.styleable.ComplicationDrawable_rangedValueSecondaryColor,
+                r.getColor(
+                    R.color.complicationDrawable_rangedValueSecondaryColor, null
+                )
+            )
+        }
+        if (a.hasValue(R.styleable.ComplicationDrawable_highlightColor)) {
+            complicationStyle.highlightColor = a.getColor(
+                R.styleable.ComplicationDrawable_highlightColor,
+                r.getColor(R.color.complicationDrawable_highlightColor, null)
+            )
+        }
+        a.recycle()
+    }
+
+    /**
+     * Inflates this ComplicationDrawable from an XML resource. This can't be called more than once
+     * for each ComplicationDrawable. Note that framework may have called this once to create the
+     * ComplicationDrawable instance from an XML resource.
+     *
+     * @param r      Resources used to resolve attribute values
+     * @param parser XML parser from which to inflate this ComplicationDrawable
+     * @param attrs  Base set of attribute values
+     * @param theme  Ignored by ComplicationDrawable
+     */
+    @Throws(XmlPullParserException::class, IOException::class)
+    public override fun inflate(
+        r: Resources,
+        parser: XmlPullParser,
+        attrs: AttributeSet,
+        theme: Resources.Theme?
+    ) {
+        check(!isInflatedFromXml) { "inflate may be called once only." }
+        isInflatedFromXml = true
+        var type: Int
+        val outerDepth = parser.depth
+        // Inflate attributes always shared between active and ambient mode
+        inflateAttributes(r, parser)
+        // Reset both style builders to default values
+        setStyleToDefaultValues(activeStyle, r)
+        setStyleToDefaultValues(ambientStyle, r)
+        // Attributes of the outer tag applies to both active and ambient styles
+        inflateStyle(false, r, parser)
+        inflateStyle(true, r, parser)
+        while (
+            parser.next().also { type = it } != XmlPullParser.END_DOCUMENT &&
+            (type != XmlPullParser.END_TAG || parser.depth > outerDepth)
+        ) {
+            if (type != XmlPullParser.START_TAG) {
+                continue
+            }
+
+            // Attributes of inner <ambient> tag applies to ambient style only
+            val name = parser.name
+            if (TextUtils.equals(name, "ambient")) {
+                inflateStyle(true, r, parser)
+            } else {
+                Log.w(
+                    "ComplicationDrawable",
+                    "Unknown element: $name for ComplicationDrawable $this"
+                )
+            }
+        }
+    }
+
+    /**
+     * Draws the complication for the last known time. Last known time is derived from
+     * ComplicationDrawable#setCurrentTimeMillis(long)}.
+     *
+     * @param canvas Canvas for the complication to be drawn onto
+     */
+    public override fun draw(canvas: Canvas) {
+        assertInitialized()
+        updateStyleIfRequired()
+        complicationRenderer?.draw(
+            canvas,
+            currentTimeMillis,
+            isInAmbientMode,
+            isLowBitAmbient,
+            isBurnInProtectionOn,
+            isHighlighted
+        )
+    }
+
+    /**
+     * This function is not supported in [ComplicationDrawable].
+     *
+     * @throws [UnsupportedOperationException] when called.
+     */
+    public override fun setAlpha(@IntRange(from = 0, to = 255) alpha: Int) {
+        throw UnsupportedOperationException("setAlpha is not supported in ComplicationDrawable.")
+    }
+
+    /**
+     * This function is not supported in [ComplicationDrawable]. Use
+     * [ComplicationStyle.imageColorFilter] instead to apply color filter to small and large images.
+     *
+     * @throws [UnsupportedOperationException] when called.
+     */
+    public override fun setColorFilter(colorFilter: ColorFilter?) {
+        throw UnsupportedOperationException(
+            "setColorFilter is not supported in ComplicationDrawable."
+        )
+    }
+
+    /** @throws [UnsupportedOperationException] when called. */
+    @Deprecated("This method is no longer used in graphics optimizations")
+    override fun getOpacity(): Int =
+        throw UnsupportedOperationException("getOpacity is not supported in ComplicationDrawable.")
+
+    protected override fun onBoundsChange(bounds: Rect) {
+        if (complicationRenderer != null) {
+            complicationRenderer!!.bounds = bounds
+        }
+    }
+
+    /** If the ranged value progress should be hidden when [ComplicationData] is of type
+     * [RANGED_VALUE].
+     *
+     * @attr ref androidx.wear.watchface.complications.rendering.R
+     * .styleable#ComplicationDrawable_rangedValueProgressHidden
+     */
+    public var isRangedValueProgressHidden: Boolean = false
+        set(rangedValueProgressHidden) {
+            field = rangedValueProgressHidden
+            complicationRenderer?.isRangedValueProgressHidden = rangedValueProgressHidden
+        }
+
+    /**
+     * Sets the complication data to be drawn. If `complicationData` is `null`, nothing
+     * will be drawn when [draw] is called.
+     *
+     * @param complicationData The [ComplicationData] to set
+     * @param loadDrawablesAsync If true any drawables should be loaded asynchronously,
+     * otherwise they will be loaded synchronously.
+     */
+    public fun setComplicationData(
+        complicationData: ComplicationData?,
+        loadDrawablesAsync: Boolean
+    ) {
+        assertInitialized()
+        complicationRenderer?.setComplicationData(
+            complicationData?.asWireComplicationData(),
+            loadDrawablesAsync
+        )
+    }
+
+    /**
+     * Returns the [ComplicationData] to be drawn by this ComplicationDrawable.
+     */
+    public val complicationData: ComplicationData?
+        get() = if (complicationRenderer?.complicationData != null)
+            complicationRenderer!!.complicationData.toApiComplicationData()
+        else null
+
+    /**
+     * Sends the tap action for the complication if tap coordinates are inside the complication
+     * bounds.
+     *
+     * This method will also highlight the complication. The highlight duration is 300
+     * milliseconds by default but can be modified using the [.setHighlightDuration]
+     * method.
+     *
+     * If [ComplicationData] has the type [NO_PERMISSION], this method will launch an intent to
+     * request complication permission for the watch face. This will only work if the context set
+     * by [getDrawable] or the constructor is an instance of WatchFaceService.
+     *
+     * @param x X coordinate of the tap relative to screen origin
+     * @param y Y coordinate of the tap relative to screen origin
+     * @return `true` if the action was successful, `false` if complication data is not set, the
+     * complication has no tap action, the tap action (i.e. [android.app.PendingIntent]) is
+     * cancelled, or the given x and y are not inside the complication bounds.
+     */
+    public fun onTap(@Px x: Int, @Px y: Int): Boolean {
+        if (complicationRenderer == null) {
+            return false
+        }
+        val data = complicationRenderer!!.complicationData ?: return false
+        if (!data.hasTapAction() && data.type
+            != android.support.wearable.complications.ComplicationData.TYPE_NO_PERMISSION
+        ) {
+            return false
+        }
+        if (!bounds.contains(x, y)) {
+            return false
+        }
+        if (data.type
+            == android.support.wearable.complications.ComplicationData.TYPE_NO_PERMISSION
+        ) {
+            // Check if context is an instance of WatchFaceService. We can't use the standard
+            // instanceof operator because WatchFaceService is defined in library which depends on
+            // this one, hence the reflection hack.
+            try {
+                if (context!!::class.java.name == "androidx.wear.watchface.WatchFaceService") {
+                    context!!.startActivity(
+                        ComplicationHelperActivity.createPermissionRequestHelperIntent(
+                            context!!,
+                            ComponentName(context!!, context!!.javaClass)
+                        )
+                            .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+                    )
+                } else {
+                    return false
+                }
+            } catch (e: ClassNotFoundException) {
+                // If watchFaceServiceClass class isn't found we know context can't be an instance
+                // of WatchFaceService.
+                return false
+            }
+        } else {
+            try {
+                data.tapAction!!.send()
+            } catch (e: PendingIntent.CanceledException) {
+                return false
+            }
+        }
+        if (highlightDuration > 0) {
+            isHighlighted = true
+            invalidateSelf()
+            mainThreadHandler.removeCallbacks(unhighlightRunnable)
+            mainThreadHandler.postDelayed(unhighlightRunnable, highlightDuration)
+        }
+        return true
+    }
+
+    /** The duration for the complication to stay highlighted after calling the [onTap] method.
+     * Default value is 300 milliseconds. Setting highlight duration to 0 disables highlighting.
+     */
+    public var highlightDuration: Long = 0
+        set(@IntRange(from = 0) highlightDurationMillis: Long) {
+            require(highlightDurationMillis >= 0) { "Highlight duration should be non-negative." }
+            field = highlightDurationMillis
+        }
+
+    /** Builds styles and syncs them with the complication renderer.  */
+    @JvmName(name = "updateStyleIfRequired")
+    internal fun updateStyleIfRequired() {
+        if (activeStyle.isDirty || ambientStyle.isDirty) {
+            complicationRenderer!!.updateStyle(activeStyle, ambientStyle)
+            activeStyle.clearDirtyFlag()
+            ambientStyle.clearDirtyFlag()
+        }
+    }
+
+    /**
+     * Throws an exception if the context is not set. This method should be called if any of the
+     * member methods do a context-dependent job.
+     */
+    private fun assertInitialized() {
+        checkNotNull(context) {
+            "ComplicationDrawable does not have a context. Use setContext(Context) to set it first."
+        }
+    }
+    /**
+     * The text to be rendered when [ComplicationData] is of type [NO_DATA]. If `noDataText` is
+     * null, an empty text will be
+     * rendered.
+     */
+    public var noDataText: CharSequence? = null
+        set(noDataText) {
+            field = noDataText?.subSequence(0, noDataText.length) ?: ""
+            if (complicationRenderer != null) {
+                complicationRenderer!!.setNoDataText(field)
+            }
+        }
+
+    public companion object {
+        /**
+         * Creates a ComplicationDrawable from a resource.
+         *
+         * @param context The [Context] to load the resource from
+         * @param id      The id of the resource to load
+         * @return The [ComplicationDrawable] loaded from the specified resource id or null if it
+         * doesn't exist.
+         */
+        @JvmStatic
+        public fun getDrawable(context: Context, id: Int): ComplicationDrawable? {
+            val drawable = context.getDrawable(id) as ComplicationDrawable? ?: return null
+            drawable.setContext(context)
+            return drawable
+        }
+
+        /** Sets the style to default values using resources.  */
+        @JvmStatic
+        internal fun setStyleToDefaultValues(style: ComplicationStyle, r: Resources) {
+            style.backgroundColor = r.getColor(R.color.complicationDrawable_backgroundColor, null)
+            style.textColor = r.getColor(R.color.complicationDrawable_textColor, null)
+            style.titleColor = r.getColor(R.color.complicationDrawable_titleColor, null)
+            style.setTextTypeface(
+                Typeface.create(
+                    r.getString(R.string.complicationDrawable_textTypeface), Typeface.NORMAL
+                )
+            )
+            style.setTitleTypeface(
+                Typeface.create(
+                    r.getString(R.string.complicationDrawable_titleTypeface), Typeface.NORMAL
+                )
+            )
+            style.textSize = r.getDimensionPixelSize(R.dimen.complicationDrawable_textSize)
+            style.titleSize = r.getDimensionPixelSize(R.dimen.complicationDrawable_titleSize)
+            style.iconColor = r.getColor(R.color.complicationDrawable_iconColor, null)
+            style.borderColor = r.getColor(R.color.complicationDrawable_borderColor, null)
+            style.borderWidth = r.getDimensionPixelSize(R.dimen.complicationDrawable_borderWidth)
+            style.borderRadius = r.getDimensionPixelSize(R.dimen.complicationDrawable_borderRadius)
+            style.borderStyle = r.getInteger(R.integer.complicationDrawable_borderStyle)
+            style.borderDashWidth =
+                r.getDimensionPixelSize(R.dimen.complicationDrawable_borderDashWidth)
+            style.borderDashGap =
+                r.getDimensionPixelSize(R.dimen.complicationDrawable_borderDashGap)
+            style.rangedValueRingWidth =
+                r.getDimensionPixelSize(R.dimen.complicationDrawable_rangedValueRingWidth)
+            style.rangedValuePrimaryColor =
+                r.getColor(R.color.complicationDrawable_rangedValuePrimaryColor, null)
+            style.rangedValueSecondaryColor =
+                r.getColor(R.color.complicationDrawable_rangedValueSecondaryColor, null)
+            style.highlightColor = r.getColor(R.color.complicationDrawable_highlightColor, null)
+        }
+    }
+}
\ No newline at end of file
diff --git a/wear/wear-watchface-complications-rendering/src/test/java/androidx/wear/watchface/complications/rendering/ComplicationDrawableTest.java b/wear/wear-watchface-complications-rendering/src/test/java/androidx/wear/watchface/complications/rendering/ComplicationDrawableTest.java
index 223c731..ed6f874c 100644
--- a/wear/wear-watchface-complications-rendering/src/test/java/androidx/wear/watchface/complications/rendering/ComplicationDrawableTest.java
+++ b/wear/wear-watchface-complications-rendering/src/test/java/androidx/wear/watchface/complications/rendering/ComplicationDrawableTest.java
@@ -120,11 +120,6 @@
     }
 
     @Test
-    public void callingSetContextWithNullThrowsIllegalArgumentException() {
-        assertThrows(IllegalArgumentException.class, () -> mComplicationDrawable.setContext(null));
-    }
-
-    @Test
     public void callingDrawOnCanvasBeforeSetContextThrowsAnException() {
         assertThrows(IllegalStateException.class, () -> mComplicationDrawable.draw(mMockCanvas));
     }
diff --git a/wear/wear-watchface-data/api/restricted_current.txt b/wear/wear-watchface-data/api/restricted_current.txt
index 98430d9..16f5ad0 100644
--- a/wear/wear-watchface-data/api/restricted_current.txt
+++ b/wear/wear-watchface-data/api/restricted_current.txt
@@ -308,7 +308,7 @@
   }
 
   @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @androidx.versionedparcelable.VersionedParcelize public class ComplicationOverlayWireFormat implements android.os.Parcelable androidx.versionedparcelable.VersionedParcelable {
-    ctor public ComplicationOverlayWireFormat(int, Boolean?, java.util.Map<androidx.wear.complications.data.ComplicationType!,android.graphics.RectF!>?, Integer?);
+    ctor public ComplicationOverlayWireFormat(int, Boolean?, java.util.Map<java.lang.Integer!,android.graphics.RectF!>?, Integer?);
     method public int describeContents();
     method public Integer? getAccessibilityTraversalIndex();
     method public void writeToParcel(android.os.Parcel, int);
@@ -319,7 +319,7 @@
     field public static final long NULL_ACCESSIBILITY_TRAVERSAL_INDEX = 4294967296L; // 0x100000000L
     field @androidx.versionedparcelable.ParcelField(1) public int mComplicationId;
     field @androidx.versionedparcelable.ParcelField(2) public int mEnabled;
-    field @androidx.versionedparcelable.ParcelField(3) public java.util.Map<androidx.wear.complications.data.ComplicationType!,android.graphics.RectF!>? mPerComplicationTypeBounds;
+    field @androidx.versionedparcelable.ParcelField(3) public java.util.Map<java.lang.Integer!,android.graphics.RectF!>? mPerComplicationTypeBounds;
   }
 
   @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @androidx.versionedparcelable.VersionedParcelize public class ComplicationsOptionWireFormat extends androidx.wear.watchface.style.data.OptionWireFormat {
diff --git a/wear/wear-watchface-data/src/main/java/androidx/wear/watchface/style/data/ComplicationOverlayWireFormat.java b/wear/wear-watchface-data/src/main/java/androidx/wear/watchface/style/data/ComplicationOverlayWireFormat.java
index 71ebc33..e63e83b 100644
--- a/wear/wear-watchface-data/src/main/java/androidx/wear/watchface/style/data/ComplicationOverlayWireFormat.java
+++ b/wear/wear-watchface-data/src/main/java/androidx/wear/watchface/style/data/ComplicationOverlayWireFormat.java
@@ -28,7 +28,6 @@
 import androidx.versionedparcelable.ParcelUtils;
 import androidx.versionedparcelable.VersionedParcelable;
 import androidx.versionedparcelable.VersionedParcelize;
-import androidx.wear.complications.data.ComplicationType;
 
 import java.util.Map;
 
@@ -54,7 +53,7 @@
 
     @ParcelField(3)
     @Nullable
-    public Map<ComplicationType, RectF> mPerComplicationTypeBounds;
+    public Map<Integer, RectF> mPerComplicationTypeBounds;
 
     /** Ideally this would be Integer but VersionedParcelable doesn't support that. */
     @ParcelField(4)
@@ -66,7 +65,7 @@
     public ComplicationOverlayWireFormat(
             int complicationId,
             @Nullable Boolean enabled,
-            @Nullable Map<ComplicationType, RectF> perComplicationTypeBounds,
+            @Nullable Map<Integer, RectF> perComplicationTypeBounds,
             @Nullable Integer accessibilityTraversalIndex
     ) {
         mComplicationId = complicationId;
diff --git a/wear/wear-watchface-editor/api/current.txt b/wear/wear-watchface-editor/api/current.txt
index f1dddbb..56b5343 100644
--- a/wear/wear-watchface-editor/api/current.txt
+++ b/wear/wear-watchface-editor/api/current.txt
@@ -37,6 +37,7 @@
     method public abstract Integer? getBackgroundComplicationId();
     method @UiThread public abstract Integer? getComplicationIdAt(@Px int x, @Px int y);
     method @UiThread public abstract suspend Object? getComplicationsPreviewData(kotlin.coroutines.Continuation<? super java.util.Map<java.lang.Integer,? extends androidx.wear.complications.data.ComplicationData>> p);
+    method @UiThread public abstract suspend Object? getComplicationsProviderInfo(kotlin.coroutines.Continuation<? super java.util.Map<java.lang.Integer,androidx.wear.complications.ComplicationProviderInfo>> p);
     method public abstract java.util.Map<java.lang.Integer,androidx.wear.watchface.client.ComplicationState> getComplicationsState();
     method public abstract long getPreviewReferenceTimeMillis();
     method public abstract androidx.wear.watchface.style.UserStyle getUserStyle();
diff --git a/wear/wear-watchface-editor/api/public_plus_experimental_current.txt b/wear/wear-watchface-editor/api/public_plus_experimental_current.txt
index f1dddbb..56b5343 100644
--- a/wear/wear-watchface-editor/api/public_plus_experimental_current.txt
+++ b/wear/wear-watchface-editor/api/public_plus_experimental_current.txt
@@ -37,6 +37,7 @@
     method public abstract Integer? getBackgroundComplicationId();
     method @UiThread public abstract Integer? getComplicationIdAt(@Px int x, @Px int y);
     method @UiThread public abstract suspend Object? getComplicationsPreviewData(kotlin.coroutines.Continuation<? super java.util.Map<java.lang.Integer,? extends androidx.wear.complications.data.ComplicationData>> p);
+    method @UiThread public abstract suspend Object? getComplicationsProviderInfo(kotlin.coroutines.Continuation<? super java.util.Map<java.lang.Integer,androidx.wear.complications.ComplicationProviderInfo>> p);
     method public abstract java.util.Map<java.lang.Integer,androidx.wear.watchface.client.ComplicationState> getComplicationsState();
     method public abstract long getPreviewReferenceTimeMillis();
     method public abstract androidx.wear.watchface.style.UserStyle getUserStyle();
diff --git a/wear/wear-watchface-editor/api/restricted_current.txt b/wear/wear-watchface-editor/api/restricted_current.txt
index 5ab6ecc..9cee27d 100644
--- a/wear/wear-watchface-editor/api/restricted_current.txt
+++ b/wear/wear-watchface-editor/api/restricted_current.txt
@@ -3,11 +3,12 @@
 
   @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public abstract class BaseEditorSession extends androidx.wear.watchface.editor.EditorSession {
     method public void close();
-    method protected final void fetchComplicationPreviewData();
+    method protected final void fetchComplicationsData();
     method public Integer? getBackgroundComplicationId();
     method protected final boolean getClosed();
     method public Integer? getComplicationIdAt(@Px int x, @Px int y);
     method public suspend Object? getComplicationsPreviewData(kotlin.coroutines.Continuation<? super java.util.Map<java.lang.Integer,? extends androidx.wear.complications.data.ComplicationData>> $completion);
+    method public suspend Object? getComplicationsProviderInfo(kotlin.coroutines.Continuation<? super java.util.Map<java.lang.Integer,androidx.wear.complications.ComplicationProviderInfo>> $completion);
     method public final kotlinx.coroutines.CoroutineScope getCoroutineScope();
     method protected final boolean getForceClosed();
     method public suspend Object? openComplicationProviderChooser(int p, kotlin.coroutines.Continuation<? super androidx.wear.watchface.editor.ChosenComplicationProvider> $completion);
@@ -57,6 +58,7 @@
     method public abstract Integer? getBackgroundComplicationId();
     method @UiThread public abstract Integer? getComplicationIdAt(@Px int x, @Px int y);
     method @UiThread public abstract suspend Object? getComplicationsPreviewData(kotlin.coroutines.Continuation<? super java.util.Map<java.lang.Integer,? extends androidx.wear.complications.data.ComplicationData>> p);
+    method @UiThread public abstract suspend Object? getComplicationsProviderInfo(kotlin.coroutines.Continuation<? super java.util.Map<java.lang.Integer,androidx.wear.complications.ComplicationProviderInfo>> p);
     method public abstract java.util.Map<java.lang.Integer,androidx.wear.watchface.client.ComplicationState> getComplicationsState();
     method public abstract long getPreviewReferenceTimeMillis();
     method public abstract androidx.wear.watchface.style.UserStyle getUserStyle();
diff --git a/wear/wear-watchface-editor/guava/api/current.txt b/wear/wear-watchface-editor/guava/api/current.txt
index 35b16e6..716598b 100644
--- a/wear/wear-watchface-editor/guava/api/current.txt
+++ b/wear/wear-watchface-editor/guava/api/current.txt
@@ -8,8 +8,10 @@
     method public Integer? getBackgroundComplicationId();
     method public Integer? getComplicationIdAt(int x, int y);
     method public suspend Object? getComplicationsPreviewData(kotlin.coroutines.Continuation<? super java.util.Map<java.lang.Integer,? extends androidx.wear.complications.data.ComplicationData>> p);
+    method public suspend Object? getComplicationsProviderInfo(kotlin.coroutines.Continuation<? super java.util.Map<java.lang.Integer,androidx.wear.complications.ComplicationProviderInfo>> p);
     method public java.util.Map<java.lang.Integer,androidx.wear.watchface.client.ComplicationState> getComplicationsState();
     method public com.google.common.util.concurrent.ListenableFuture<java.util.Map<java.lang.Integer,androidx.wear.complications.data.ComplicationData>> getListenableComplicationPreviewData();
+    method public com.google.common.util.concurrent.ListenableFuture<java.util.Map<java.lang.Integer,androidx.wear.complications.ComplicationProviderInfo>> getListenableComplicationsProviderInfo();
     method public long getPreviewReferenceTimeMillis();
     method public androidx.wear.watchface.style.UserStyle getUserStyle();
     method public androidx.wear.watchface.style.UserStyleSchema getUserStyleSchema();
diff --git a/wear/wear-watchface-editor/guava/api/public_plus_experimental_current.txt b/wear/wear-watchface-editor/guava/api/public_plus_experimental_current.txt
index 35b16e6..716598b 100644
--- a/wear/wear-watchface-editor/guava/api/public_plus_experimental_current.txt
+++ b/wear/wear-watchface-editor/guava/api/public_plus_experimental_current.txt
@@ -8,8 +8,10 @@
     method public Integer? getBackgroundComplicationId();
     method public Integer? getComplicationIdAt(int x, int y);
     method public suspend Object? getComplicationsPreviewData(kotlin.coroutines.Continuation<? super java.util.Map<java.lang.Integer,? extends androidx.wear.complications.data.ComplicationData>> p);
+    method public suspend Object? getComplicationsProviderInfo(kotlin.coroutines.Continuation<? super java.util.Map<java.lang.Integer,androidx.wear.complications.ComplicationProviderInfo>> p);
     method public java.util.Map<java.lang.Integer,androidx.wear.watchface.client.ComplicationState> getComplicationsState();
     method public com.google.common.util.concurrent.ListenableFuture<java.util.Map<java.lang.Integer,androidx.wear.complications.data.ComplicationData>> getListenableComplicationPreviewData();
+    method public com.google.common.util.concurrent.ListenableFuture<java.util.Map<java.lang.Integer,androidx.wear.complications.ComplicationProviderInfo>> getListenableComplicationsProviderInfo();
     method public long getPreviewReferenceTimeMillis();
     method public androidx.wear.watchface.style.UserStyle getUserStyle();
     method public androidx.wear.watchface.style.UserStyleSchema getUserStyleSchema();
diff --git a/wear/wear-watchface-editor/guava/api/restricted_current.txt b/wear/wear-watchface-editor/guava/api/restricted_current.txt
index 35b16e6..716598b 100644
--- a/wear/wear-watchface-editor/guava/api/restricted_current.txt
+++ b/wear/wear-watchface-editor/guava/api/restricted_current.txt
@@ -8,8 +8,10 @@
     method public Integer? getBackgroundComplicationId();
     method public Integer? getComplicationIdAt(int x, int y);
     method public suspend Object? getComplicationsPreviewData(kotlin.coroutines.Continuation<? super java.util.Map<java.lang.Integer,? extends androidx.wear.complications.data.ComplicationData>> p);
+    method public suspend Object? getComplicationsProviderInfo(kotlin.coroutines.Continuation<? super java.util.Map<java.lang.Integer,androidx.wear.complications.ComplicationProviderInfo>> p);
     method public java.util.Map<java.lang.Integer,androidx.wear.watchface.client.ComplicationState> getComplicationsState();
     method public com.google.common.util.concurrent.ListenableFuture<java.util.Map<java.lang.Integer,androidx.wear.complications.data.ComplicationData>> getListenableComplicationPreviewData();
+    method public com.google.common.util.concurrent.ListenableFuture<java.util.Map<java.lang.Integer,androidx.wear.complications.ComplicationProviderInfo>> getListenableComplicationsProviderInfo();
     method public long getPreviewReferenceTimeMillis();
     method public androidx.wear.watchface.style.UserStyle getUserStyle();
     method public androidx.wear.watchface.style.UserStyleSchema getUserStyleSchema();
diff --git a/wear/wear-watchface-editor/guava/src/main/java/androidx/wear/watchface/editor/ListenableEditorSession.kt b/wear/wear-watchface-editor/guava/src/main/java/androidx/wear/watchface/editor/ListenableEditorSession.kt
index f6c92d1..d9f57d1 100644
--- a/wear/wear-watchface-editor/guava/src/main/java/androidx/wear/watchface/editor/ListenableEditorSession.kt
+++ b/wear/wear-watchface-editor/guava/src/main/java/androidx/wear/watchface/editor/ListenableEditorSession.kt
@@ -24,6 +24,7 @@
 import androidx.annotation.RequiresApi
 import androidx.annotation.UiThread
 import androidx.concurrent.futures.ResolvableFuture
+import androidx.wear.complications.ComplicationProviderInfo
 import androidx.wear.complications.data.ComplicationData
 import androidx.wear.watchface.RenderParameters
 import androidx.wear.watchface.client.ComplicationState
@@ -138,9 +139,26 @@
             return future
         }
 
+    /** [ListenableFuture] wrapper around [EditorSession.getComplicationsProviderInfo]. */
+    public fun getListenableComplicationsProviderInfo():
+        ListenableFuture<Map<Int, ComplicationProviderInfo?>> {
+            val future = ResolvableFuture.create<Map<Int, ComplicationProviderInfo?>>()
+            getCoroutineScope().launch {
+                try {
+                    future.set(wrappedEditorSession.getComplicationsProviderInfo())
+                } catch (e: Exception) {
+                    future.setException(e)
+                }
+            }
+            return future
+        }
+
     override suspend fun getComplicationsPreviewData(): Map<Int, ComplicationData> =
         wrappedEditorSession.getComplicationsPreviewData()
 
+    override suspend fun getComplicationsProviderInfo(): Map<Int, ComplicationProviderInfo?> =
+        wrappedEditorSession.getComplicationsProviderInfo()
+
     @get:SuppressWarnings("AutoBoxing")
     override val backgroundComplicationId: Int?
         get() = wrappedEditorSession.backgroundComplicationId
diff --git a/wear/wear-watchface-editor/lint-baseline.xml b/wear/wear-watchface-editor/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/wear/wear-watchface-editor/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/wear/wear-watchface-editor/samples/lint-baseline.xml b/wear/wear-watchface-editor/samples/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/wear/wear-watchface-editor/samples/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/wear/wear-watchface-editor/samples/src/main/java/androidx/wear/watchface/editor/sample/ComplicationConfigFragment.kt b/wear/wear-watchface-editor/samples/src/main/java/androidx/wear/watchface/editor/sample/ComplicationConfigFragment.kt
index 12d569a..53f57ea 100644
--- a/wear/wear-watchface-editor/samples/src/main/java/androidx/wear/watchface/editor/sample/ComplicationConfigFragment.kt
+++ b/wear/wear-watchface-editor/samples/src/main/java/androidx/wear/watchface/editor/sample/ComplicationConfigFragment.kt
@@ -20,6 +20,7 @@
 import android.graphics.Canvas
 import android.graphics.Color
 import android.graphics.Rect
+import android.os.Build
 import android.os.Bundle
 import android.util.Log
 import android.util.TypedValue
@@ -27,8 +28,10 @@
 import android.view.View
 import android.view.ViewGroup
 import android.widget.Button
+import androidx.annotation.RequiresApi
 import androidx.annotation.RestrictTo
 import androidx.fragment.app.Fragment
+import androidx.wear.complications.ComplicationProviderInfo
 import androidx.wear.complications.data.ComplicationData
 import androidx.wear.watchface.DrawMode
 import androidx.wear.watchface.RenderParameters
@@ -110,6 +113,14 @@
                         }.resourceId
                     )
                     setOnClickListener { onComplicationButtonClicked(stateEntry.key) }
+                    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+                        setOnLongClickListener {
+                            TooltipApi26.updateTooltip(it, watchFaceConfigActivity, stateEntry.key)
+                            // Do not consume the long click so that the tooltip is shown by the
+                            // default handler.
+                            false
+                        }
+                    }
                     addView(this)
                 }
             }
@@ -146,6 +157,25 @@
         }
     }
 
+    @RequiresApi(Build.VERSION_CODES.O)
+    private object TooltipApi26 {
+        fun updateTooltip(
+            button: View,
+            watchFaceConfigActivity: WatchFaceConfigActivity,
+            complicationId: Int
+        ) {
+            watchFaceConfigActivity.coroutineScope.launch {
+                val providerInfo =
+                    watchFaceConfigActivity.editorSession
+                        .getComplicationsProviderInfo()[complicationId]
+                button.tooltipText = getProviderInfoToast(providerInfo)
+            }
+        }
+
+        private fun getProviderInfoToast(providerInfo: ComplicationProviderInfo?): String =
+            providerInfo?.name ?: "Empty complication provider"
+    }
+
     override fun onSizeChanged(width: Int, height: Int, oldWidth: Int, oldHeight: Int) {
         drawRect.set(0, 0, width, height)
     }
diff --git a/wear/wear-watchface-editor/src/androidTest/AndroidManifest.xml b/wear/wear-watchface-editor/src/androidTest/AndroidManifest.xml
index e1d17da..8698f0b 100644
--- a/wear/wear-watchface-editor/src/androidTest/AndroidManifest.xml
+++ b/wear/wear-watchface-editor/src/androidTest/AndroidManifest.xml
@@ -25,6 +25,7 @@
                 <category android:name="android.intent.category.LAUNCHER" />
             </intent-filter>
         </activity>
+
         <activity android:name="androidx.wear.watchface.editor.TestComplicationHelperActivity"
             android:exported="false">
             <intent-filter>
@@ -33,5 +34,10 @@
                 <category android:name="android.intent.category.LAUNCHER" />
             </intent-filter>
         </activity>
+
+        <activity
+            android:name="androidx.wear.watchface.editor.TestComplicationProviderChooserActivity" />
+
+        <activity android:name="androidx.wear.complications.ComplicationHelperActivity" />
     </application>
 </manifest>
diff --git a/wear/wear-watchface-editor/src/androidTest/java/androidx/wear/watchface/editor/EditingSessionTest.kt b/wear/wear-watchface-editor/src/androidTest/java/androidx/wear/watchface/editor/EditingSessionTest.kt
index 72b79d5..38007635 100644
--- a/wear/wear-watchface-editor/src/androidTest/java/androidx/wear/watchface/editor/EditingSessionTest.kt
+++ b/wear/wear-watchface-editor/src/androidTest/java/androidx/wear/watchface/editor/EditingSessionTest.kt
@@ -41,6 +41,7 @@
 import androidx.test.filters.MediumTest
 import androidx.test.filters.SdkSuppress
 import androidx.wear.complications.ComplicationBounds
+import androidx.wear.complications.ComplicationHelperActivity
 import androidx.wear.complications.ComplicationProviderInfo
 import androidx.wear.complications.DefaultComplicationProviderPolicy
 import androidx.wear.complications.ProviderChooserIntent
@@ -110,6 +111,8 @@
 
 private const val PROVIDER_CHOOSER_EXTRA_KEY = "PROVIDER_CHOOSER_EXTRA_KEY"
 private const val PROVIDER_CHOOSER_EXTRA_VALUE = "PROVIDER_CHOOSER_EXTRA_VALUE"
+private const val PROVIDER_CHOOSER_RESULT_EXTRA_KEY = "PROVIDER_CHOOSER_RESULT_EXTRA_KEY"
+private const val PROVIDER_CHOOSER_RESULT_EXTRA_VALUE = "PROVIDER_CHOOSER_RESULT_EXTRA_VALUE"
 
 private typealias WireComplicationProviderInfo =
     android.support.wearable.complications.ComplicationProviderInfo
@@ -235,6 +238,24 @@
     }
 }
 
+/** Fake complication provider choooser for testing. */
+public class TestComplicationProviderChooserActivity : Activity() {
+
+    public companion object {
+        public var lastIntent: Intent? = null
+        public var resultIntent: Intent? = null
+    }
+
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+
+        lastIntent = intent
+
+        setResult(123, resultIntent)
+        finish()
+    }
+}
+
 @RunWith(AndroidJUnit4::class)
 @MediumTest
 public class EditorSessionTest {
@@ -409,6 +430,9 @@
 
     @After
     public fun tearDown() {
+        ComplicationProviderChooserContract.useTestComplicationHelperActivity = false
+        ComplicationHelperActivity.useTestComplicationProviderChooserActivity = false
+        ComplicationHelperActivity.skipPermissionCheck = false
         WatchFace.clearAllEditorDelegates()
     }
 
@@ -897,7 +921,7 @@
     }
 
     @Test
-    public fun launchComplicationProviderChooser_ComplicationConfigExtras() {
+    public fun launchComplicationProviderChooser_ComplicationConfigExtrasToHelper() {
         ComplicationProviderChooserContract.useTestComplicationHelperActivity = true
         val chosenComplicationProviderInfo = ComplicationProviderInfo(
             "TestProvider3App",
@@ -913,6 +937,7 @@
                 "android.support.wearable.complications.EXTRA_PROVIDER_INFO",
                 chosenComplicationProviderInfo.toWireComplicationProviderInfo()
             )
+            putExtra(PROVIDER_CHOOSER_RESULT_EXTRA_KEY, PROVIDER_CHOOSER_RESULT_EXTRA_VALUE)
         }
 
         val scenario = createOnWatchFaceEditingTestActivity(
@@ -935,6 +960,9 @@
                 chosenComplicationProviderInfo,
                 chosenComplicationProvider.complicationProviderInfo
             )
+            assertThat(
+                chosenComplicationProvider.extras[PROVIDER_CHOOSER_RESULT_EXTRA_KEY]
+            ).isEqualTo(PROVIDER_CHOOSER_RESULT_EXTRA_VALUE)
 
             assertThat(
                 TestComplicationHelperActivity.lastIntent?.extras?.getString(
@@ -945,6 +973,62 @@
     }
 
     @Test
+    public fun launchComplicationProviderChooser_ComplicationConfigExtrasToChooser() {
+        // Invoke the test provider chooser to record the result.
+        ComplicationHelperActivity.useTestComplicationProviderChooserActivity = true
+        // Invoke the provider chooser without checking for permissions first.
+        ComplicationHelperActivity.skipPermissionCheck = true
+
+        val chosenComplicationProviderInfo = ComplicationProviderInfo(
+            "TestProvider3App",
+            "TestProvider3",
+            Icon.createWithBitmap(
+                Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888)
+            ),
+            ComplicationType.LONG_TEXT,
+            provider3
+        )
+        TestComplicationProviderChooserActivity.resultIntent = Intent().apply {
+            putExtra(
+                "android.support.wearable.complications.EXTRA_PROVIDER_INFO",
+                chosenComplicationProviderInfo.toWireComplicationProviderInfo()
+            )
+            putExtra(PROVIDER_CHOOSER_RESULT_EXTRA_KEY, PROVIDER_CHOOSER_RESULT_EXTRA_VALUE)
+        }
+
+        val scenario = createOnWatchFaceEditingTestActivity(
+            emptyList(),
+            listOf(leftComplication, rightComplication)
+        )
+
+        lateinit var editorSession: EditorSession
+        scenario.onActivity { activity ->
+            editorSession = activity.editorSession
+        }
+
+        runBlocking {
+            val chosenComplicationProvider =
+                editorSession.openComplicationProviderChooser(RIGHT_COMPLICATION_ID)
+            assertThat(chosenComplicationProvider).isNotNull()
+            checkNotNull(chosenComplicationProvider)
+            assertThat(chosenComplicationProvider.complicationId).isEqualTo(RIGHT_COMPLICATION_ID)
+            assertEquals(
+                chosenComplicationProviderInfo,
+                chosenComplicationProvider.complicationProviderInfo
+            )
+            assertThat(
+                chosenComplicationProvider.extras[PROVIDER_CHOOSER_RESULT_EXTRA_KEY]
+            ).isEqualTo(PROVIDER_CHOOSER_RESULT_EXTRA_VALUE)
+
+            assertThat(
+                TestComplicationProviderChooserActivity.lastIntent?.extras?.getString(
+                    PROVIDER_CHOOSER_EXTRA_KEY
+                )
+            ).isEqualTo(PROVIDER_CHOOSER_EXTRA_VALUE)
+        }
+    }
+
+    @Test
     public fun getComplicationIdAt() {
         val scenario = createOnWatchFaceEditingTestActivity(
             emptyList(),
@@ -1244,7 +1328,7 @@
         scenario.onActivity {
             baseEditorSession = it.editorSession as BaseEditorSession
             baseEditorSession.pendingComplicationProviderChooserResult = CompletableDeferred()
-            baseEditorSession.updatePreviewData(
+            baseEditorSession.onComplicationProviderChooserResult(
                 ComplicationProviderChooserResult(
                     ComplicationProviderInfo(
                         "provider.app",
diff --git a/wear/wear-watchface-editor/src/main/java/androidx/wear/watchface/editor/EditorSession.kt b/wear/wear-watchface-editor/src/main/java/androidx/wear/watchface/editor/EditorSession.kt
index d08f9f6..6935eed 100644
--- a/wear/wear-watchface-editor/src/main/java/androidx/wear/watchface/editor/EditorSession.kt
+++ b/wear/wear-watchface-editor/src/main/java/androidx/wear/watchface/editor/EditorSession.kt
@@ -132,6 +132,16 @@
     @UiThread
     public abstract suspend fun getComplicationsPreviewData(): Map<Int, ComplicationData>
 
+    /**
+     * Returns a map of complication ids to [ComplicationProviderInfo] that represent the
+     * information available about the provider for each complication.
+     *
+     * A `null` [ComplicationProviderInfo] will be associated with a complication id if the
+     * complication is configured to show the empty complication provider.
+     */
+    @UiThread
+    public abstract suspend fun getComplicationsProviderInfo(): Map<Int, ComplicationProviderInfo?>
+
     /** The ID of the background complication or `null` if there isn't one. */
     @get:SuppressWarnings("AutoBoxing")
     public abstract val backgroundComplicationId: Int?
@@ -319,8 +329,10 @@
         EditorService.globalEditorService.addCloseCallback(closeCallback)
     }
 
-    // This is completed when [fetchComplicationPreviewData] has called [getPreviewData] for
-    // each complication and each of those have been completed.
+    /**
+     * This is completed when [fetchComplicationsData] has called [getPreviewData] for each
+     * complication and each of those have been completed.
+     */
     private val deferredComplicationPreviewDataMap =
         CompletableDeferred<MutableMap<Int, ComplicationData>>()
 
@@ -328,6 +340,14 @@
         return deferredComplicationPreviewDataMap.await()
     }
 
+    // This is completed when [fetchProviderInfo] has called [getProviderInfo] for each
+    // complication and each of those have been completed.
+    private val deferredComplicationsProviderInfoMap =
+        CompletableDeferred<MutableMap<Int, ComplicationProviderInfo?>>()
+
+    override suspend fun getComplicationsProviderInfo(): Map<Int, ComplicationProviderInfo?> =
+        deferredComplicationsProviderInfoMap.await()
+
     /** Pending result for [openComplicationProviderChooser]. */
     internal var pendingComplicationProviderChooserResult:
         CompletableDeferred<ChosenComplicationProvider?>? = null
@@ -337,10 +357,10 @@
 
     private val chooseComplicationProvider =
         activity.registerForActivityResult(ComplicationProviderChooserContract()) {
-            updatePreviewData(it)
+            onComplicationProviderChooserResult(it)
         }
 
-    internal fun updatePreviewData(
+    internal fun onComplicationProviderChooserResult(
         complicationProviderChooserResult: ComplicationProviderChooserResult?
     ) {
         // Check if the user cancelled the provider chooser.
@@ -351,8 +371,13 @@
         }
         val providerInfoRetriever =
             providerInfoRetrieverProvider.getProviderInfoRetriever()
-        coroutineScope.launchWithTracing("BaseEditorSession.updatePreviewData") {
+        coroutineScope.launchWithTracing(
+            "BaseEditorSession.onComplicationProviderChooserResult"
+        ) {
             try {
+                val complicationsProviderInfoMap = deferredComplicationsProviderInfoMap.await()
+                complicationsProviderInfoMap[pendingComplicationProviderId] =
+                    complicationProviderChooserResult.providerInfo
                 val previewData = getPreviewData(
                     providerInfoRetriever,
                     complicationProviderChooserResult.providerInfo
@@ -463,9 +488,9 @@
             MonochromaticImage.Builder(providerInfo.icon).build()
         ).build()
 
-    protected fun fetchComplicationPreviewData() {
+    protected fun fetchComplicationsData() {
         val providerInfoRetriever = providerInfoRetrieverProvider.getProviderInfoRetriever()
-        coroutineScope.launchWithTracing("BaseEditorSession.fetchComplicationPreviewData") {
+        coroutineScope.launchWithTracing("BaseEditorSession.fetchComplicationsData") {
             try {
                 // Unlikely but WCS could conceivably crash during this call. We could retry but it's
                 // not obvious if that'd succeed or if WCS session state is recoverable, it's probably
@@ -474,6 +499,10 @@
                     watchFaceComponentName,
                     complicationsState.keys.toIntArray()
                 )
+                deferredComplicationsProviderInfoMap.complete(
+                    extractComplicationsProviderInfoMap(providerInfoArray)?.toMutableMap()
+                        ?: mutableMapOf()
+                )
                 deferredComplicationPreviewDataMap.complete(
                     // Parallel fetch preview ComplicationData.
                     providerInfoArray?.associateBy(
@@ -645,7 +674,7 @@
                 UserStyle(initialEditorUserStyle, editorDelegate.userStyleSchema)
         }
 
-        fetchComplicationPreviewData()
+        fetchComplicationsData()
     }
 }
 
@@ -686,7 +715,7 @@
     }
 
     init {
-        fetchComplicationPreviewData()
+        fetchComplicationsData()
     }
 }
 
@@ -712,6 +741,12 @@
 
     internal companion object {
         const val EXTRA_PROVIDER_INFO = "android.support.wearable.complications.EXTRA_PROVIDER_INFO"
+
+        /**
+         * Whether to invoke a test activity instead of the [ComplicationHelperActivity].
+         *
+         * To be used in tests.
+         */
         internal var useTestComplicationHelperActivity = false
     }
 
@@ -749,4 +784,16 @@
     }
 }
 
+/**
+ * Extracts a map from complication ID to the corresponding [ComplicationProviderInfo] from the
+ * given array of [ProviderInfoRetriever.ProviderInfo].
+ */
+internal fun extractComplicationsProviderInfoMap(
+    providerInfoArray: Array<ProviderInfoRetriever.ProviderInfo>?
+): Map<Int, ComplicationProviderInfo?>? =
+    providerInfoArray?.associateBy(
+        { it.watchFaceComplicationId },
+        { it.info }
+    )
+
 internal fun Bundle.asString() = keySet().map { "$it: ${get(it)}" }
diff --git a/wear/wear-watchface-style/lint-baseline.xml b/wear/wear-watchface-style/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/wear/wear-watchface-style/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/wear/wear-watchface-style/src/main/java/androidx/wear/watchface/style/UserStyleSetting.kt b/wear/wear-watchface-style/src/main/java/androidx/wear/watchface/style/UserStyleSetting.kt
index 55a881d..d3169ac 100644
--- a/wear/wear-watchface-style/src/main/java/androidx/wear/watchface/style/UserStyleSetting.kt
+++ b/wear/wear-watchface-style/src/main/java/androidx/wear/watchface/style/UserStyleSetting.kt
@@ -19,6 +19,7 @@
 import android.graphics.drawable.Icon
 import androidx.annotation.RestrictTo
 import androidx.wear.complications.ComplicationBounds
+import androidx.wear.complications.data.ComplicationType
 import androidx.wear.watchface.style.UserStyleSetting.ComplicationsUserStyleSetting.ComplicationOverlay
 import androidx.wear.watchface.style.UserStyleSetting.ComplicationsUserStyleSetting.ComplicationsOption
 import androidx.wear.watchface.style.UserStyleSetting.Id.Companion.MAX_LENGTH
@@ -438,7 +439,11 @@
                         "Unrecognised wireFormat.mEnabled " + wireFormat.mEnabled
                     )
                 },
-                wireFormat.mPerComplicationTypeBounds?.let { ComplicationBounds(it) },
+                wireFormat.mPerComplicationTypeBounds?.let {
+                    ComplicationBounds(
+                        it.mapKeys { ComplicationType.fromWireType(it.key) }
+                    )
+                },
                 wireFormat.accessibilityTraversalIndex
             )
 
@@ -446,7 +451,9 @@
                 ComplicationOverlayWireFormat(
                     complicationId,
                     enabled,
-                    complicationBounds?.perComplicationTypeBounds,
+                    complicationBounds?.perComplicationTypeBounds?.mapKeys {
+                        it.key.toWireComplicationType()
+                    },
                     accessibilityTraversalIndex
                 )
         }
diff --git a/wear/wear-watchface/api/restricted_current.txt b/wear/wear-watchface/api/restricted_current.txt
index eaa4494..1aa5089 100644
--- a/wear/wear-watchface/api/restricted_current.txt
+++ b/wear/wear-watchface/api/restricted_current.txt
@@ -359,7 +359,7 @@
     method @UiThread public void invalidate();
     method public void onUserStyleChanged();
     method public void setActiveComplications(int[] watchFaceComplicationIds);
-    method public void setDefaultComplicationProviderWithFallbacks(int watchFaceComplicationId, java.util.List<android.content.ComponentName>? providers, @androidx.wear.complications.SystemProviders.Companion.ProviderId int fallbackSystemProvider, int type);
+    method public void setDefaultComplicationProviderWithFallbacks(int watchFaceComplicationId, java.util.List<android.content.ComponentName>? providers, @androidx.wear.complications.SystemProviders.ProviderId int fallbackSystemProvider, int type);
     method public void updateContentDescriptionLabels();
   }
 
diff --git a/wear/wear-watchface/samples/lint-baseline.xml b/wear/wear-watchface/samples/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/wear/wear-watchface/samples/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/wear/wear-watchface/src/main/java/androidx/wear/watchface/BroadcastReceivers.kt b/wear/wear-watchface/src/main/java/androidx/wear/watchface/BroadcastReceivers.kt
deleted file mode 100644
index 8f9b1b9..0000000
--- a/wear/wear-watchface/src/main/java/androidx/wear/watchface/BroadcastReceivers.kt
+++ /dev/null
@@ -1,216 +0,0 @@
-/*
- * Copyright 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.wear.watchface
-
-import android.content.BroadcastReceiver
-import android.content.Context
-import android.content.Intent
-import android.content.IntentFilter
-import androidx.annotation.UiThread
-import androidx.annotation.VisibleForTesting
-
-/**
- * All watchface instances share the same [Context] which is a problem for broadcast receivers
- * because the OS will mistakenly believe we're leaking them if there's more than one instance. So
- * we need to use this class to share them.
- */
-internal class BroadcastReceivers private constructor(private val context: Context) {
-
-    interface BroadcastEventObserver {
-        /** Called when we receive [Intent.ACTION_TIME_TICK]. */
-        @UiThread
-        fun onActionTimeTick()
-
-        /** Called when we receive [Intent.ACTION_TIMEZONE_CHANGED]. */
-        @UiThread
-        fun onActionTimeZoneChanged()
-
-        /** Called when we receive [Intent.ACTION_TIME_CHANGED]. */
-        @UiThread
-        fun onActionTimeChanged()
-
-        /** Called when we receive [Intent.ACTION_BATTERY_LOW]. */
-        @UiThread
-        fun onActionBatteryLow()
-
-        /** Called when we receive [Intent.ACTION_BATTERY_OKAY]. */
-        @UiThread
-        fun onActionBatteryOkay()
-
-        /** Called when we receive [Intent.ACTION_POWER_CONNECTED]. */
-        @UiThread
-        fun onActionPowerConnected()
-
-        /** Called when we receive [WatchFaceImpl.MOCK_TIME_INTENT]. */
-        @UiThread
-        fun onMockTime(intent: Intent)
-    }
-
-    companion object {
-        val broadcastEventObservers = HashSet<BroadcastEventObserver>()
-
-        /**
-         * We don't leak due to balanced calls to [addBroadcastEventObserver] and
-         * [removeBroadcastEventObserver] which sets this back to null.
-         */
-        @SuppressWarnings("StaticFieldLeak")
-        var broadcastReceivers: BroadcastReceivers? = null
-
-        @UiThread
-        fun addBroadcastEventObserver(context: Context, observer: BroadcastEventObserver) {
-            broadcastEventObservers.add(observer)
-            if (broadcastReceivers == null) {
-                broadcastReceivers = BroadcastReceivers(context)
-            }
-        }
-
-        @UiThread
-        fun removeBroadcastEventObserver(observer: BroadcastEventObserver) {
-            broadcastEventObservers.remove(observer)
-            if (broadcastEventObservers.isEmpty()) {
-                broadcastReceivers!!.onDestroy()
-                broadcastReceivers = null
-            }
-        }
-
-        @VisibleForTesting
-        fun sendOnActionBatteryLowForTesting(intent: Intent) {
-            require(intent.action == Intent.ACTION_BATTERY_LOW)
-            require(broadcastEventObservers.isNotEmpty())
-            for (observer in broadcastEventObservers) {
-                observer.onActionBatteryLow()
-            }
-        }
-
-        @VisibleForTesting
-        fun sendOnActionBatteryOkayForTesting(intent: Intent) {
-            require(intent.action == Intent.ACTION_BATTERY_OKAY)
-            require(broadcastEventObservers.isNotEmpty())
-            for (observer in broadcastEventObservers) {
-                observer.onActionBatteryOkay()
-            }
-        }
-
-        @VisibleForTesting
-        fun sendOnActionPowerConnectedForTesting(intent: Intent) {
-            require(intent.action == Intent.ACTION_POWER_CONNECTED)
-            require(broadcastEventObservers.isNotEmpty())
-            for (observer in broadcastEventObservers) {
-                observer.onActionPowerConnected()
-            }
-        }
-
-        @VisibleForTesting
-        fun sendOnMockTimeForTesting(intent: Intent) {
-            require(intent.action == WatchFaceImpl.MOCK_TIME_INTENT)
-            require(broadcastEventObservers.isNotEmpty())
-            for (observer in broadcastEventObservers) {
-                observer.onMockTime(intent)
-            }
-        }
-    }
-
-    private val actionTimeTickReceiver: BroadcastReceiver = object : BroadcastReceiver() {
-        @SuppressWarnings("SyntheticAccessor")
-        override fun onReceive(context: Context, intent: Intent) {
-            for (observer in broadcastEventObservers) {
-                observer.onActionTimeTick()
-            }
-        }
-    }
-
-    private val actionTimeZoneReceiver: BroadcastReceiver = object : BroadcastReceiver() {
-        override fun onReceive(context: Context, intent: Intent) {
-            for (observer in broadcastEventObservers) {
-                observer.onActionTimeZoneChanged()
-            }
-        }
-    }
-
-    private val actionTimeReceiver: BroadcastReceiver = object : BroadcastReceiver() {
-        override fun onReceive(context: Context?, intent: Intent?) {
-            for (observer in broadcastEventObservers) {
-                observer.onActionTimeChanged()
-            }
-        }
-    }
-
-    private val actionBatteryLowReceiver: BroadcastReceiver = object : BroadcastReceiver() {
-        @SuppressWarnings("SyntheticAccessor")
-        override fun onReceive(context: Context, intent: Intent) {
-            for (observer in broadcastEventObservers) {
-                observer.onActionBatteryLow()
-            }
-        }
-    }
-
-    private val actionBatteryOkayReceiver: BroadcastReceiver = object : BroadcastReceiver() {
-        @SuppressWarnings("SyntheticAccessor")
-        override fun onReceive(context: Context, intent: Intent) {
-            for (observer in broadcastEventObservers) {
-                observer.onActionBatteryOkay()
-            }
-        }
-    }
-
-    private val actionPowerConnectedReceiver: BroadcastReceiver = object : BroadcastReceiver() {
-        @SuppressWarnings("SyntheticAccessor")
-        override fun onReceive(context: Context, intent: Intent) {
-            for (observer in broadcastEventObservers) {
-                observer.onActionPowerConnected()
-            }
-        }
-    }
-
-    private val mockTimeReceiver: BroadcastReceiver = object : BroadcastReceiver() {
-        @SuppressWarnings("SyntheticAccessor")
-        override fun onReceive(context: Context, intent: Intent) {
-            for (observer in broadcastEventObservers) {
-                observer.onMockTime(intent)
-            }
-        }
-    }
-
-    init {
-        context.registerReceiver(actionTimeTickReceiver, IntentFilter(Intent.ACTION_TIME_TICK))
-        context.registerReceiver(
-            actionTimeZoneReceiver,
-            IntentFilter(Intent.ACTION_TIMEZONE_CHANGED)
-        )
-        context.registerReceiver(actionTimeReceiver, IntentFilter(Intent.ACTION_TIME_CHANGED))
-        context.registerReceiver(actionBatteryLowReceiver, IntentFilter(Intent.ACTION_BATTERY_LOW))
-        context.registerReceiver(
-            actionBatteryOkayReceiver,
-            IntentFilter(Intent.ACTION_BATTERY_OKAY)
-        )
-        context.registerReceiver(
-            actionPowerConnectedReceiver,
-            IntentFilter(Intent.ACTION_POWER_CONNECTED)
-        )
-        context.registerReceiver(mockTimeReceiver, IntentFilter(WatchFaceImpl.MOCK_TIME_INTENT))
-    }
-
-    fun onDestroy() {
-        context.unregisterReceiver(actionTimeTickReceiver)
-        context.unregisterReceiver(actionTimeZoneReceiver)
-        context.unregisterReceiver(actionTimeReceiver)
-        context.unregisterReceiver(actionBatteryLowReceiver)
-        context.unregisterReceiver(actionBatteryOkayReceiver)
-        context.unregisterReceiver(actionPowerConnectedReceiver)
-        context.unregisterReceiver(mockTimeReceiver)
-    }
-}
\ No newline at end of file
diff --git a/wear/wear-watchface/src/main/java/androidx/wear/watchface/BroadcastsReceiver.kt b/wear/wear-watchface/src/main/java/androidx/wear/watchface/BroadcastsReceiver.kt
new file mode 100644
index 0000000..cc6675a
--- /dev/null
+++ b/wear/wear-watchface/src/main/java/androidx/wear/watchface/BroadcastsReceiver.kt
@@ -0,0 +1,139 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.wear.watchface
+
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.content.IntentFilter
+import androidx.annotation.UiThread
+
+/**
+ * This class decouples [BroadcastEventObserver]s from the actual broadcast event receivers to make
+ * testing easier.
+ */
+internal class BroadcastsReceiver constructor(
+    private val context: Context,
+    private val observer: BroadcastEventObserver
+) {
+
+    interface BroadcastEventObserver {
+        /** Called when we receive [Intent.ACTION_TIME_TICK]. */
+        @UiThread
+        fun onActionTimeTick()
+
+        /** Called when we receive [Intent.ACTION_TIMEZONE_CHANGED]. */
+        @UiThread
+        fun onActionTimeZoneChanged()
+
+        /** Called when we receive [Intent.ACTION_TIME_CHANGED]. */
+        @UiThread
+        fun onActionTimeChanged()
+
+        /** Called when we receive [Intent.ACTION_BATTERY_LOW]. */
+        @UiThread
+        fun onActionBatteryLow()
+
+        /** Called when we receive [Intent.ACTION_BATTERY_OKAY]. */
+        @UiThread
+        fun onActionBatteryOkay()
+
+        /** Called when we receive [Intent.ACTION_POWER_CONNECTED]. */
+        @UiThread
+        fun onActionPowerConnected()
+
+        /** Called when we receive [WatchFaceImpl.MOCK_TIME_INTENT]. */
+        @UiThread
+        fun onMockTime(intent: Intent)
+    }
+
+    internal val actionTimeTickReceiver: BroadcastReceiver = object : BroadcastReceiver() {
+        @SuppressWarnings("SyntheticAccessor")
+        override fun onReceive(context: Context, intent: Intent) {
+            observer.onActionTimeTick()
+        }
+    }
+
+    internal val actionTimeZoneReceiver: BroadcastReceiver = object : BroadcastReceiver() {
+        override fun onReceive(context: Context, intent: Intent) {
+            observer.onActionTimeZoneChanged()
+        }
+    }
+
+    internal val actionTimeReceiver: BroadcastReceiver = object : BroadcastReceiver() {
+        override fun onReceive(context: Context?, intent: Intent?) {
+            observer.onActionTimeChanged()
+        }
+    }
+
+    internal val actionBatteryLowReceiver: BroadcastReceiver = object : BroadcastReceiver() {
+        @SuppressWarnings("SyntheticAccessor")
+        override fun onReceive(context: Context, intent: Intent) {
+            observer.onActionBatteryLow()
+        }
+    }
+
+    internal val actionBatteryOkayReceiver: BroadcastReceiver = object : BroadcastReceiver() {
+        @SuppressWarnings("SyntheticAccessor")
+        override fun onReceive(context: Context, intent: Intent) {
+            observer.onActionBatteryOkay()
+        }
+    }
+
+    internal val actionPowerConnectedReceiver: BroadcastReceiver = object : BroadcastReceiver() {
+        @SuppressWarnings("SyntheticAccessor")
+        override fun onReceive(context: Context, intent: Intent) {
+            observer.onActionPowerConnected()
+        }
+    }
+
+    internal val mockTimeReceiver: BroadcastReceiver = object : BroadcastReceiver() {
+        @SuppressWarnings("SyntheticAccessor")
+        override fun onReceive(context: Context, intent: Intent) {
+            observer.onMockTime(intent)
+        }
+    }
+
+    init {
+        context.registerReceiver(actionTimeTickReceiver, IntentFilter(Intent.ACTION_TIME_TICK))
+        context.registerReceiver(
+            actionTimeZoneReceiver,
+            IntentFilter(Intent.ACTION_TIMEZONE_CHANGED)
+        )
+        context.registerReceiver(actionTimeReceiver, IntentFilter(Intent.ACTION_TIME_CHANGED))
+        context.registerReceiver(actionBatteryLowReceiver, IntentFilter(Intent.ACTION_BATTERY_LOW))
+        context.registerReceiver(
+            actionBatteryOkayReceiver,
+            IntentFilter(Intent.ACTION_BATTERY_OKAY)
+        )
+        context.registerReceiver(
+            actionPowerConnectedReceiver,
+            IntentFilter(Intent.ACTION_POWER_CONNECTED)
+        )
+        context.registerReceiver(mockTimeReceiver, IntentFilter(WatchFaceImpl.MOCK_TIME_INTENT))
+    }
+
+    fun onDestroy() {
+        context.unregisterReceiver(actionTimeTickReceiver)
+        context.unregisterReceiver(actionTimeZoneReceiver)
+        context.unregisterReceiver(actionTimeReceiver)
+        context.unregisterReceiver(actionBatteryLowReceiver)
+        context.unregisterReceiver(actionBatteryOkayReceiver)
+        context.unregisterReceiver(actionPowerConnectedReceiver)
+        context.unregisterReceiver(mockTimeReceiver)
+    }
+}
\ No newline at end of file
diff --git a/wear/wear-watchface/src/main/java/androidx/wear/watchface/Complication.kt b/wear/wear-watchface/src/main/java/androidx/wear/watchface/Complication.kt
index 27a58d2..09eca33 100644
--- a/wear/wear-watchface/src/main/java/androidx/wear/watchface/Complication.kt
+++ b/wear/wear-watchface/src/main/java/androidx/wear/watchface/Complication.kt
@@ -445,7 +445,9 @@
 
             // The caller might modify a number of complications. For efficiency we need to coalesce
             // these into one update task.
-            complicationsManager.scheduleUpdate()
+            if (this::complicationsManager.isInitialized) {
+                complicationsManager.scheduleUpdate()
+            }
         }
 
     internal var enabledDirty = true
diff --git a/wear/wear-watchface/src/main/java/androidx/wear/watchface/WatchFace.kt b/wear/wear-watchface/src/main/java/androidx/wear/watchface/WatchFace.kt
index 2153570..8dbc10c 100644
--- a/wear/wear-watchface/src/main/java/androidx/wear/watchface/WatchFace.kt
+++ b/wear/wear-watchface/src/main/java/androidx/wear/watchface/WatchFace.kt
@@ -435,7 +435,7 @@
     private var mockTime = MockTime(1.0, 0, Long.MAX_VALUE)
 
     private var lastTappedComplicationId: Int? = null
-    private var registeredReceivers = false
+    internal var broadcastsReceiver: BroadcastsReceiver? = null
 
     // True if 'Do Not Disturb' mode is on.
     private var muteMode = false
@@ -465,7 +465,7 @@
         legacyWatchFaceStyle.tapEventsAccepted
     )
 
-    private val broadcastEventObserver = object : BroadcastReceivers.BroadcastEventObserver {
+    private val broadcastEventObserver = object : BroadcastsReceiver.BroadcastEventObserver {
         override fun onActionTimeTick() {
             if (!watchState.isAmbient.value) {
                 renderer.invalidate()
@@ -759,14 +759,12 @@
         require(watchFaceHostApi.getHandler().looper.isCurrentThread) {
             "registerReceivers must be called the UiThread"
         }
-        if (registeredReceivers) {
-            return
+
+        // There's no point registering BroadcastsReceiver for headless instances.
+        if (broadcastsReceiver == null && !watchState.isHeadless) {
+            broadcastsReceiver =
+                BroadcastsReceiver(watchFaceHostApi.getContext(), broadcastEventObserver)
         }
-        registeredReceivers = true
-        BroadcastReceivers.addBroadcastEventObserver(
-            watchFaceHostApi.getContext(),
-            broadcastEventObserver
-        )
     }
 
     @UiThread
@@ -774,11 +772,8 @@
         require(watchFaceHostApi.getHandler().looper.isCurrentThread) {
             "unregisterReceivers must be called the UiThread"
         }
-        if (!registeredReceivers) {
-            return
-        }
-        registeredReceivers = false
-        BroadcastReceivers.removeBroadcastEventObserver(broadcastEventObserver)
+        broadcastsReceiver?.onDestroy()
+        broadcastsReceiver = null
     }
 
     private fun scheduleDraw() {
diff --git a/wear/wear-watchface/src/main/java/androidx/wear/watchface/WatchFaceHostApi.kt b/wear/wear-watchface/src/main/java/androidx/wear/watchface/WatchFaceHostApi.kt
index 9f074cd..55f0263 100644
--- a/wear/wear-watchface/src/main/java/androidx/wear/watchface/WatchFaceHostApi.kt
+++ b/wear/wear-watchface/src/main/java/androidx/wear/watchface/WatchFaceHostApi.kt
@@ -22,7 +22,7 @@
 import android.support.wearable.complications.ComplicationData
 import androidx.annotation.RestrictTo
 import androidx.annotation.UiThread
-import androidx.wear.complications.SystemProviders.Companion.ProviderId
+import androidx.wear.complications.SystemProviders.ProviderId
 import androidx.wear.watchface.style.data.UserStyleWireFormat
 
 /**
diff --git a/wear/wear-watchface/src/main/java/androidx/wear/watchface/WatchFaceService.kt b/wear/wear-watchface/src/main/java/androidx/wear/watchface/WatchFaceService.kt
index 2cb6fc3..b9a7ee8 100644
--- a/wear/wear-watchface/src/main/java/androidx/wear/watchface/WatchFaceService.kt
+++ b/wear/wear-watchface/src/main/java/androidx/wear/watchface/WatchFaceService.kt
@@ -50,7 +50,7 @@
 import androidx.annotation.RestrictTo
 import androidx.annotation.UiThread
 import androidx.versionedparcelable.ParcelUtils
-import androidx.wear.complications.SystemProviders.Companion.ProviderId
+import androidx.wear.complications.SystemProviders.ProviderId
 import androidx.wear.complications.data.ComplicationData
 import androidx.wear.complications.data.ComplicationType
 import androidx.wear.complications.data.toApiComplicationData
diff --git a/wear/wear-watchface/src/test/java/androidx/wear/watchface/WatchFaceServiceTest.kt b/wear/wear-watchface/src/test/java/androidx/wear/watchface/WatchFaceServiceTest.kt
index b2668c2..118cb31 100644
--- a/wear/wear-watchface/src/test/java/androidx/wear/watchface/WatchFaceServiceTest.kt
+++ b/wear/wear-watchface/src/test/java/androidx/wear/watchface/WatchFaceServiceTest.kt
@@ -624,7 +624,8 @@
         watchState.isAmbient.value = false
         testWatchFaceService.mockSystemTimeMillis = 1000L
 
-        BroadcastReceivers.sendOnMockTimeForTesting(
+        watchFaceImpl.broadcastsReceiver!!.mockTimeReceiver.onReceive(
+            context,
             Intent(WatchFaceImpl.MOCK_TIME_INTENT).apply {
                 putExtra(WatchFaceImpl.EXTRA_MOCK_TIME_SPEED_MULTIPLIER, 2.0f)
                 putExtra(WatchFaceImpl.EXTRA_MOCK_TIME_WRAPPING_MIN_TIME, -1L)
@@ -651,7 +652,8 @@
         watchState.isAmbient.value = false
         testWatchFaceService.mockSystemTimeMillis = 1000L
 
-        BroadcastReceivers.sendOnMockTimeForTesting(
+        watchFaceImpl.broadcastsReceiver!!.mockTimeReceiver.onReceive(
+            context,
             Intent(WatchFaceImpl.MOCK_TIME_INTENT).apply {
                 putExtra(WatchFaceImpl.EXTRA_MOCK_TIME_SPEED_MULTIPLIER, 2.0f)
                 putExtra(WatchFaceImpl.EXTRA_MOCK_TIME_WRAPPING_MIN_TIME, 1000L)
@@ -857,13 +859,19 @@
         )
 
         // The delay should change when battery is low.
-        BroadcastReceivers.sendOnActionBatteryLowForTesting(Intent(Intent.ACTION_BATTERY_LOW))
+        watchFaceImpl.broadcastsReceiver!!.actionBatteryLowReceiver.onReceive(
+            context,
+            Intent(Intent.ACTION_BATTERY_LOW)
+        )
         assertThat(watchFaceImpl.computeDelayTillNextFrame(0, 0)).isEqualTo(
             WatchFaceImpl.MAX_LOW_POWER_INTERACTIVE_UPDATE_RATE_MS
         )
 
         // And go back to normal when battery is OK.
-        BroadcastReceivers.sendOnActionBatteryOkayForTesting(Intent(Intent.ACTION_BATTERY_OKAY))
+        watchFaceImpl.broadcastsReceiver!!.actionBatteryOkayReceiver.onReceive(
+            context,
+            Intent(Intent.ACTION_BATTERY_OKAY)
+        )
         assertThat(watchFaceImpl.computeDelayTillNextFrame(0, 0)).isEqualTo(
             INTERACTIVE_UPDATE_RATE_MS
         )
@@ -882,13 +890,17 @@
         )
 
         // The delay should change when battery is low.
-        BroadcastReceivers.sendOnActionBatteryLowForTesting(Intent(Intent.ACTION_BATTERY_LOW))
+        watchFaceImpl.broadcastsReceiver!!.actionBatteryLowReceiver.onReceive(
+            context,
+            Intent(Intent.ACTION_BATTERY_LOW)
+        )
         assertThat(watchFaceImpl.computeDelayTillNextFrame(0, 0)).isEqualTo(
             WatchFaceImpl.MAX_LOW_POWER_INTERACTIVE_UPDATE_RATE_MS
         )
 
         // And go back to normal when power is connected.
-        BroadcastReceivers.sendOnActionPowerConnectedForTesting(
+        watchFaceImpl.broadcastsReceiver!!.actionPowerConnectedReceiver.onReceive(
+            context,
             Intent(Intent.ACTION_POWER_CONNECTED)
         )
         assertThat(watchFaceImpl.computeDelayTillNextFrame(0, 0)).isEqualTo(
@@ -2535,6 +2547,38 @@
         verify(observer).onChanged(true)
     }
 
+    @Test
+    public fun complicationsUserStyleSetting_with_setComplicationBounds() {
+        val complicationsStyleSetting = ComplicationsUserStyleSetting(
+            UserStyleSetting.Id("complications_style_setting"),
+            "AllComplications",
+            "Number and position",
+            icon = null,
+            complicationConfig = listOf(
+                ComplicationsOption(
+                    Option.Id(RIGHT_COMPLICATION),
+                    "Right",
+                    null,
+                    listOf(
+                        ComplicationOverlay.Builder(LEFT_COMPLICATION_ID)
+                            .setComplicationBounds(
+                                ComplicationBounds(RectF(10f, 10f, 20f, 20f))
+                            ).build()
+                    )
+                )
+            ),
+            affectsWatchFaceLayers = listOf(WatchFaceLayer.COMPLICATIONS)
+        )
+
+        // This should not crash.
+        initEngine(
+            WatchFaceType.DIGITAL,
+            listOf(leftComplication, rightComplication),
+            UserStyleSchema(listOf(complicationsStyleSetting)),
+            apiVersion = 4
+        )
+    }
+
     @Suppress("DEPRECATION")
     private fun getChinWindowInsetsApi25(@Px chinHeight: Int): WindowInsets =
         WindowInsets.Builder().setSystemWindowInsets(
diff --git a/wear/wear/api/current.txt b/wear/wear/api/current.txt
index eb93233..1b11fb1 100644
--- a/wear/wear/api/current.txt
+++ b/wear/wear/api/current.txt
@@ -263,6 +263,7 @@
     method public void setText(String?);
     method public void setTextColor(@ColorInt int);
     method public void setTextSize(float);
+    method public void setTypeface(android.graphics.Typeface?, int);
     method public void setTypeface(android.graphics.Typeface?);
   }
 
diff --git a/wear/wear/api/public_plus_experimental_current.txt b/wear/wear/api/public_plus_experimental_current.txt
index eb93233..1b11fb1 100644
--- a/wear/wear/api/public_plus_experimental_current.txt
+++ b/wear/wear/api/public_plus_experimental_current.txt
@@ -263,6 +263,7 @@
     method public void setText(String?);
     method public void setTextColor(@ColorInt int);
     method public void setTextSize(float);
+    method public void setTypeface(android.graphics.Typeface?, int);
     method public void setTypeface(android.graphics.Typeface?);
   }
 
diff --git a/wear/wear/api/restricted_current.txt b/wear/wear/api/restricted_current.txt
index aca9475..6e3ffdf 100644
--- a/wear/wear/api/restricted_current.txt
+++ b/wear/wear/api/restricted_current.txt
@@ -270,6 +270,7 @@
     method public void setText(String?);
     method public void setTextColor(@ColorInt int);
     method public void setTextSize(float);
+    method public void setTypeface(android.graphics.Typeface?, int);
     method public void setTypeface(android.graphics.Typeface?);
   }
 
diff --git a/wear/wear/src/androidTest/java/androidx/wear/widget/CurvedTextViewTest.kt b/wear/wear/src/androidTest/java/androidx/wear/widget/CurvedTextViewTest.kt
index 0d81777..34cec3d 100644
--- a/wear/wear/src/androidTest/java/androidx/wear/widget/CurvedTextViewTest.kt
+++ b/wear/wear/src/androidTest/java/androidx/wear/widget/CurvedTextViewTest.kt
@@ -19,6 +19,7 @@
 import android.graphics.Bitmap
 import android.graphics.Canvas
 import android.graphics.Color
+import android.graphics.Typeface
 import android.text.TextUtils
 import android.view.View
 import android.view.View.MeasureSpec
@@ -277,6 +278,36 @@
         )
     }
 
+    @Test
+    fun testStyles() {
+        doOneTest(
+            "styles_test",
+            listOf(
+                CurvedTextView(ApplicationProvider.getApplicationContext())
+                    .apply {
+                        text = "Bold"
+                        anchorAngleDegrees = 0.0f
+                        anchorType = ArcLayout.ANCHOR_START
+                        setTypeface(null, Typeface.BOLD)
+                    },
+                CurvedTextView(ApplicationProvider.getApplicationContext())
+                    .apply {
+                        text = "Italic"
+                        anchorAngleDegrees = 60.0f
+                        anchorType = ArcLayout.ANCHOR_START
+                        setTypeface(null, Typeface.ITALIC)
+                    },
+                CurvedTextView(ApplicationProvider.getApplicationContext())
+                    .apply {
+                        text = "ItalicBold"
+                        anchorAngleDegrees = 120.0f
+                        anchorType = ArcLayout.ANCHOR_START
+                        setTypeface(null, Typeface.BOLD_ITALIC)
+                    },
+            )
+        )
+    }
+
     companion object {
         private const val SCREEN_WIDTH = 390
         private const val SCREEN_HEIGHT = 390
diff --git a/wear/wear/src/main/java/androidx/wear/widget/CurvedTextView.java b/wear/wear/src/main/java/androidx/wear/widget/CurvedTextView.java
index 9cd55a9..949098a 100644
--- a/wear/wear/src/main/java/androidx/wear/widget/CurvedTextView.java
+++ b/wear/wear/src/main/java/androidx/wear/widget/CurvedTextView.java
@@ -553,7 +553,7 @@
      * bold and italic bits in the Paint if the Typeface that you provided does not have all the
      * bits in the style that you specified.
      */
-    private void setTypeface(@Nullable Typeface tf, int style) {
+    public void setTypeface(@Nullable Typeface tf, int style) {
         if (style > 0) {
             if (tf == null) {
                 tf = Typeface.defaultFromStyle(style);
@@ -578,6 +578,7 @@
                 mTypeface = tf;
             }
         }
+        doUpdate();
     }
 
     /**
diff --git a/webkit/integration-tests/testapp/build.gradle b/webkit/integration-tests/testapp/build.gradle
index 4caa18a..3442df0 100644
--- a/webkit/integration-tests/testapp/build.gradle
+++ b/webkit/integration-tests/testapp/build.gradle
@@ -13,7 +13,6 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-import static androidx.build.dependencies.DependenciesKt.*
 
 plugins {
     id("AndroidXPlugin")
@@ -24,21 +23,21 @@
     implementation("androidx.appcompat:appcompat:1.1.0")
     implementation("androidx.core:core:1.1.0")
     implementation(project(":webkit:webkit"))
-    implementation(GUAVA_ANDROID)
-    implementation(ESPRESSO_IDLING_NET)
-    implementation(ESPRESSO_IDLING_RESOURCE)
+    implementation(libs.guavaAndroid)
+    implementation(libs.espressoIdlingNet)
+    implementation(libs.espressoIdlingResource)
 
-    androidTestImplementation(ANDROIDX_TEST_EXT_JUNIT)
-    androidTestImplementation(ANDROIDX_TEST_CORE)
-    androidTestImplementation(ANDROIDX_TEST_RUNNER)
-    androidTestImplementation(ANDROIDX_TEST_RULES)
-    androidTestImplementation(ESPRESSO_CORE, excludes.espresso)
-    androidTestImplementation(ESPRESSO_CONTRIB, excludes.espresso)
-    androidTestImplementation(ESPRESSO_IDLING_RESOURCE)
-    androidTestImplementation(ESPRESSO_WEB, excludes.espresso)
-    androidTestImplementation(MOCKITO_CORE, excludes.bytebuddy)
+    androidTestImplementation(libs.testExtJunit)
+    androidTestImplementation(libs.testCore)
+    androidTestImplementation(libs.testRunner)
+    androidTestImplementation(libs.testRules)
+    androidTestImplementation(libs.espressoCore, excludes.espresso)
+    androidTestImplementation(libs.espressoContrib, excludes.espresso)
+    androidTestImplementation(libs.espressoIdlingResource)
+    androidTestImplementation(libs.espressoWeb, excludes.espresso)
+    androidTestImplementation(libs.mockitoCore, excludes.bytebuddy)
     // DexMaker has it"s own MockMaker
-    androidTestImplementation(DEXMAKER_MOCKITO, excludes.bytebuddy)
+    androidTestImplementation(libs.dexmakerMockito, excludes.bytebuddy)
 }
 
 rootProject.tasks.getByName("buildOnServer").dependsOn(project.path + ":assembleRelease")
diff --git a/webkit/webkit/build.gradle b/webkit/webkit/build.gradle
index a52b165..ff5de9e 100644
--- a/webkit/webkit/build.gradle
+++ b/webkit/webkit/build.gradle
@@ -14,7 +14,6 @@
  * limitations under the License.
  */
 
-import static androidx.build.dependencies.DependenciesKt.*
 import androidx.build.LibraryGroups
 import androidx.build.Publish
 import androidx.build.SupportConfigKt
@@ -28,15 +27,15 @@
     api("androidx.annotation:annotation:1.1.0")
     api("androidx.core:core:1.1.0")
 
-    androidTestImplementation(OKHTTP_MOCKWEBSERVER)
-    androidTestImplementation(ANDROIDX_TEST_EXT_JUNIT)
-    androidTestImplementation(ANDROIDX_TEST_CORE)
-    androidTestImplementation(ANDROIDX_TEST_RUNNER)
-    androidTestImplementation(ANDROIDX_TEST_RULES)
+    androidTestImplementation(libs.okhttpMockwebserver)
+    androidTestImplementation(libs.testExtJunit)
+    androidTestImplementation(libs.testCore)
+    androidTestImplementation(libs.testRunner)
+    androidTestImplementation(libs.testRules)
     androidTestImplementation("androidx.concurrent:concurrent-futures:1.0.0")
 
     // Hamcrest matchers:
-    androidTestImplementation(ESPRESSO_CONTRIB, excludes.espresso)
+    androidTestImplementation(libs.espressoContrib, excludes.espresso)
 }
 
 ext {
diff --git a/window/window-extensions/lint-baseline.xml b/window/window-extensions/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/window/window-extensions/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/window/window-samples/lint-baseline.xml b/window/window-samples/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/window/window-samples/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/window/window-sidecar/lint-baseline.xml b/window/window-sidecar/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/window/window-sidecar/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/window/window/api/current.txt b/window/window/api/current.txt
index 5c30487..c82eaa4 100644
--- a/window/window/api/current.txt
+++ b/window/window/api/current.txt
@@ -40,6 +40,12 @@
     method public void unregisterLayoutChangeCallback(androidx.core.util.Consumer<androidx.window.WindowLayoutInfo> callback);
   }
 
+  public interface WindowInfoRepo {
+    method public androidx.window.WindowMetrics currentWindowMetrics();
+    method public androidx.window.WindowMetrics maximumWindowMetrics();
+    method public kotlinx.coroutines.flow.Flow<androidx.window.WindowLayoutInfo> windowLayoutInfo();
+  }
+
   public final class WindowLayoutInfo {
     method public java.util.List<androidx.window.DisplayFeature> getDisplayFeatures();
     property public final java.util.List<androidx.window.DisplayFeature> displayFeatures;
@@ -66,5 +72,8 @@
     property public final android.graphics.Rect bounds;
   }
 
+  public final class WindowServices {
+  }
+
 }
 
diff --git a/window/window/api/public_plus_experimental_current.txt b/window/window/api/public_plus_experimental_current.txt
index 5c30487..c6788d2 100644
--- a/window/window/api/public_plus_experimental_current.txt
+++ b/window/window/api/public_plus_experimental_current.txt
@@ -40,6 +40,12 @@
     method public void unregisterLayoutChangeCallback(androidx.core.util.Consumer<androidx.window.WindowLayoutInfo> callback);
   }
 
+  public interface WindowInfoRepo {
+    method public androidx.window.WindowMetrics currentWindowMetrics();
+    method public androidx.window.WindowMetrics maximumWindowMetrics();
+    method public kotlinx.coroutines.flow.Flow<androidx.window.WindowLayoutInfo> windowLayoutInfo();
+  }
+
   public final class WindowLayoutInfo {
     method public java.util.List<androidx.window.DisplayFeature> getDisplayFeatures();
     property public final java.util.List<androidx.window.DisplayFeature> displayFeatures;
@@ -66,5 +72,9 @@
     property public final android.graphics.Rect bounds;
   }
 
+  public final class WindowServices {
+    method @kotlinx.coroutines.ExperimentalCoroutinesApi public static androidx.window.WindowInfoRepo windowInfoRepository(android.app.Activity);
+  }
+
 }
 
diff --git a/window/window/api/restricted_current.txt b/window/window/api/restricted_current.txt
index 5c30487..c82eaa4 100644
--- a/window/window/api/restricted_current.txt
+++ b/window/window/api/restricted_current.txt
@@ -40,6 +40,12 @@
     method public void unregisterLayoutChangeCallback(androidx.core.util.Consumer<androidx.window.WindowLayoutInfo> callback);
   }
 
+  public interface WindowInfoRepo {
+    method public androidx.window.WindowMetrics currentWindowMetrics();
+    method public androidx.window.WindowMetrics maximumWindowMetrics();
+    method public kotlinx.coroutines.flow.Flow<androidx.window.WindowLayoutInfo> windowLayoutInfo();
+  }
+
   public final class WindowLayoutInfo {
     method public java.util.List<androidx.window.DisplayFeature> getDisplayFeatures();
     property public final java.util.List<androidx.window.DisplayFeature> displayFeatures;
@@ -66,5 +72,8 @@
     property public final android.graphics.Rect bounds;
   }
 
+  public final class WindowServices {
+  }
+
 }
 
diff --git a/window/window/build.gradle b/window/window/build.gradle
index 2fe4f79..cfb74ad 100644
--- a/window/window/build.gradle
+++ b/window/window/build.gradle
@@ -17,19 +17,9 @@
 import androidx.build.LibraryGroups
 import androidx.build.LibraryVersions
 import androidx.build.Publish
+import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
 
-import static androidx.build.dependencies.DependenciesKt.ANDROIDX_TEST_CORE
-import static androidx.build.dependencies.DependenciesKt.ANDROIDX_TEST_EXT_JUNIT
-import static androidx.build.dependencies.DependenciesKt.ANDROIDX_TEST_RULES
-import static androidx.build.dependencies.DependenciesKt.ANDROIDX_TEST_RUNNER
-import static androidx.build.dependencies.DependenciesKt.DEXMAKER_MOCKITO
-import static androidx.build.dependencies.DependenciesKt.JUNIT
-import static androidx.build.dependencies.DependenciesKt.MOCKITO_CORE
-import static androidx.build.dependencies.DependenciesKt.MOCKITO_KOTLIN
-import static androidx.build.dependencies.DependenciesKt.ROBOLECTRIC
-import static androidx.build.dependencies.DependenciesKt.TRUTH
-import static androidx.build.dependencies.DependenciesKt.getKOTLIN_COROUTINES_ANDROID
-import static androidx.build.dependencies.DependenciesKt.getKOTLIN_STDLIB
+import static androidx.build.dependencies.DependenciesKt.*
 
 plugins {
     id("AndroidXPlugin")
@@ -38,6 +28,9 @@
 }
 
 android {
+    defaultConfig {
+        multiDexEnabled = true
+    }
     buildTypes.all {
         consumerProguardFiles "proguard-rules.pro"
     }
@@ -69,6 +62,7 @@
     testImplementation(ROBOLECTRIC)
     testImplementation(MOCKITO_CORE)
     testImplementation(MOCKITO_KOTLIN)
+    testImplementation(KOTLIN_COROUTINES_TEST)
     testImplementation(compileOnly(project(":window:window-extensions")))
     testImplementation(compileOnly(project(":window:window-sidecar")))
 
@@ -78,6 +72,8 @@
     androidTestImplementation(DEXMAKER_MOCKITO, excludes.bytebuddy)
     androidTestImplementation(MOCKITO_CORE, excludes.bytebuddy)
     androidTestImplementation(MOCKITO_KOTLIN, excludes.bytebuddy)
+    androidTestImplementation(KOTLIN_COROUTINES_TEST)
+    androidTestImplementation(MULTIDEX)
     androidTestImplementation(TRUTH)
     androidTestImplementation(compileOnly(project(":window:window-extensions")))
     androidTestImplementation(compileOnly(project(":window:window-sidecar")))
@@ -95,3 +91,10 @@
     // Sidecar interface.
     failOnDeprecationWarnings = false
 }
+
+// Allow usage of Kotlin's @OptIn.
+tasks.withType(KotlinCompile).configureEach {
+    kotlinOptions {
+        freeCompilerArgs += ["-Xopt-in=kotlin.RequiresOptIn"]
+    }
+}
diff --git a/window/window/src/androidTest/java/androidx/window/ExtensionCompatDeviceTest.kt b/window/window/src/androidTest/java/androidx/window/ExtensionCompatDeviceTest.kt
index 1847ea0..6eb3985 100644
--- a/window/window/src/androidTest/java/androidx/window/ExtensionCompatDeviceTest.kt
+++ b/window/window/src/androidTest/java/androidx/window/ExtensionCompatDeviceTest.kt
@@ -16,7 +16,6 @@
 package androidx.window
 
 import android.content.Context
-import android.content.Intent
 import androidx.test.core.app.ApplicationProvider
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.LargeTest
@@ -48,14 +47,14 @@
 
     @Test
     override fun testWindowLayoutCallback() {
-        val activity = activityTestRule.launchActivity(Intent())
-        val windowToken = getActivityWindowToken(activity)
-        assertNotNull(windowToken)
-        val callbackInterface = mock<ExtensionCallbackInterface>()
-        extensionCompat.setExtensionCallback(callbackInterface)
-        extensionCompat.onWindowLayoutChangeListenerAdded(activity)
-        verify(callbackInterface)
-            .onWindowLayoutChanged(any(), any())
+        activityTestRule.scenario.onActivity { activity ->
+            val windowToken = getActivityWindowToken(activity)
+            assertNotNull(windowToken)
+            val callbackInterface = mock<ExtensionCallbackInterface>()
+            extensionCompat.setExtensionCallback(callbackInterface)
+            extensionCompat.onWindowLayoutChangeListenerAdded(activity)
+            verify(callbackInterface).onWindowLayoutChanged(any(), any())
+        }
     }
 
     private fun assumeExtensionV1_0() {
diff --git a/window/window/src/androidTest/java/androidx/window/ExtensionTest.kt b/window/window/src/androidTest/java/androidx/window/ExtensionTest.kt
index b9fd0fe..2a8267c 100644
--- a/window/window/src/androidTest/java/androidx/window/ExtensionTest.kt
+++ b/window/window/src/androidTest/java/androidx/window/ExtensionTest.kt
@@ -13,19 +13,18 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-@file:Suppress("DEPRECATION") // TODO(b/185594766) Migrate from ActivityTestRule
 
 package androidx.window
 
 import android.content.Context
-import android.content.Intent
 import android.content.pm.ActivityInfo
 import android.content.res.Configuration
 import android.graphics.Rect
+import androidx.test.core.app.ActivityScenario
 import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.rules.ActivityScenarioRule
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.LargeTest
-import androidx.test.rule.ActivityTestRule
 import androidx.window.ExtensionInterfaceCompat.ExtensionCallbackInterface
 import androidx.window.TestActivity.Companion.resetResumeCounter
 import androidx.window.TestActivity.Companion.waitForOnResume
@@ -51,9 +50,8 @@
 
     private lateinit var context: Context
 
-    private val configHandlingActivityTestRule = ActivityTestRule(
-        TestConfigChangeHandlingActivity::class.java, false, true
-    )
+    private val configHandlingActivityTestRule =
+        ActivityScenarioRule(TestConfigChangeHandlingActivity::class.java)
 
     @Before
     public fun setUp() {
@@ -83,22 +81,24 @@
         val extension = ExtensionWindowBackend.initAndVerifyExtension(context)
         val callbackInterface = mock<ExtensionCallbackInterface>()
         extension!!.setExtensionCallback(callbackInterface)
-        val activity = activityTestRule.launchActivity(Intent())
-        extension.onWindowLayoutChangeListenerAdded(activity)
-        assertTrue("Layout must happen after launch", activity.waitForLayout())
-        verify(callbackInterface, atLeastOnce()).onWindowLayoutChanged(
-            any(),
-            argThat(WindowLayoutInfoValidator(activity))
-        )
+        activityTestRule.scenario.onActivity { activity ->
+            extension.onWindowLayoutChangeListenerAdded(activity)
+            assertTrue("Layout must happen after launch", activity.waitForLayout())
+            verify(callbackInterface, atLeastOnce()).onWindowLayoutChanged(
+                any(),
+                argThat(WindowLayoutInfoValidator(activity))
+            )
+        }
     }
 
     @Test
     public fun testRegisterWindowLayoutChangeListener() {
         assumeExtensionV10_V01()
         val extension = ExtensionWindowBackend.initAndVerifyExtension(context)
-        val activity = activityTestRule.launchActivity(Intent())
-        extension!!.onWindowLayoutChangeListenerAdded(activity)
-        extension.onWindowLayoutChangeListenerRemoved(activity)
+        activityTestRule.scenario.onActivity { activity ->
+            extension!!.onWindowLayoutChangeListenerAdded(activity)
+            extension.onWindowLayoutChangeListenerRemoved(activity)
+        }
     }
 
     @Test
@@ -108,36 +108,37 @@
         val callbackInterface = mock<ExtensionCallbackInterface>()
 
         extension!!.setExtensionCallback(callbackInterface)
-        val activity = configHandlingActivityTestRule.launchActivity(Intent())
-        extension.onWindowLayoutChangeListenerAdded(activity)
-        activity.resetLayoutCounter()
-        activity.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
-        activity.waitForLayout()
-        if (activity.resources.configuration.orientation
-            != Configuration.ORIENTATION_PORTRAIT
-        ) {
-            // Orientation change did not occur on this device config. Skipping the test.
-            return
-        }
-        activity.resetLayoutCounter()
-        activity.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE
-        val layoutHappened = activity.waitForLayout()
-        if (activity.resources.configuration.orientation
-            != Configuration.ORIENTATION_LANDSCAPE
-        ) {
-            // Orientation change did not occur on this device config. Skipping the test.
-            return
-        }
-        assertTrue("Layout must happen after orientation change", layoutHappened)
-        if (!isSidecar) {
-            verify(callbackInterface, atLeastOnce())
-                .onWindowLayoutChanged(
-                    any(),
-                    argThat(DistinctWindowLayoutInfoMatcher())
-                )
-        } else {
-            verify(callbackInterface, atLeastOnce())
-                .onWindowLayoutChanged(any(), any())
+        val scenario = ActivityScenario.launch(TestConfigChangeHandlingActivity::class.java)
+        scenario.onActivity { activity ->
+            extension.onWindowLayoutChangeListenerAdded(activity)
+            activity.resetLayoutCounter()
+            activity.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
+            activity.waitForLayout()
+            val activityOrientation = activity.resources.configuration.orientation
+            if (activityOrientation != Configuration.ORIENTATION_PORTRAIT) {
+                // Orientation change did not occur on this device config. Skipping the test.
+                return@onActivity
+            }
+            activity.resetLayoutCounter()
+            activity.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE
+            val layoutHappened = activity.waitForLayout()
+            if (activity.resources.configuration.orientation
+                != Configuration.ORIENTATION_LANDSCAPE
+            ) {
+                // Orientation change did not occur on this device config. Skipping the test.
+                return@onActivity
+            }
+            assertTrue("Layout must happen after orientation change", layoutHappened)
+            if (!isSidecar) {
+                verify(callbackInterface, atLeastOnce())
+                    .onWindowLayoutChanged(
+                        any(),
+                        argThat(DistinctWindowLayoutInfoMatcher())
+                    )
+            } else {
+                verify(callbackInterface, atLeastOnce())
+                    .onWindowLayoutChanged(any(), any())
+            }
         }
     }
 
@@ -147,38 +148,37 @@
         val extension = ExtensionWindowBackend.initAndVerifyExtension(context)
         val callbackInterface = mock<ExtensionCallbackInterface>()
         extension!!.setExtensionCallback(callbackInterface)
-        var activity = activityTestRule.launchActivity(Intent())
-        extension.onWindowLayoutChangeListenerAdded(activity)
-        activity.resetLayoutCounter()
-        activity.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
-        activity = activityTestRule.activity
-        activity.waitForLayout()
-        if (activity.resources.configuration.orientation
-            != Configuration.ORIENTATION_PORTRAIT
-        ) {
-            // Orientation change did not occur on this device config. Skipping the test.
-            return
-        }
-        resetResumeCounter()
-        activity.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE
-        waitForOnResume()
-        activity = activityTestRule.activity
-        activity.waitForLayout()
-        if (activity.resources.configuration.orientation
-            != Configuration.ORIENTATION_LANDSCAPE
-        ) {
-            // Orientation change did not occur on this device config. Skipping the test.
-            return
-        }
-        if (!isSidecar) {
-            verify(callbackInterface, atLeastOnce())
-                .onWindowLayoutChanged(
-                    any(),
-                    argThat(DistinctWindowLayoutInfoMatcher())
-                )
-        } else {
-            verify(callbackInterface, atLeastOnce())
-                .onWindowLayoutChanged(any(), any())
+        activityTestRule.scenario.onActivity { activity ->
+            extension.onWindowLayoutChangeListenerAdded(activity)
+            activity.resetLayoutCounter()
+            activity.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
+            activity.waitForLayout()
+            if (activity.resources.configuration.orientation
+                != Configuration.ORIENTATION_PORTRAIT
+            ) {
+                // Orientation change did not occur on this device config. Skipping the test.
+                return@onActivity
+            }
+            resetResumeCounter()
+            activity.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE
+            waitForOnResume()
+            activity.waitForLayout()
+            if (activity.resources.configuration.orientation
+                != Configuration.ORIENTATION_LANDSCAPE
+            ) {
+                // Orientation change did not occur on this device config. Skipping the test.
+                return@onActivity
+            }
+            if (!isSidecar) {
+                verify(callbackInterface, atLeastOnce())
+                    .onWindowLayoutChanged(
+                        any(),
+                        argThat(DistinctWindowLayoutInfoMatcher())
+                    )
+            } else {
+                verify(callbackInterface, atLeastOnce())
+                    .onWindowLayoutChanged(any(), any())
+            }
         }
     }
 
diff --git a/window/window/src/androidTest/java/androidx/window/ExtensionWindowBackendTest.kt b/window/window/src/androidTest/java/androidx/window/ExtensionWindowBackendTest.kt
index 0154f5c..51d5e6b 100644
--- a/window/window/src/androidTest/java/androidx/window/ExtensionWindowBackendTest.kt
+++ b/window/window/src/androidTest/java/androidx/window/ExtensionWindowBackendTest.kt
@@ -17,7 +17,6 @@
 
 import android.app.Activity
 import android.content.Context
-import android.content.Intent
 import android.graphics.Rect
 import androidx.core.util.Consumer
 import androidx.test.core.app.ApplicationProvider
@@ -82,84 +81,91 @@
 
     @Test
     public fun testRegisterLayoutChangeCallback() {
-        val backend = ExtensionWindowBackend.getInstance(context)
-        backend.windowExtension = mock<ExtensionInterfaceCompat>()
+        activityTestRule.scenario.onActivity { activity ->
+            val backend = ExtensionWindowBackend.getInstance(context)
+            backend.windowExtension = mock()
+            // Check registering the layout change callback
+            val consumer = mock<Consumer<WindowLayoutInfo>>()
 
-        // Check registering the layout change callback
-        val consumer = mock<Consumer<WindowLayoutInfo>>()
-        val activity = activityTestRule.launchActivity(Intent())
-        backend.registerLayoutChangeCallback(activity, { obj: Runnable -> obj.run() }, consumer)
-        assertEquals(1, backend.windowLayoutChangeCallbacks.size.toLong())
-        verify(backend.windowExtension!!).onWindowLayoutChangeListenerAdded(activity)
+            backend.registerLayoutChangeCallback(activity, { obj: Runnable -> obj.run() }, consumer)
+            assertEquals(1, backend.windowLayoutChangeCallbacks.size.toLong())
+            verify(backend.windowExtension!!).onWindowLayoutChangeListenerAdded(activity)
 
-        // Check unregistering the layout change callback
-        backend.unregisterLayoutChangeCallback(consumer)
-        assertTrue(backend.windowLayoutChangeCallbacks.isEmpty())
-        verify(backend.windowExtension!!).onWindowLayoutChangeListenerRemoved(
-            eq(activity)
-        )
+            // Check unregistering the layout change callback
+            backend.unregisterLayoutChangeCallback(consumer)
+            assertTrue(backend.windowLayoutChangeCallbacks.isEmpty())
+            verify(backend.windowExtension!!).onWindowLayoutChangeListenerRemoved(
+                eq(activity)
+            )
+        }
     }
 
     @Test
     public fun testRegisterLayoutChangeCallback_callsExtensionOnce() {
-        val backend = ExtensionWindowBackend.getInstance(context)
-        backend.windowExtension = mock()
+        activityTestRule.scenario.onActivity { activity ->
+            val backend = ExtensionWindowBackend.getInstance(context)
+            backend.windowExtension = mock()
 
-        // Check registering the layout change callback
-        val consumer = mock<Consumer<WindowLayoutInfo>>()
-        val activity = activityTestRule.launchActivity(Intent())
-        backend.registerLayoutChangeCallback(activity, Runnable::run, consumer)
-        backend.registerLayoutChangeCallback(activity, Runnable::run, mock())
-        assertEquals(2, backend.windowLayoutChangeCallbacks.size.toLong())
-        verify(backend.windowExtension!!).onWindowLayoutChangeListenerAdded(activity)
+            // Check registering the layout change callback
+            val consumer = mock<Consumer<WindowLayoutInfo>>()
 
-        // Check unregistering the layout change callback
-        backend.unregisterLayoutChangeCallback(consumer)
-        assertEquals(1, backend.windowLayoutChangeCallbacks.size.toLong())
-        verify(backend.windowExtension!!, times(0))
-            .onWindowLayoutChangeListenerRemoved(eq(activity))
+            backend.registerLayoutChangeCallback(activity, Runnable::run, consumer)
+            backend.registerLayoutChangeCallback(activity, Runnable::run, mock())
+            assertEquals(2, backend.windowLayoutChangeCallbacks.size.toLong())
+            verify(backend.windowExtension!!).onWindowLayoutChangeListenerAdded(activity)
+
+            // Check unregistering the layout change callback
+            backend.unregisterLayoutChangeCallback(consumer)
+            assertEquals(1, backend.windowLayoutChangeCallbacks.size.toLong())
+            verify(backend.windowExtension!!, times(0))
+                .onWindowLayoutChangeListenerRemoved(eq(activity))
+        }
     }
 
     @Test
     public fun testRegisterLayoutChangeCallback_clearListeners() {
-        val backend = ExtensionWindowBackend.getInstance(context)
-        backend.windowExtension = mock()
+        activityTestRule.scenario.onActivity { activity ->
+            val backend = ExtensionWindowBackend.getInstance(context)
+            backend.windowExtension = mock()
 
-        // Check registering the layout change callback
-        val firstConsumer = mock<Consumer<WindowLayoutInfo>>()
-        val secondConsumer = mock<Consumer<WindowLayoutInfo>>()
-        val activity = activityTestRule.launchActivity(Intent())
-        backend.registerLayoutChangeCallback(
-            activity,
-            { obj: Runnable -> obj.run() },
-            firstConsumer
-        )
-        backend.registerLayoutChangeCallback(
-            activity,
-            { obj: Runnable -> obj.run() },
-            secondConsumer
-        )
+            // Check registering the layout change callback
+            val firstConsumer = mock<Consumer<WindowLayoutInfo>>()
+            val secondConsumer = mock<Consumer<WindowLayoutInfo>>()
 
-        // Check unregistering the layout change callback
-        backend.unregisterLayoutChangeCallback(firstConsumer)
-        backend.unregisterLayoutChangeCallback(secondConsumer)
-        assertTrue(backend.windowLayoutChangeCallbacks.isEmpty())
-        verify(backend.windowExtension!!).onWindowLayoutChangeListenerRemoved(activity)
+            backend.registerLayoutChangeCallback(
+                activity,
+                { obj: Runnable -> obj.run() },
+                firstConsumer
+            )
+            backend.registerLayoutChangeCallback(
+                activity,
+                { obj: Runnable -> obj.run() },
+                secondConsumer
+            )
+
+            // Check unregistering the layout change callback
+            backend.unregisterLayoutChangeCallback(firstConsumer)
+            backend.unregisterLayoutChangeCallback(secondConsumer)
+            assertTrue(backend.windowLayoutChangeCallbacks.isEmpty())
+            verify(backend.windowExtension!!).onWindowLayoutChangeListenerRemoved(activity)
+        }
     }
 
     @Test
     public fun testLayoutChangeCallback_emitNewValue() {
-        val backend = ExtensionWindowBackend.getInstance(context)
-        backend.windowExtension = mock<ExtensionInterfaceCompat>()
+        activityTestRule.scenario.onActivity { activity ->
+            val backend = ExtensionWindowBackend.getInstance(context)
+            backend.windowExtension = mock()
 
-        // Check that callbacks from the extension are propagated correctly
-        val consumer = mock<Consumer<WindowLayoutInfo>>()
-        val activity = activityTestRule.launchActivity(Intent())
-        backend.registerLayoutChangeCallback(activity, { obj: Runnable -> obj.run() }, consumer)
-        val windowLayoutInfo = newTestWindowLayoutInfo()
-        val backendListener = backend.ExtensionListenerImpl()
-        backendListener.onWindowLayoutChanged(activity, windowLayoutInfo)
-        verify(consumer).accept(eq(windowLayoutInfo))
+            // Check that callbacks from the extension are propagated correctly
+            val consumer = mock<Consumer<WindowLayoutInfo>>()
+
+            backend.registerLayoutChangeCallback(activity, { obj: Runnable -> obj.run() }, consumer)
+            val windowLayoutInfo = newTestWindowLayoutInfo()
+            val backendListener = backend.ExtensionListenerImpl()
+            backendListener.onWindowLayoutChanged(activity, windowLayoutInfo)
+            verify(consumer).accept(eq(windowLayoutInfo))
+        }
     }
 
     @Test
@@ -185,14 +191,6 @@
         override fun accept(t: T) {
             values.add(t)
         }
-
-        fun allValues(): List<T> {
-            return values
-        }
-
-        fun lastValue(): T {
-            return values.last()
-        }
     }
 
     internal companion object {
diff --git a/window/window/src/androidTest/java/androidx/window/SidecarCompatDeviceTest.kt b/window/window/src/androidTest/java/androidx/window/SidecarCompatDeviceTest.kt
index 2f5e3fc..a3a0a0e 100644
--- a/window/window/src/androidTest/java/androidx/window/SidecarCompatDeviceTest.kt
+++ b/window/window/src/androidTest/java/androidx/window/SidecarCompatDeviceTest.kt
@@ -19,7 +19,6 @@
 package androidx.window
 
 import android.content.Context
-import android.content.Intent
 import androidx.test.core.app.ApplicationProvider
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.LargeTest
@@ -46,27 +45,27 @@
 public class SidecarCompatDeviceTest : WindowTestBase(), CompatDeviceTestInterface {
 
     private lateinit var sidecarCompat: SidecarCompat
-    private lateinit var testActivity: TestActivity
 
     @Before
     public fun setUp() {
         assumeExtensionV01()
         sidecarCompat = SidecarCompat(ApplicationProvider.getApplicationContext() as Context)
-        testActivity = activityTestRule.launchActivity(Intent())
     }
 
     @Test
     override fun testWindowLayoutCallback() {
-        val windowToken = getActivityWindowToken(testActivity)
-        assertNotNull(windowToken)
-        val callbackInterface = mock<ExtensionCallbackInterface>()
-        sidecarCompat.setExtensionCallback(callbackInterface)
-        sidecarCompat.onWindowLayoutChangeListenerAdded(testActivity)
-        val sidecarWindowLayoutInfo = sidecarCompat.sidecar!!.getWindowLayoutInfo(windowToken)
-        verify(callbackInterface, atLeastOnce()).onWindowLayoutChanged(
-            any(),
-            argThat(SidecarMatcher(sidecarWindowLayoutInfo))
-        )
+        activityTestRule.scenario.onActivity { testActivity ->
+            val windowToken = getActivityWindowToken(testActivity)
+            assertNotNull(windowToken)
+            val callbackInterface = mock<ExtensionCallbackInterface>()
+            sidecarCompat.setExtensionCallback(callbackInterface)
+            sidecarCompat.onWindowLayoutChangeListenerAdded(testActivity)
+            val sidecarWindowLayoutInfo = sidecarCompat.sidecar!!.getWindowLayoutInfo(windowToken)
+            verify(callbackInterface, atLeastOnce()).onWindowLayoutChanged(
+                any(),
+                argThat(SidecarMatcher(sidecarWindowLayoutInfo))
+            )
+        }
     }
 
     private fun assumeExtensionV01() {
diff --git a/window/window/src/androidTest/java/androidx/window/WindowBackendTest.kt b/window/window/src/androidTest/java/androidx/window/WindowBackendTest.kt
index adc63a5..fce69cd 100644
--- a/window/window/src/androidTest/java/androidx/window/WindowBackendTest.kt
+++ b/window/window/src/androidTest/java/androidx/window/WindowBackendTest.kt
@@ -16,7 +16,6 @@
 package androidx.window
 
 import android.app.Activity
-import android.content.Intent
 import android.graphics.Rect
 import androidx.core.util.Consumer
 import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -41,13 +40,14 @@
     public fun testFakeWindowBackend() {
         val windowLayoutInfo = newTestWindowLayout()
         val windowBackend: WindowBackend = FakeWindowBackend(windowLayoutInfo)
-        val activity = activityTestRule.launchActivity(Intent())
-        val wm = WindowManager(activity, windowBackend)
-        val layoutInfoConsumer: Consumer<WindowLayoutInfo> = mock(
-            WindowLayoutInfoConsumer::class.java
-        )
-        wm.registerLayoutChangeCallback(MoreExecutors.directExecutor(), layoutInfoConsumer)
-        verify(layoutInfoConsumer).accept(windowLayoutInfo)
+        activityTestRule.scenario.onActivity { activity ->
+            val wm = WindowManager(activity, windowBackend)
+            val layoutInfoConsumer: Consumer<WindowLayoutInfo> = mock(
+                WindowLayoutInfoConsumer::class.java
+            )
+            wm.registerLayoutChangeCallback(MoreExecutors.directExecutor(), layoutInfoConsumer)
+            verify(layoutInfoConsumer).accept(windowLayoutInfo)
+        }
     }
 
     private fun newTestWindowLayout(): WindowLayoutInfo {
diff --git a/window/window/src/androidTest/java/androidx/window/WindowInfoRepoImpTest.kt b/window/window/src/androidTest/java/androidx/window/WindowInfoRepoImpTest.kt
new file mode 100644
index 0000000..724e6e3
--- /dev/null
+++ b/window/window/src/androidTest/java/androidx/window/WindowInfoRepoImpTest.kt
@@ -0,0 +1,122 @@
+/*
+ * 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.window
+
+import android.app.Activity
+import androidx.core.util.Consumer
+import androidx.test.ext.junit.rules.ActivityScenarioRule
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.test.TestCoroutineScope
+import kotlinx.coroutines.test.runBlockingTest
+import org.junit.Assert.assertEquals
+import org.junit.Rule
+import org.junit.Test
+import java.util.concurrent.Executor
+
+@OptIn(ExperimentalCoroutinesApi::class)
+public class WindowInfoRepoImpTest {
+
+    @get:Rule
+    public val activityScenario: ActivityScenarioRule<TestActivity> =
+        ActivityScenarioRule(TestActivity::class.java)
+
+    private val testScope = TestCoroutineScope()
+
+    @Test
+    public fun testGetCurrentWindowMetrics() {
+        activityScenario.scenario.onActivity { testActivity ->
+            val windowBoundsHelper = WindowBoundsHelper.instance
+            val repo = WindowInfoRepoImp(
+                testActivity,
+                windowBoundsHelper,
+                FakeWindowBackend()
+            )
+            val expectedBounds = windowBoundsHelper.computeCurrentWindowBounds(testActivity)
+            val expected = WindowMetrics(expectedBounds)
+            val actual = repo.currentWindowMetrics()
+            assertEquals(expected, actual)
+        }
+    }
+
+    @Test
+    public fun testGetMaximumWindowMetrics() {
+        activityScenario.scenario.onActivity { testActivity ->
+            val windowBoundsHelper = WindowBoundsHelper.instance
+            val repo = WindowInfoRepoImp(
+                testActivity,
+                windowBoundsHelper,
+                FakeWindowBackend()
+            )
+            val expectedBounds = windowBoundsHelper.computeMaximumWindowBounds(testActivity)
+            val expected = WindowMetrics(expectedBounds)
+            val actual = repo.maximumWindowMetrics()
+            assertEquals(expected, actual)
+        }
+    }
+
+    @Test
+    public fun testWindowLayoutFeatures(): Unit = testScope.runBlockingTest {
+        activityScenario.scenario.onActivity { testActivity ->
+            val windowBoundsHelper = WindowBoundsHelper.instance
+            val fakeBackend = FakeWindowBackend()
+            val repo = WindowInfoRepoImp(
+                testActivity,
+                windowBoundsHelper,
+                fakeBackend
+            )
+            val collector = TestConsumer<WindowLayoutInfo>()
+            testScope.launch {
+                repo.windowLayoutInfo().collect(collector::accept)
+            }
+            fakeBackend.triggerSignal(WindowLayoutInfo(emptyList()))
+            collector.assertValue(WindowLayoutInfo(emptyList()))
+        }
+    }
+
+    private class FakeWindowBackend : WindowBackend {
+
+        private class CallbackHolder(
+            val executor: Executor,
+            val callback: Consumer<WindowLayoutInfo>
+        ) : Consumer<WindowLayoutInfo> {
+
+            override fun accept(t: WindowLayoutInfo?) {
+                executor.execute { callback.accept(t) }
+            }
+        }
+
+        private val consumers = mutableListOf<CallbackHolder>()
+
+        fun triggerSignal(info: WindowLayoutInfo) {
+            consumers.forEach { consumer -> consumer.accept(info) }
+        }
+
+        override fun registerLayoutChangeCallback(
+            activity: Activity,
+            executor: Executor,
+            callback: Consumer<WindowLayoutInfo>
+        ) {
+            consumers.add(CallbackHolder(executor, callback))
+        }
+
+        override fun unregisterLayoutChangeCallback(callback: Consumer<WindowLayoutInfo>) {
+            consumers.removeIf { it.callback == callback }
+        }
+    }
+}
\ No newline at end of file
diff --git a/window/window/src/androidTest/java/androidx/window/WindowTestBase.kt b/window/window/src/androidTest/java/androidx/window/WindowTestBase.kt
index 1f5ef28..ac49fa1 100644
--- a/window/window/src/androidTest/java/androidx/window/WindowTestBase.kt
+++ b/window/window/src/androidTest/java/androidx/window/WindowTestBase.kt
@@ -13,21 +13,21 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-@file:Suppress("DEPRECATION") // TODO(b/185594766) Need to migrate to ActivityScenario
 
 package androidx.window
 
 import android.app.Activity
 import android.os.IBinder
-import androidx.test.rule.ActivityTestRule
+import androidx.test.ext.junit.rules.ActivityScenarioRule
+import org.junit.Rule
 
 /**
  * Base class for all tests in the module.
  */
 public open class WindowTestBase {
-    @JvmField
-    public val activityTestRule: ActivityTestRule<TestActivity> =
-        ActivityTestRule(TestActivity::class.java, false, true)
+    @get:Rule
+    public val activityTestRule: ActivityScenarioRule<TestActivity> =
+        ActivityScenarioRule(TestActivity::class.java)
 
     public companion object {
         @JvmStatic
diff --git a/window/window/src/main/java/androidx/window/ExtensionWindowBackend.kt b/window/window/src/main/java/androidx/window/ExtensionWindowBackend.kt
index 24abe4e..477c79d 100644
--- a/window/window/src/main/java/androidx/window/ExtensionWindowBackend.kt
+++ b/window/window/src/main/java/androidx/window/ExtensionWindowBackend.kt
@@ -141,7 +141,7 @@
     }
 
     /**
-     * Wrapper around [<] that also includes the [Executor]
+     * Wrapper around [Consumer<WindowLayoutInfo>] that also includes the [Executor]
      * on which the callback should run and the [Activity].
      */
     internal class WindowLayoutChangeCallbackWrapper(
diff --git a/window/window/src/main/java/androidx/window/WindowInfoRepo.kt b/window/window/src/main/java/androidx/window/WindowInfoRepo.kt
new file mode 100644
index 0000000..32ea8e3
--- /dev/null
+++ b/window/window/src/main/java/androidx/window/WindowInfoRepo.kt
@@ -0,0 +1,77 @@
+/*
+ * 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.window
+
+import android.content.Context
+import kotlinx.coroutines.flow.Flow
+
+/**
+ * An interface to provide all the relevant info about a [android.view.Window].
+ */
+public interface WindowInfoRepo {
+
+    /**
+     * Returns the [WindowMetrics] according to the current system state.
+     *
+     *
+     * The metrics describe the size of the area the window would occupy with
+     * [MATCH_PARENT][android.view.WindowManager.LayoutParams.MATCH_PARENT] width and height
+     * and any combination of flags that would allow the window to extend behind display cutouts.
+     *
+     *
+     * The value of this is based on the **current** windowing state of the system. For
+     * example, for activities in multi-window mode, the metrics returned are based on the
+     * current bounds that the user has selected for the [Activity][android.app.Activity]'s
+     * window.
+     *
+     * @see maximumWindowMetrics
+     * @see android.view.WindowManager.getCurrentWindowMetrics
+     */
+    public fun currentWindowMetrics(): WindowMetrics
+
+    /**
+     * Returns the largest [WindowMetrics] an app may expect in the current system state.
+     *
+     *
+     * The metrics describe the size of the largest potential area the window might occupy with
+     * [MATCH_PARENT][android.view.WindowManager.LayoutParams.MATCH_PARENT] width and height
+     * and any combination of flags that would allow the window to extend behind display cutouts.
+     *
+     *
+     * The value of this is based on the largest **potential** windowing state of the system.
+     * For example, for activities in multi-window mode the metrics returned are based on what the
+     * bounds would be if the user expanded the window to cover the entire screen.
+     *
+     *
+     * Note that this might still be smaller than the size of the physical display if certain
+     * areas of the display are not available to windows created for the associated [Context].
+     * For example, devices with foldable displays that wrap around the enclosure may split the
+     * physical display into different regions, one for the front and one for the back, each acting
+     * as different logical displays. In this case [.getMaximumWindowMetrics] would return
+     * the region describing the side of the device the associated [context&#39;s][Context]
+     * window is placed.
+     *
+     * @see currentWindowMetrics
+     * @see android.view.WindowManager.getMaximumWindowMetrics
+     */
+    public fun maximumWindowMetrics(): WindowMetrics
+
+    /**
+     * A [Flow] of [WindowLayoutInfo] that contains all the available features.
+     */
+    public fun windowLayoutInfo(): Flow<WindowLayoutInfo>
+}
diff --git a/window/window/src/main/java/androidx/window/WindowInfoRepoImp.kt b/window/window/src/main/java/androidx/window/WindowInfoRepoImp.kt
new file mode 100644
index 0000000..817459f
--- /dev/null
+++ b/window/window/src/main/java/androidx/window/WindowInfoRepoImp.kt
@@ -0,0 +1,104 @@
+/*
+ * 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.window
+
+import android.app.Activity
+import android.content.Context
+import androidx.core.util.Consumer
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.channels.Channel.Factory.UNLIMITED
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.buffer
+import kotlinx.coroutines.flow.callbackFlow
+
+/**
+ * An implementation of [WindowInfoRepo] that provides the [WindowLayoutInfo] and
+ * [WindowMetrics] for the given [Activity].
+ *
+ * @param activity that the provided window is based on.
+ * @param windowBoundsHelper a helper to calculate the [WindowMetrics] for the [Activity].
+ * @param windowBackend a helper to provide the [WindowLayoutInfo].
+ */
+@ExperimentalCoroutinesApi
+internal class WindowInfoRepoImp(
+    private val activity: Activity,
+    private val windowBoundsHelper: WindowBoundsHelper,
+    private val windowBackend: WindowBackend
+) : WindowInfoRepo {
+
+    /**
+     * Returns the [WindowMetrics] according to the current system state.
+     *
+     *
+     * The metrics describe the size of the area the window would occupy with
+     * [MATCH_PARENT][android.view.WindowManager.LayoutParams.MATCH_PARENT] width and height
+     * and any combination of flags that would allow the window to extend behind display cutouts.
+     *
+     *
+     * The value of this is based on the **current** windowing state of the system. For
+     * example, for activities in multi-window mode, the metrics returned are based on the
+     * current bounds that the user has selected for the [Activity][android.app.Activity]'s
+     * window.
+     *
+     * @see maximumWindowMetrics
+     * @see android.view.WindowManager.getCurrentWindowMetrics
+     */
+    override fun currentWindowMetrics(): WindowMetrics {
+        return WindowMetrics(windowBoundsHelper.computeCurrentWindowBounds(activity))
+    }
+
+    /**
+     * Returns the largest [WindowMetrics] an app may expect in the current system state.
+     *
+     *
+     * The metrics describe the size of the largest potential area the window might occupy with
+     * [MATCH_PARENT][android.view.WindowManager.LayoutParams.MATCH_PARENT] width and height
+     * and any combination of flags that would allow the window to extend behind display cutouts.
+     *
+     *
+     * The value of this is based on the largest **potential** windowing state of the system.
+     * For example, for activities in multi-window mode the metrics returned are based on what the
+     * bounds would be if the user expanded the window to cover the entire screen.
+     *
+     *
+     * Note that this might still be smaller than the size of the physical display if certain
+     * areas of the display are not available to windows created for the associated [Context].
+     * For example, devices with foldable displays that wrap around the enclosure may split the
+     * physical display into different regions, one for the front and one for the back, each acting
+     * as different logical displays. In this case [.getMaximumWindowMetrics] would return
+     * the region describing the side of the device the associated [context&#39;s][Context]
+     * window is placed.
+     *
+     * @see currentWindowMetrics
+     * @see android.view.WindowManager.getMaximumWindowMetrics
+     */
+    override fun maximumWindowMetrics(): WindowMetrics {
+        return WindowMetrics(windowBoundsHelper.computeMaximumWindowBounds(activity))
+    }
+
+    /**
+     * A [Flow] of window layout changes in the current visual [Context].
+     *
+     * @see Activity.onAttachedToWindow
+     */
+    override fun windowLayoutInfo(): Flow<WindowLayoutInfo> = callbackFlow {
+        val callback = Consumer<WindowLayoutInfo> { info -> offer(info) }
+        windowBackend.registerLayoutChangeCallback(activity, Runnable::run, callback)
+        awaitClose { windowBackend.unregisterLayoutChangeCallback(callback) }
+    }.buffer(capacity = UNLIMITED)
+}
diff --git a/window/window/src/main/java/androidx/window/WindowManager.kt b/window/window/src/main/java/androidx/window/WindowManager.kt
index 3c29415..5b6deed 100644
--- a/window/window/src/main/java/androidx/window/WindowManager.kt
+++ b/window/window/src/main/java/androidx/window/WindowManager.kt
@@ -90,7 +90,7 @@
      * current bounds that the user has selected for the [Activity][android.app.Activity]'s
      * window.
      *
-     * @see .getMaximumWindowMetrics
+     * @see getMaximumWindowMetrics
      * @see android.view.WindowManager.getCurrentWindowMetrics
      */
     public fun getCurrentWindowMetrics(): WindowMetrics {
@@ -121,7 +121,7 @@
      * the region describing the side of the device the associated [context&#39;s][Context]
      * window is placed.
      *
-     * @see .getCurrentWindowMetrics
+     * @see getCurrentWindowMetrics
      * @see android.view.WindowManager.getMaximumWindowMetrics
      */
     public fun getMaximumWindowMetrics(): WindowMetrics {
diff --git a/window/window/src/main/java/androidx/window/WindowServices.kt b/window/window/src/main/java/androidx/window/WindowServices.kt
new file mode 100644
index 0000000..fd3881d
--- /dev/null
+++ b/window/window/src/main/java/androidx/window/WindowServices.kt
@@ -0,0 +1,33 @@
+/*
+ * 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.
+ */
+@file:JvmName("WindowServices")
+
+package androidx.window
+
+import android.app.Activity
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+
+/**
+ * Provide an instance of [WindowInfoRepo] that is associated to the given [Activity]
+ */
+@ExperimentalCoroutinesApi
+public fun Activity.windowInfoRepository(): WindowInfoRepo {
+    return WindowInfoRepoImp(
+        this,
+        WindowBoundsHelper.instance,
+        ExtensionWindowBackend.getInstance(this)
+    )
+}
diff --git a/window/window/src/testUtil/java/androidx/window/TestConsumer.kt b/window/window/src/testUtil/java/androidx/window/TestConsumer.kt
new file mode 100644
index 0000000..6976014
--- /dev/null
+++ b/window/window/src/testUtil/java/androidx/window/TestConsumer.kt
@@ -0,0 +1,50 @@
+/*
+ * 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.window
+
+import androidx.core.util.Consumer
+import org.junit.Assert.assertEquals
+
+/**
+ * A test [Consumer] to hold all the values and make assertions based on the values.
+ */
+public class TestConsumer<T> : Consumer<T> {
+
+    private val values: MutableList<T> = mutableListOf()
+
+    /**
+     * Add the value to the list of seen values.
+     */
+    override fun accept(t: T) {
+        values.add(t)
+    }
+
+    /**
+     * Assert that there have been a fixed number of values
+     */
+    public fun assertValueCount(count: Int) {
+        assertEquals(count, values.size)
+    }
+
+    /**
+     * Assert that there has been exactly one value.
+     */
+    public fun assertValue(t: T) {
+        assertValueCount(1)
+        assertEquals(t, values[0])
+    }
+}
diff --git a/work/workmanager-benchmark/lint-baseline.xml b/work/workmanager-benchmark/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/work/workmanager-benchmark/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/work/workmanager-inspection/lint-baseline.xml b/work/workmanager-inspection/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/work/workmanager-inspection/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/work/workmanager-ktx/lint-baseline.xml b/work/workmanager-ktx/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/work/workmanager-ktx/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/work/workmanager-lint/lint-baseline.xml b/work/workmanager-lint/lint-baseline.xml
deleted file mode 100644
index 8794ae8..0000000
--- a/work/workmanager-lint/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" version="4.2.0-beta06">
-
-</issues>
diff --git a/work/workmanager-multiprocess/lint-baseline.xml b/work/workmanager-multiprocess/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/work/workmanager-multiprocess/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/work/workmanager-rxjava2/lint-baseline.xml b/work/workmanager-rxjava2/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/work/workmanager-rxjava2/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>
diff --git a/work/workmanager-rxjava3/lint-baseline.xml b/work/workmanager-rxjava3/lint-baseline.xml
deleted file mode 100644
index aaebb86..0000000
--- a/work/workmanager-rxjava3/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
-
-</issues>