Merge "Move compiler-xprocessing to compiler-processing" into androidx-master-dev
diff --git a/buildSrc/src/main/kotlin/androidx/build/AndroidXRootPlugin.kt b/buildSrc/src/main/kotlin/androidx/build/AndroidXRootPlugin.kt
index 1a011b5..1c47c0b 100644
--- a/buildSrc/src/main/kotlin/androidx/build/AndroidXRootPlugin.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/AndroidXRootPlugin.kt
@@ -24,6 +24,7 @@
 import androidx.build.gradle.isRoot
 import androidx.build.jacoco.Jacoco
 import androidx.build.license.CheckExternalDependencyLicensesTask
+import androidx.build.playground.VerifyPlaygroundGradlePropertiesTask
 import androidx.build.studio.StudioTask.Companion.registerStudioTask
 import androidx.build.uptodatedness.TaskUpToDateValidator
 import com.android.build.gradle.api.AndroidBasePlugin
@@ -76,6 +77,10 @@
             tasks.register(AndroidXPlugin.CREATE_LIBRARY_BUILD_INFO_FILES_TASK)
         )
 
+        VerifyPlaygroundGradlePropertiesTask.createIfNecessary(project)?.let {
+            buildOnServerTask.dependsOn(it)
+        }
+
         extra.set("versionChecker", GMavenVersionChecker(logger))
         val createArchiveTask = Release.getGlobalFullZipTask(this)
         buildOnServerTask.dependsOn(createArchiveTask)
diff --git a/buildSrc/src/main/kotlin/androidx/build/dependencies/Dependencies.kt b/buildSrc/src/main/kotlin/androidx/build/dependencies/Dependencies.kt
index 6ab319f..933f9da 100644
--- a/buildSrc/src/main/kotlin/androidx/build/dependencies/Dependencies.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/dependencies/Dependencies.kt
@@ -84,7 +84,7 @@
 const val MOCKITO_KOTLIN = "com.nhaarman.mockitokotlin2:mockito-kotlin:2.1.0"
 const val MULTIDEX = "androidx.multidex:multidex:2.0.0"
 const val NULLAWAY = "com.uber.nullaway:nullaway:0.3.7"
-const val PLAY_CORE = "com.google.android.play:core:1.7.2"
+const val PLAY_CORE = "com.google.android.play:core:1.8.0"
 const val REACTIVE_STREAMS = "org.reactivestreams:reactive-streams:1.0.0"
 const val RX_JAVA = "io.reactivex.rxjava2:rxjava:2.2.9"
 const val RX_JAVA3 = "io.reactivex.rxjava3:rxjava:3.0.0"
diff --git a/buildSrc/src/main/kotlin/androidx/build/playground/VerifyPlaygroundGradlePropertiesTask.kt b/buildSrc/src/main/kotlin/androidx/build/playground/VerifyPlaygroundGradlePropertiesTask.kt
new file mode 100644
index 0000000..8721a8f0
--- /dev/null
+++ b/buildSrc/src/main/kotlin/androidx/build/playground/VerifyPlaygroundGradlePropertiesTask.kt
@@ -0,0 +1,116 @@
+/*
+ * 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.build.playground
+
+import org.gradle.api.DefaultTask
+import org.gradle.api.GradleException
+import org.gradle.api.Project
+import org.gradle.api.file.RegularFileProperty
+import org.gradle.api.tasks.InputFile
+import org.gradle.api.tasks.OutputFile
+import org.gradle.api.tasks.TaskAction
+import org.gradle.api.tasks.TaskProvider
+import java.io.File
+import java.util.Properties
+
+/**
+ * Compares the properties file for playground projects with the main androidx properties file
+ * to ensure playgrounds do not define any property in their own build that conflicts with the
+ * main build.
+ */
+@Suppress("UnstableApiUsage") // for fileProperty
+abstract class VerifyPlaygroundGradlePropertiesTask : DefaultTask() {
+    @get:InputFile
+    abstract val androidxProperties: RegularFileProperty
+
+    @get:InputFile
+    abstract val playgroundProperties: RegularFileProperty
+
+    @get:OutputFile
+    abstract val outputFile: RegularFileProperty
+
+    @TaskAction
+    fun compareProperties() {
+        val rootProperties = loadPropertiesFile(androidxProperties.get().asFile)
+        val playgroundProperties = loadPropertiesFile(playgroundProperties.get().asFile)
+        validateProperties(rootProperties, playgroundProperties)
+    }
+
+    private fun validateProperties(
+        rootProperties: Properties,
+        playgroundProperties: Properties
+    ) {
+        // ensure we don't define properties that do not match the root file
+        // this includes properties that are not defined in the root androidx build as they might
+        // be properties which can alter the build output. We might consider whitelisting certain
+        // properties in the future if necessary.
+        playgroundProperties.forEach {
+            val rootValue = rootProperties[it.key]
+            if (rootValue != it.value) {
+                throw GradleException(
+                    """
+                    ${it.key} is defined as ${it.value} in playground properties but
+                    it does not match the value defined in root properties file ($rootValue).
+                    Having inconsistent properties in playground projects might trigger wrong
+                    compilation output in the main AndroidX build, thus not allowed.
+                    """.trimIndent()
+                )
+            }
+        }
+        // put the success into an output so that task can be up to date.
+        outputFile.get().asFile.writeText("valid", Charsets.UTF_8)
+    }
+
+    private fun loadPropertiesFile(file: File) = file.inputStream().use { inputStream ->
+        Properties().apply {
+            load(inputStream)
+        }
+    }
+
+    companion object {
+        private const val TASK_NAME = "verifyPlaygroundGradleProperties"
+
+        /**
+         * Creates the task to verify playground properties if an only if we have the
+         * playground-common folder to check against.
+         */
+        fun createIfNecessary(
+            project: Project
+        ): TaskProvider<VerifyPlaygroundGradlePropertiesTask>? {
+            return if (project.projectDir.resolve("playground-common").exists()) {
+                project.tasks.register(
+                    TASK_NAME,
+                    VerifyPlaygroundGradlePropertiesTask::class.java
+                ) {
+                    it.androidxProperties.set(
+                        project.layout.projectDirectory.file("gradle.properties")
+                    )
+                    it.playgroundProperties.set(
+                        project.layout.projectDirectory.file(
+                            "playground-common/androidx-shared.properties"
+                        )
+                    )
+                    it.outputFile.set(
+                        project.layout.buildDirectory.file("playgroundPropertiesValidation.out")
+                    )
+                }
+            } else {
+                null
+            }
+        }
+    }
+}
diff --git a/core/core/api/1.5.0-alpha01.txt b/core/core/api/1.5.0-alpha01.txt
index 713d200..2f34606 100644
--- a/core/core/api/1.5.0-alpha01.txt
+++ b/core/core/api/1.5.0-alpha01.txt
@@ -2678,6 +2678,7 @@
     method public androidx.core.view.accessibility.AccessibilityNodeInfoCompat! focusSearch(int);
     method public java.util.List<androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat!>! getActionList();
     method public int getActions();
+    method public java.util.List<java.lang.String!>? getAvailableExtraData();
     method @Deprecated public void getBoundsInParent(android.graphics.Rect!);
     method public void getBoundsInScreen(android.graphics.Rect!);
     method public androidx.core.view.accessibility.AccessibilityNodeInfoCompat! getChild(int);
@@ -2688,6 +2689,7 @@
     method public CharSequence! getContentDescription();
     method public int getDrawingOrder();
     method public CharSequence! getError();
+    method public android.view.accessibility.AccessibilityNodeInfo.ExtraRenderingInfo? getExtraRenderingInfo();
     method public android.os.Bundle! getExtras();
     method public CharSequence? getHintText();
     method @Deprecated public Object! getInfo();
@@ -2743,10 +2745,12 @@
     method public boolean performAction(int, android.os.Bundle!);
     method public void recycle();
     method public boolean refresh();
+    method public boolean refreshWithExtraData(String?, android.os.Bundle);
     method public boolean removeAction(androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat!);
     method public boolean removeChild(android.view.View!);
     method public boolean removeChild(android.view.View!, int);
     method public void setAccessibilityFocused(boolean);
+    method public void setAvailableExtraData(java.util.List<java.lang.String!>);
     method @Deprecated public void setBoundsInParent(android.graphics.Rect!);
     method public void setBoundsInScreen(android.graphics.Rect!);
     method public void setCanOpenPopup(boolean);
@@ -2840,6 +2844,10 @@
     field public static final int ACTION_SELECT = 4; // 0x4
     field public static final int ACTION_SET_SELECTION = 131072; // 0x20000
     field public static final int ACTION_SET_TEXT = 2097152; // 0x200000
+    field public static final String EXTRA_DATA_RENDERING_INFO_KEY = "android.view.accessibility.extra.DATA_RENDERING_INFO_KEY";
+    field public static final String EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_LENGTH = "android.view.accessibility.extra.DATA_TEXT_CHARACTER_LOCATION_ARG_LENGTH";
+    field public static final String EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_START_INDEX = "android.view.accessibility.extra.DATA_TEXT_CHARACTER_LOCATION_ARG_START_INDEX";
+    field public static final String EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY = "android.view.accessibility.extra.DATA_TEXT_CHARACTER_LOCATION_KEY";
     field public static final int FOCUS_ACCESSIBILITY = 2; // 0x2
     field public static final int FOCUS_INPUT = 1; // 0x1
     field public static final int MOVEMENT_GRANULARITY_CHARACTER = 1; // 0x1
diff --git a/core/core/api/current.txt b/core/core/api/current.txt
index 4742c40..34d2fa0 100644
--- a/core/core/api/current.txt
+++ b/core/core/api/current.txt
@@ -2697,6 +2697,7 @@
     method public androidx.core.view.accessibility.AccessibilityNodeInfoCompat! focusSearch(int);
     method public java.util.List<androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat!>! getActionList();
     method public int getActions();
+    method public java.util.List<java.lang.String!>? getAvailableExtraData();
     method @Deprecated public void getBoundsInParent(android.graphics.Rect!);
     method public void getBoundsInScreen(android.graphics.Rect!);
     method public androidx.core.view.accessibility.AccessibilityNodeInfoCompat! getChild(int);
@@ -2707,6 +2708,7 @@
     method public CharSequence! getContentDescription();
     method public int getDrawingOrder();
     method public CharSequence! getError();
+    method public android.view.accessibility.AccessibilityNodeInfo.ExtraRenderingInfo? getExtraRenderingInfo();
     method public android.os.Bundle! getExtras();
     method public CharSequence? getHintText();
     method @Deprecated public Object! getInfo();
@@ -2762,10 +2764,12 @@
     method public boolean performAction(int, android.os.Bundle!);
     method public void recycle();
     method public boolean refresh();
+    method public boolean refreshWithExtraData(String?, android.os.Bundle);
     method public boolean removeAction(androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat!);
     method public boolean removeChild(android.view.View!);
     method public boolean removeChild(android.view.View!, int);
     method public void setAccessibilityFocused(boolean);
+    method public void setAvailableExtraData(java.util.List<java.lang.String!>);
     method @Deprecated public void setBoundsInParent(android.graphics.Rect!);
     method public void setBoundsInScreen(android.graphics.Rect!);
     method public void setCanOpenPopup(boolean);
@@ -2859,6 +2863,10 @@
     field public static final int ACTION_SELECT = 4; // 0x4
     field public static final int ACTION_SET_SELECTION = 131072; // 0x20000
     field public static final int ACTION_SET_TEXT = 2097152; // 0x200000
+    field public static final String EXTRA_DATA_RENDERING_INFO_KEY = "android.view.accessibility.extra.DATA_RENDERING_INFO_KEY";
+    field public static final String EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_LENGTH = "android.view.accessibility.extra.DATA_TEXT_CHARACTER_LOCATION_ARG_LENGTH";
+    field public static final String EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_START_INDEX = "android.view.accessibility.extra.DATA_TEXT_CHARACTER_LOCATION_ARG_START_INDEX";
+    field public static final String EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY = "android.view.accessibility.extra.DATA_TEXT_CHARACTER_LOCATION_KEY";
     field public static final int FOCUS_ACCESSIBILITY = 2; // 0x2
     field public static final int FOCUS_INPUT = 1; // 0x1
     field public static final int MOVEMENT_GRANULARITY_CHARACTER = 1; // 0x1
diff --git a/core/core/api/public_plus_experimental_1.5.0-alpha01.txt b/core/core/api/public_plus_experimental_1.5.0-alpha01.txt
index 2e0be49..0140bc1 100644
--- a/core/core/api/public_plus_experimental_1.5.0-alpha01.txt
+++ b/core/core/api/public_plus_experimental_1.5.0-alpha01.txt
@@ -2676,6 +2676,7 @@
     method public androidx.core.view.accessibility.AccessibilityNodeInfoCompat! focusSearch(int);
     method public java.util.List<androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat!>! getActionList();
     method public int getActions();
+    method public java.util.List<java.lang.String!>? getAvailableExtraData();
     method @Deprecated public void getBoundsInParent(android.graphics.Rect!);
     method public void getBoundsInScreen(android.graphics.Rect!);
     method public androidx.core.view.accessibility.AccessibilityNodeInfoCompat! getChild(int);
@@ -2686,6 +2687,7 @@
     method public CharSequence! getContentDescription();
     method public int getDrawingOrder();
     method public CharSequence! getError();
+    method public android.view.accessibility.AccessibilityNodeInfo.ExtraRenderingInfo? getExtraRenderingInfo();
     method public android.os.Bundle! getExtras();
     method public CharSequence? getHintText();
     method @Deprecated public Object! getInfo();
@@ -2741,10 +2743,12 @@
     method public boolean performAction(int, android.os.Bundle!);
     method public void recycle();
     method public boolean refresh();
+    method public boolean refreshWithExtraData(String?, android.os.Bundle);
     method public boolean removeAction(androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat!);
     method public boolean removeChild(android.view.View!);
     method public boolean removeChild(android.view.View!, int);
     method public void setAccessibilityFocused(boolean);
+    method public void setAvailableExtraData(java.util.List<java.lang.String!>);
     method @Deprecated public void setBoundsInParent(android.graphics.Rect!);
     method public void setBoundsInScreen(android.graphics.Rect!);
     method public void setCanOpenPopup(boolean);
@@ -2838,6 +2842,10 @@
     field public static final int ACTION_SELECT = 4; // 0x4
     field public static final int ACTION_SET_SELECTION = 131072; // 0x20000
     field public static final int ACTION_SET_TEXT = 2097152; // 0x200000
+    field public static final String EXTRA_DATA_RENDERING_INFO_KEY = "android.view.accessibility.extra.DATA_RENDERING_INFO_KEY";
+    field public static final String EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_LENGTH = "android.view.accessibility.extra.DATA_TEXT_CHARACTER_LOCATION_ARG_LENGTH";
+    field public static final String EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_START_INDEX = "android.view.accessibility.extra.DATA_TEXT_CHARACTER_LOCATION_ARG_START_INDEX";
+    field public static final String EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY = "android.view.accessibility.extra.DATA_TEXT_CHARACTER_LOCATION_KEY";
     field public static final int FOCUS_ACCESSIBILITY = 2; // 0x2
     field public static final int FOCUS_INPUT = 1; // 0x1
     field public static final int MOVEMENT_GRANULARITY_CHARACTER = 1; // 0x1
diff --git a/core/core/api/public_plus_experimental_current.txt b/core/core/api/public_plus_experimental_current.txt
index f33927a..42c8f98 100644
--- a/core/core/api/public_plus_experimental_current.txt
+++ b/core/core/api/public_plus_experimental_current.txt
@@ -2695,6 +2695,7 @@
     method public androidx.core.view.accessibility.AccessibilityNodeInfoCompat! focusSearch(int);
     method public java.util.List<androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat!>! getActionList();
     method public int getActions();
+    method public java.util.List<java.lang.String!>? getAvailableExtraData();
     method @Deprecated public void getBoundsInParent(android.graphics.Rect!);
     method public void getBoundsInScreen(android.graphics.Rect!);
     method public androidx.core.view.accessibility.AccessibilityNodeInfoCompat! getChild(int);
@@ -2705,6 +2706,7 @@
     method public CharSequence! getContentDescription();
     method public int getDrawingOrder();
     method public CharSequence! getError();
+    method public android.view.accessibility.AccessibilityNodeInfo.ExtraRenderingInfo? getExtraRenderingInfo();
     method public android.os.Bundle! getExtras();
     method public CharSequence? getHintText();
     method @Deprecated public Object! getInfo();
@@ -2760,10 +2762,12 @@
     method public boolean performAction(int, android.os.Bundle!);
     method public void recycle();
     method public boolean refresh();
+    method public boolean refreshWithExtraData(String?, android.os.Bundle);
     method public boolean removeAction(androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat!);
     method public boolean removeChild(android.view.View!);
     method public boolean removeChild(android.view.View!, int);
     method public void setAccessibilityFocused(boolean);
+    method public void setAvailableExtraData(java.util.List<java.lang.String!>);
     method @Deprecated public void setBoundsInParent(android.graphics.Rect!);
     method public void setBoundsInScreen(android.graphics.Rect!);
     method public void setCanOpenPopup(boolean);
@@ -2857,6 +2861,10 @@
     field public static final int ACTION_SELECT = 4; // 0x4
     field public static final int ACTION_SET_SELECTION = 131072; // 0x20000
     field public static final int ACTION_SET_TEXT = 2097152; // 0x200000
+    field public static final String EXTRA_DATA_RENDERING_INFO_KEY = "android.view.accessibility.extra.DATA_RENDERING_INFO_KEY";
+    field public static final String EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_LENGTH = "android.view.accessibility.extra.DATA_TEXT_CHARACTER_LOCATION_ARG_LENGTH";
+    field public static final String EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_START_INDEX = "android.view.accessibility.extra.DATA_TEXT_CHARACTER_LOCATION_ARG_START_INDEX";
+    field public static final String EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY = "android.view.accessibility.extra.DATA_TEXT_CHARACTER_LOCATION_KEY";
     field public static final int FOCUS_ACCESSIBILITY = 2; // 0x2
     field public static final int FOCUS_INPUT = 1; // 0x1
     field public static final int MOVEMENT_GRANULARITY_CHARACTER = 1; // 0x1
diff --git a/core/core/api/restricted_1.5.0-alpha01.txt b/core/core/api/restricted_1.5.0-alpha01.txt
index 84edbd8..0dafa25 100644
--- a/core/core/api/restricted_1.5.0-alpha01.txt
+++ b/core/core/api/restricted_1.5.0-alpha01.txt
@@ -3093,6 +3093,7 @@
     method public androidx.core.view.accessibility.AccessibilityNodeInfoCompat! focusSearch(int);
     method public java.util.List<androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat!>! getActionList();
     method public int getActions();
+    method public java.util.List<java.lang.String!>? getAvailableExtraData();
     method @Deprecated public void getBoundsInParent(android.graphics.Rect!);
     method public void getBoundsInScreen(android.graphics.Rect!);
     method public androidx.core.view.accessibility.AccessibilityNodeInfoCompat! getChild(int);
@@ -3104,6 +3105,7 @@
     method public CharSequence! getContentDescription();
     method public int getDrawingOrder();
     method public CharSequence! getError();
+    method public android.view.accessibility.AccessibilityNodeInfo.ExtraRenderingInfo? getExtraRenderingInfo();
     method public android.os.Bundle! getExtras();
     method public CharSequence? getHintText();
     method @Deprecated public Object! getInfo();
@@ -3159,10 +3161,12 @@
     method public boolean performAction(int, android.os.Bundle!);
     method public void recycle();
     method public boolean refresh();
+    method public boolean refreshWithExtraData(String?, android.os.Bundle);
     method public boolean removeAction(androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat!);
     method public boolean removeChild(android.view.View!);
     method public boolean removeChild(android.view.View!, int);
     method public void setAccessibilityFocused(boolean);
+    method public void setAvailableExtraData(java.util.List<java.lang.String!>);
     method @Deprecated public void setBoundsInParent(android.graphics.Rect!);
     method public void setBoundsInScreen(android.graphics.Rect!);
     method public void setCanOpenPopup(boolean);
@@ -3256,6 +3260,10 @@
     field public static final int ACTION_SELECT = 4; // 0x4
     field public static final int ACTION_SET_SELECTION = 131072; // 0x20000
     field public static final int ACTION_SET_TEXT = 2097152; // 0x200000
+    field public static final String EXTRA_DATA_RENDERING_INFO_KEY = "android.view.accessibility.extra.DATA_RENDERING_INFO_KEY";
+    field public static final String EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_LENGTH = "android.view.accessibility.extra.DATA_TEXT_CHARACTER_LOCATION_ARG_LENGTH";
+    field public static final String EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_START_INDEX = "android.view.accessibility.extra.DATA_TEXT_CHARACTER_LOCATION_ARG_START_INDEX";
+    field public static final String EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY = "android.view.accessibility.extra.DATA_TEXT_CHARACTER_LOCATION_KEY";
     field public static final int FOCUS_ACCESSIBILITY = 2; // 0x2
     field public static final int FOCUS_INPUT = 1; // 0x1
     field public static final int MOVEMENT_GRANULARITY_CHARACTER = 1; // 0x1
diff --git a/core/core/api/restricted_current.txt b/core/core/api/restricted_current.txt
index 66fed5c..b5197212 100644
--- a/core/core/api/restricted_current.txt
+++ b/core/core/api/restricted_current.txt
@@ -3115,6 +3115,7 @@
     method public androidx.core.view.accessibility.AccessibilityNodeInfoCompat! focusSearch(int);
     method public java.util.List<androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat!>! getActionList();
     method public int getActions();
+    method public java.util.List<java.lang.String!>? getAvailableExtraData();
     method @Deprecated public void getBoundsInParent(android.graphics.Rect!);
     method public void getBoundsInScreen(android.graphics.Rect!);
     method public androidx.core.view.accessibility.AccessibilityNodeInfoCompat! getChild(int);
@@ -3126,6 +3127,7 @@
     method public CharSequence! getContentDescription();
     method public int getDrawingOrder();
     method public CharSequence! getError();
+    method public android.view.accessibility.AccessibilityNodeInfo.ExtraRenderingInfo? getExtraRenderingInfo();
     method public android.os.Bundle! getExtras();
     method public CharSequence? getHintText();
     method @Deprecated public Object! getInfo();
@@ -3181,10 +3183,12 @@
     method public boolean performAction(int, android.os.Bundle!);
     method public void recycle();
     method public boolean refresh();
+    method public boolean refreshWithExtraData(String?, android.os.Bundle);
     method public boolean removeAction(androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat!);
     method public boolean removeChild(android.view.View!);
     method public boolean removeChild(android.view.View!, int);
     method public void setAccessibilityFocused(boolean);
+    method public void setAvailableExtraData(java.util.List<java.lang.String!>);
     method @Deprecated public void setBoundsInParent(android.graphics.Rect!);
     method public void setBoundsInScreen(android.graphics.Rect!);
     method public void setCanOpenPopup(boolean);
@@ -3278,6 +3282,10 @@
     field public static final int ACTION_SELECT = 4; // 0x4
     field public static final int ACTION_SET_SELECTION = 131072; // 0x20000
     field public static final int ACTION_SET_TEXT = 2097152; // 0x200000
+    field public static final String EXTRA_DATA_RENDERING_INFO_KEY = "android.view.accessibility.extra.DATA_RENDERING_INFO_KEY";
+    field public static final String EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_LENGTH = "android.view.accessibility.extra.DATA_TEXT_CHARACTER_LOCATION_ARG_LENGTH";
+    field public static final String EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_START_INDEX = "android.view.accessibility.extra.DATA_TEXT_CHARACTER_LOCATION_ARG_START_INDEX";
+    field public static final String EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY = "android.view.accessibility.extra.DATA_TEXT_CHARACTER_LOCATION_KEY";
     field public static final int FOCUS_ACCESSIBILITY = 2; // 0x2
     field public static final int FOCUS_INPUT = 1; // 0x1
     field public static final int MOVEMENT_GRANULARITY_CHARACTER = 1; // 0x1
diff --git a/core/core/src/main/java/androidx/core/view/accessibility/AccessibilityNodeInfoCompat.java b/core/core/src/main/java/androidx/core/view/accessibility/AccessibilityNodeInfoCompat.java
index 6b35ae0..77b849b 100644
--- a/core/core/src/main/java/androidx/core/view/accessibility/AccessibilityNodeInfoCompat.java
+++ b/core/core/src/main/java/androidx/core/view/accessibility/AccessibilityNodeInfoCompat.java
@@ -35,6 +35,7 @@
 import android.util.SparseArray;
 import android.view.View;
 import android.view.accessibility.AccessibilityNodeInfo;
+import android.view.accessibility.AccessibilityNodeInfo.ExtraRenderingInfo;
 import android.view.accessibility.AccessibilityNodeInfo.TouchDelegateInfo;
 
 import androidx.annotation.IntRange;
@@ -1668,6 +1669,64 @@
      */
     public static final int MOVEMENT_GRANULARITY_PAGE = 0x00000010;
 
+    /**
+     * Key used to request and locate extra data for text character location. This key requests that
+     * an array of {@link android.graphics.RectF}s be added to the extras. This request is made with
+     * {@link #refreshWithExtraData(String, Bundle)}. The arguments taken by this request are two
+     * integers: {@link #EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_START_INDEX} and
+     * {@link #EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_LENGTH}. The starting index must be valid
+     * inside the CharSequence returned by {@link #getText()}, and the length must be positive.
+     * <p>
+     * The data can be retrieved from the {@code Bundle} returned by {@link #getExtras()} using this
+     * string as a key for {@link Bundle#getParcelableArray(String)}. The
+     * {@link android.graphics.RectF} will be null for characters that either do not exist or are
+     * off the screen.
+     *
+     * {@see #refreshWithExtraData(String, Bundle)}
+     */
+    @SuppressLint("ActionValue")
+    public static final String EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY =
+            "android.view.accessibility.extra.DATA_TEXT_CHARACTER_LOCATION_KEY";
+
+    /**
+     * Integer argument specifying the start index of the requested text location data. Must be
+     * valid inside the CharSequence returned by {@link #getText()}.
+     *
+     * @see #EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY
+     */
+    @SuppressLint("ActionValue")
+    public static final String EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_START_INDEX =
+            "android.view.accessibility.extra.DATA_TEXT_CHARACTER_LOCATION_ARG_START_INDEX";
+
+    /**
+     * Integer argument specifying the end index of the requested text location data. Must be
+     * positive.
+     *
+     * @see #EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY
+     */
+    @SuppressLint("ActionValue")
+    public static final String EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_LENGTH =
+            "android.view.accessibility.extra.DATA_TEXT_CHARACTER_LOCATION_ARG_LENGTH";
+
+    /**
+     * Key used to request extra data for the rendering information.
+     * The key requests that a {@link AccessibilityNodeInfo.ExtraRenderingInfo} be added to this
+     * info. This request is made with {@link #refreshWithExtraData(String, Bundle)} without
+     * argument.
+     * <p>
+     * The data can be retrieved from the {@link ExtraRenderingInfo} returned by
+     * {@link #getExtraRenderingInfo()} using {@link ExtraRenderingInfo#getLayoutSize},
+     * {@link ExtraRenderingInfo#getTextSizeInPx()} and
+     * {@link ExtraRenderingInfo#getTextSizeUnit()}. For layout params, it is supported by both
+     * {@link android.widget.TextView} and {@link android.view.ViewGroup}. For text size and
+     * unit, it is only supported by {@link android.widget.TextView}.
+     *
+     * @see #refreshWithExtraData(String, Bundle)
+     */
+    @SuppressLint("ActionValue")
+    public static final String EXTRA_DATA_RENDERING_INFO_KEY =
+            "android.view.accessibility.extra.DATA_RENDERING_INFO_KEY";
+
     private static int sClickableSpanId = 0;
 
     /**
@@ -3060,6 +3119,23 @@
     }
 
     /**
+     * Gets the {@link ExtraRenderingInfo extra rendering info} if the node is meant to be
+     * refreshed with extra data to examine rendering related accessibility issues.
+     *
+     * @return The {@link ExtraRenderingInfo extra rendering info}.
+     *
+     * @see #EXTRA_DATA_RENDERING_INFO_KEY
+     * @see #refreshWithExtraData(String, Bundle)
+     */
+    @Nullable
+    public ExtraRenderingInfo getExtraRenderingInfo() {
+        if (Build.VERSION.SDK_INT >= 30) {
+            return mInfo.getExtraRenderingInfo();
+        }
+        return null;
+    }
+
+    /**
      * Gets the actions that can be performed on the node.
      *
      * @return A list of AccessibilityActions.
@@ -3649,6 +3725,47 @@
     }
 
     /**
+     * Get the extra data available for this node.
+     * <p>
+     * Some data that is useful for some accessibility services is expensive to compute, and would
+     * place undue overhead on apps to compute all the time. That data can be requested with
+     * {@link #refreshWithExtraData(String, Bundle)}.
+     *
+     * @return An unmodifiable list of keys corresponding to extra data that can be requested.
+     * @see #EXTRA_DATA_RENDERING_INFO_KEY
+     * @see #EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY
+     */
+    @Nullable
+    public List<String> getAvailableExtraData() {
+        if (Build.VERSION.SDK_INT >= 26) {
+            return mInfo.getAvailableExtraData();
+        }
+        return Collections.emptyList();
+    }
+
+    /**
+     * Set the extra data available for this node.
+     * <p>
+     * <strong>Note:</strong> When a {@code View} passes in a non-empty list, it promises that
+     * it will populate the node's extras with corresponding pieces of information in
+     * {@link View#addExtraDataToAccessibilityNodeInfo(AccessibilityNodeInfo, String, Bundle)}.
+     * <p>
+     * <strong>Note:</strong> Cannot be called from an
+     * {@link android.accessibilityservice.AccessibilityService}.
+     * This class is made immutable before being delivered to an AccessibilityService.
+     *
+     * @param extraDataKeys A list of types of extra data that are available.
+     * @see #getAvailableExtraData()
+     *
+     * @throws IllegalStateException If called from an AccessibilityService.
+     */
+    public void setAvailableExtraData(@NonNull List<String> extraDataKeys) {
+        if (Build.VERSION.SDK_INT >= 26) {
+            mInfo.setAvailableExtraData(extraDataKeys);
+        }
+    }
+
+    /**
      * Gets the window to which this node belongs.
      *
      * @return The window.
@@ -3981,6 +4098,30 @@
     }
 
     /**
+     * Refreshes this info with the latest state of the view it represents, and request new
+     * data be added by the View.
+     *
+     * @param extraDataKey The extra data requested. Data that must be requested
+     *                     with this mechanism is generally expensive to retrieve, so should only be
+     *                     requested when needed. See
+     *                     {@link #EXTRA_DATA_RENDERING_INFO_KEY},
+     *                     {@link #EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY},
+     *                     {@link #getAvailableExtraData()} and {@link #getExtraRenderingInfo()}.
+     * @param args A bundle of arguments for the request. These depend on the particular request.
+     *
+     * @return {@code true} if the refresh succeeded. {@code false} if the {@link View} represented
+     * by this node is no longer in the view tree (and thus this node is obsolete and should be
+     * recycled).
+     */
+    public boolean refreshWithExtraData(@Nullable String extraDataKey, @NonNull Bundle args) {
+        if (Build.VERSION.SDK_INT >= 26) {
+            return mInfo.refreshWithExtraData(extraDataKey, args);
+        } else {
+            return false;
+        }
+    }
+
+    /**
      * Gets the custom role description.
      * @return The role description.
      */
diff --git a/core/core/src/main/java/androidx/core/view/accessibility/AccessibilityNodeProviderCompat.java b/core/core/src/main/java/androidx/core/view/accessibility/AccessibilityNodeProviderCompat.java
index 477a4fe..5e51fd1 100644
--- a/core/core/src/main/java/androidx/core/view/accessibility/AccessibilityNodeProviderCompat.java
+++ b/core/core/src/main/java/androidx/core/view/accessibility/AccessibilityNodeProviderCompat.java
@@ -240,7 +240,7 @@
      *                     the info's {@link AccessibilityNodeInfoCompat#getExtras} method.
      * @param arguments A {@link Bundle} holding any arguments relevant for this request.
      *
-     * @see AccessibilityNodeInfo#setAvailableExtraData(List)
+     * @see AccessibilityNodeInfoCompat#setAvailableExtraData(List)
      */
     public void addExtraDataToAccessibilityNodeInfo(int virtualViewId,
             @NonNull AccessibilityNodeInfoCompat info, @NonNull String extraDataKey,
diff --git a/datastore/datastore-core/src/test/kotlin/androidx/datastore/DataMigrationInitializerTest.kt b/datastore/datastore-core/src/test/java/androidx/datastore/DataMigrationInitializerTest.kt
similarity index 100%
rename from datastore/datastore-core/src/test/kotlin/androidx/datastore/DataMigrationInitializerTest.kt
rename to datastore/datastore-core/src/test/java/androidx/datastore/DataMigrationInitializerTest.kt
diff --git a/datastore/datastore-core/src/test/kotlin/androidx/datastore/DataStoreFactoryTest.kt b/datastore/datastore-core/src/test/java/androidx/datastore/DataStoreFactoryTest.kt
similarity index 100%
rename from datastore/datastore-core/src/test/kotlin/androidx/datastore/DataStoreFactoryTest.kt
rename to datastore/datastore-core/src/test/java/androidx/datastore/DataStoreFactoryTest.kt
diff --git a/datastore/datastore-core/src/test/kotlin/androidx/datastore/SingleProcessDataStoreTest.kt b/datastore/datastore-core/src/test/java/androidx/datastore/SingleProcessDataStoreTest.kt
similarity index 100%
rename from datastore/datastore-core/src/test/kotlin/androidx/datastore/SingleProcessDataStoreTest.kt
rename to datastore/datastore-core/src/test/java/androidx/datastore/SingleProcessDataStoreTest.kt
diff --git a/datastore/datastore-core/src/test/kotlin/androidx/datastore/TestingSerializer.kt b/datastore/datastore-core/src/test/java/androidx/datastore/TestingSerializer.kt
similarity index 100%
rename from datastore/datastore-core/src/test/kotlin/androidx/datastore/TestingSerializer.kt
rename to datastore/datastore-core/src/test/java/androidx/datastore/TestingSerializer.kt
diff --git a/datastore/datastore-core/src/test/kotlin/androidx/datastore/handlers/ReplaceFileCorruptionHandlerTest.kt b/datastore/datastore-core/src/test/java/androidx/datastore/handlers/ReplaceFileCorruptionHandlerTest.kt
similarity index 100%
rename from datastore/datastore-core/src/test/kotlin/androidx/datastore/handlers/ReplaceFileCorruptionHandlerTest.kt
rename to datastore/datastore-core/src/test/java/androidx/datastore/handlers/ReplaceFileCorruptionHandlerTest.kt
diff --git a/datastore/datastore-sampleapp/build.gradle b/datastore/datastore-sampleapp/build.gradle
index 0072845..ff065b1 100644
--- a/datastore/datastore-sampleapp/build.gradle
+++ b/datastore/datastore-sampleapp/build.gradle
@@ -52,11 +52,13 @@
 }
 
 dependencies {
-    //For DataStore with Preferences
+    // For DataStore with Preferences
     implementation(project(":datastore:datastore-preferences"))
 
     // For DataStore with protos
     implementation(project(":datastore:datastore-core"))
+    api("androidx.preference:preference:1.1.0")
+
     implementation(PROTOBUF_LITE)
 
     implementation(KOTLIN_STDLIB)
diff --git a/datastore/datastore-sampleapp/src/main/AndroidManifest.xml b/datastore/datastore-sampleapp/src/main/AndroidManifest.xml
index e46b51a..28673cf 100644
--- a/datastore/datastore-sampleapp/src/main/AndroidManifest.xml
+++ b/datastore/datastore-sampleapp/src/main/AndroidManifest.xml
@@ -34,5 +34,12 @@
                 <category android:name="android.intent.category.LAUNCHER" />
             </intent-filter>
         </activity>
+
+        <activity android:name=".SettingsFragmentActivity">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
     </application>
 </manifest>
\ No newline at end of file
diff --git a/datastore/datastore-sampleapp/src/main/java/com/example/datastoresampleapp/SettingsFragment.kt b/datastore/datastore-sampleapp/src/main/java/com/example/datastoresampleapp/SettingsFragment.kt
new file mode 100644
index 0000000..cd4966f
--- /dev/null
+++ b/datastore/datastore-sampleapp/src/main/java/com/example/datastoresampleapp/SettingsFragment.kt
@@ -0,0 +1,160 @@
+/*
+ * 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 com.example.datastoresampleapp
+
+import android.annotation.SuppressLint
+import android.content.Context
+import android.os.Bundle
+import android.util.Log
+import android.view.View
+import androidx.appcompat.app.AppCompatActivity
+import androidx.datastore.CorruptionException
+import androidx.datastore.DataStore
+import androidx.datastore.DataStoreFactory
+import androidx.datastore.Serializer
+import androidx.preference.Preference
+import androidx.preference.SwitchPreference
+import androidx.preference.PreferenceFragmentCompat
+import androidx.lifecycle.lifecycleScope
+import androidx.preference.TwoStatePreference
+import com.google.protobuf.InvalidProtocolBufferException
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.callbackFlow
+import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.launch
+import java.io.File
+import java.io.IOException
+import java.io.InputStream
+import java.io.OutputStream
+
+private val TAG = "SettingsActivity"
+
+class SettingsFragmentActivity() : AppCompatActivity() {
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+        supportFragmentManager.beginTransaction()
+            .replace(android.R.id.content, SettingsFragment()).commit()
+    }
+}
+
+/**
+ * Toggle States:
+ * 1) Value not read from disk. Toggle is disabled in default position.
+ * 2) Value read from disk and no pending updates. Toggle is enabled in latest persisted position.
+ * 3) Value read from disk but with pending updates. Toggle is disabled in pending position.
+ */
+class SettingsFragment() : PreferenceFragmentCompat() {
+    private val fooToggle: TwoStatePreference by lazy {
+        createFooPreference(preferenceManager.context)
+    }
+
+    private val PROTO_STORE_FILE_NAME = "datastore_test_app.pb"
+
+    private val settingsStore: DataStore<Settings> by lazy {
+        DataStoreFactory().create(
+            { File(requireActivity().applicationContext.filesDir, PROTO_STORE_FILE_NAME) },
+            SettingsSerializer
+        )
+    }
+
+    override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
+        val preferences = preferenceManager.createPreferenceScreen(preferenceManager.context)
+        preferences.addPreference(fooToggle)
+        preferenceScreen = preferences
+    }
+
+    @SuppressLint("SyntheticAccessor")
+    @ExperimentalCoroutinesApi
+    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+        super.onViewCreated(view, savedInstanceState)
+
+        viewLifecycleOwner.lifecycleScope.launchWhenStarted {
+            // Read the initial value from disk
+            val settings: Settings = try {
+                settingsStore.data.first()
+            } catch (ex: IOException) {
+                Log.e(TAG, "Could not read settings.", ex)
+                // Show error to user here, or try re-reading.
+                return@launchWhenStarted
+            }
+
+            // Set the toggle to the value read from disk and enable the toggle.
+            fooToggle.isChecked = settings.foo
+            fooToggle.isEnabled = true
+
+            fooToggle.changeFlow.flatMapLatest { (_: Preference?, newValue: Any?) ->
+                val isChecked = newValue as Boolean
+
+                fooToggle.isEnabled = false // Disable the toggle until the write is completed
+                fooToggle.isChecked = isChecked // Set the disabled toggle to the pending value
+
+                try {
+                    settingsStore.setFoo(isChecked)
+                } catch (ex: IOException) { // setFoo can only throw IOExceptions
+                    Log.e(TAG, "Could not write settings", ex)
+                    // Show error to user here
+                }
+                settingsStore.data // Switch to data flow since it is the source of truth.
+            }.collect {
+                // We update the toggle to the latest persisted value - whether or not the
+                // update succeeded. If the write failed, this will reset to original state.
+                fooToggle.isChecked = it.foo
+                fooToggle.isEnabled = true
+            }
+        }
+    }
+
+    private suspend fun DataStore<Settings>.setFoo(foo: Boolean) = updateData {
+        it.toBuilder().setFoo(foo).build()
+    }
+
+    private fun createFooPreference(context: Context) = SwitchPreference(context).apply {
+        isEnabled = false // Start out disabled
+        isPersistent = false // Disable SharedPreferences
+        title = "Foo title"
+        summary = "Summary of Foo toggle"
+    }
+}
+
+@ExperimentalCoroutinesApi
+private val Preference.changeFlow: Flow<Pair<Preference?, Any?>>
+    get() = callbackFlow {
+        this@changeFlow.setOnPreferenceChangeListener { preference: Preference?, newValue: Any? ->
+            this@callbackFlow.launch {
+                send(Pair(preference, newValue))
+            }
+            false // Do not update the state of the toggle.
+        }
+
+        awaitClose { this@changeFlow.onPreferenceChangeListener = null }
+    }
+
+private object SettingsSerializer : Serializer<Settings> {
+    override fun readFrom(input: InputStream): Settings {
+        try {
+            return Settings.parseFrom(input)
+        } catch (ipbe: InvalidProtocolBufferException) {
+            throw CorruptionException("Cannot read proto.", ipbe)
+        }
+    }
+
+    override fun writeTo(t: Settings, output: OutputStream) = t.writeTo(output)
+}
\ No newline at end of file
diff --git a/datastore/datastore-sampleapp/src/main/proto/settings.proto b/datastore/datastore-sampleapp/src/main/proto/settings.proto
index 38fd074..1fb4e0a 100644
--- a/datastore/datastore-sampleapp/src/main/proto/settings.proto
+++ b/datastore/datastore-sampleapp/src/main/proto/settings.proto
@@ -5,4 +5,5 @@
 
 message Settings {
   int32 counter = 1;
+  bool foo = 2;
 }
diff --git a/datastore/datastore-sampleapp/src/main/res/layout/settings_fragment.xml b/datastore/datastore-sampleapp/src/main/res/layout/settings_fragment.xml
new file mode 100644
index 0000000..7a9bcbf
--- /dev/null
+++ b/datastore/datastore-sampleapp/src/main/res/layout/settings_fragment.xml
@@ -0,0 +1,33 @@
+<?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">
+
+    <fragment
+        android:id="@+id/fragment"
+        android:name="com.example.datastoresampleapp.SettingsFragment"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        tools:layout_editor_absoluteY="197dp" />
+</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
diff --git a/inspection/inspection-testing/src/main/java/androidx/inspection/testing/InspectorTester.kt b/inspection/inspection-testing/src/main/java/androidx/inspection/testing/InspectorTester.kt
index fd56c1e..18265b8 100644
--- a/inspection/inspection-testing/src/main/java/androidx/inspection/testing/InspectorTester.kt
+++ b/inspection/inspection-testing/src/main/java/androidx/inspection/testing/InspectorTester.kt
@@ -139,7 +139,6 @@
 open class DefaultTestInspectorEnvironment(
     private val testInspectorExecutors: InspectorExecutors
 ) : InspectorEnvironment {
-    constructor(parent: Job) : this(TestInspectorExecutors(parent))
 
     override fun registerEntryHook(
         originClass: Class<*>,
diff --git a/inspection/inspection-testing/src/main/java/androidx/inspection/testing/TestInspectorExecutors.kt b/inspection/inspection-testing/src/main/java/androidx/inspection/testing/TestInspectorExecutors.kt
index bdd680a..96adc63 100644
--- a/inspection/inspection-testing/src/main/java/androidx/inspection/testing/TestInspectorExecutors.kt
+++ b/inspection/inspection-testing/src/main/java/androidx/inspection/testing/TestInspectorExecutors.kt
@@ -21,6 +21,7 @@
 import androidx.inspection.InspectorExecutors
 import kotlinx.coroutines.Job
 import java.util.concurrent.Executor
+import java.util.concurrent.Executors
 import java.util.concurrent.RejectedExecutionException
 
 /**
@@ -29,10 +30,12 @@
  * HandlerThread created for inspector will quit once parent job completes.
  */
 class TestInspectorExecutors(
-    parentJob: Job
+    parentJob: Job,
+    ioExecutor: Executor? = null
 ) : InspectorExecutors {
     private val handlerThread = HandlerThread("Test Inspector Handler Thread")
     private val handler: Handler
+    private val ioExecutor: Executor
 
     init {
         handlerThread.start()
@@ -40,6 +43,11 @@
         parentJob.invokeOnCompletion {
             handlerThread.looper.quitSafely()
         }
+        this.ioExecutor = ioExecutor ?: Executors.newFixedThreadPool(4).also { executor ->
+            parentJob.invokeOnCompletion {
+                executor.shutdown()
+            }
+        }
     }
 
     override fun handler() = handler
@@ -50,7 +58,5 @@
         }
     }
 
-    override fun io(): Executor {
-        TODO()
-    }
+    override fun io() = ioExecutor
 }
\ No newline at end of file
diff --git a/leanback/leanback/src/androidTest/java/androidx/leanback/app/PlaybackFragmentTest.java b/leanback/leanback/src/androidTest/java/androidx/leanback/app/PlaybackFragmentTest.java
index 531670b..37b9400 100644
--- a/leanback/leanback/src/androidTest/java/androidx/leanback/app/PlaybackFragmentTest.java
+++ b/leanback/leanback/src/androidTest/java/androidx/leanback/app/PlaybackFragmentTest.java
@@ -540,7 +540,7 @@
         final ControlsOverlayAutoHideDisabledFragment fragment =
                 (ControlsOverlayAutoHideDisabledFragment) activity.getTestFragment();
 
-        // Sanity check that onViewCreated has made the controls invisible
+        // Validate that onViewCreated has made the controls invisible
         assertFalse(fragment.mControlVisible);
         activityTestRule.runOnUiThread(new Runnable() {
             public void run() {
diff --git a/leanback/leanback/src/androidTest/java/androidx/leanback/app/PlaybackSupportFragmentTest.java b/leanback/leanback/src/androidTest/java/androidx/leanback/app/PlaybackSupportFragmentTest.java
index 913c660..9e930c4 100644
--- a/leanback/leanback/src/androidTest/java/androidx/leanback/app/PlaybackSupportFragmentTest.java
+++ b/leanback/leanback/src/androidTest/java/androidx/leanback/app/PlaybackSupportFragmentTest.java
@@ -537,7 +537,7 @@
         final ControlsOverlayAutoHideDisabledFragment fragment =
                 (ControlsOverlayAutoHideDisabledFragment) activity.getTestFragment();
 
-        // Sanity check that onViewCreated has made the controls invisible
+        // Validate that onViewCreated has made the controls invisible
         assertFalse(fragment.mControlVisible);
         activityTestRule.runOnUiThread(new Runnable() {
             public void run() {
diff --git a/navigation/navigation-dynamic-features-fragment/src/androidTest/java/androidx/navigation/dynamicfeatures/fragment/DynamicNavHostFragmentTest.kt b/navigation/navigation-dynamic-features-fragment/src/androidTest/java/androidx/navigation/dynamicfeatures/fragment/DynamicNavHostFragmentTest.kt
index e12afd8..59e061f 100644
--- a/navigation/navigation-dynamic-features-fragment/src/androidTest/java/androidx/navigation/dynamicfeatures/fragment/DynamicNavHostFragmentTest.kt
+++ b/navigation/navigation-dynamic-features-fragment/src/androidTest/java/androidx/navigation/dynamicfeatures/fragment/DynamicNavHostFragmentTest.kt
@@ -23,7 +23,7 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.MediumTest
 import androidx.testutils.withActivity
-import org.junit.Assert.assertNotEquals
+import org.junit.Assert.assertEquals
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -47,7 +47,7 @@
                     .commitNow()
             }
         }
-        assertNotEquals(fragment.createSplitInstallManager(), fragment.createSplitInstallManager())
+        assertEquals(fragment.createSplitInstallManager(), fragment.createSplitInstallManager())
     }
 }
 
diff --git a/navigation/navigation-dynamic-features-fragment/src/main/java/androidx/navigation/dynamicfeatures/fragment/ui/AbstractProgressFragment.kt b/navigation/navigation-dynamic-features-fragment/src/main/java/androidx/navigation/dynamicfeatures/fragment/ui/AbstractProgressFragment.kt
index 7223b1a..aee34b8 100644
--- a/navigation/navigation-dynamic-features-fragment/src/main/java/androidx/navigation/dynamicfeatures/fragment/ui/AbstractProgressFragment.kt
+++ b/navigation/navigation-dynamic-features-fragment/src/main/java/androidx/navigation/dynamicfeatures/fragment/ui/AbstractProgressFragment.kt
@@ -29,6 +29,7 @@
 import androidx.navigation.dynamicfeatures.DynamicExtras
 import androidx.navigation.dynamicfeatures.DynamicInstallMonitor
 import androidx.navigation.fragment.findNavController
+import com.google.android.play.core.common.IntentSenderForResultStarter
 import com.google.android.play.core.splitinstall.SplitInstallSessionState
 import com.google.android.play.core.splitinstall.model.SplitInstallErrorCode
 import com.google.android.play.core.splitinstall.model.SplitInstallSessionStatus
@@ -128,11 +129,30 @@
                         navigate()
                     }
                     SplitInstallSessionStatus.REQUIRES_USER_CONFIRMATION -> try {
-                        @Suppress("DEPRECATION")
-                        // TODO replace once PlayCore ships with code landed in b/145276704.
-                        startIntentSenderForResult(
-                            sessionState.resolutionIntent().intentSender,
-                            INSTALL_REQUEST_CODE, null, 0, 0, 0, null
+                        val splitInstallManager = monitor.splitInstallManager
+                        if (splitInstallManager == null) {
+                            onFailed(SplitInstallErrorCode.INTERNAL_ERROR)
+                            return
+                        }
+                        splitInstallManager.startConfirmationDialogForResult(
+                            sessionState,
+                            IntentSenderForResultStarter { intent,
+                                                           requestCode,
+                                                           fillInIntent,
+                                                           flagsMask,
+                                                           flagsValues,
+                                                           extraFlags,
+                                                           options ->
+                                startIntentSenderForResult(
+                                    intent,
+                                    requestCode,
+                                    fillInIntent,
+                                    flagsMask,
+                                    flagsValues,
+                                    extraFlags,
+                                    options
+                                )
+                            }, INSTALL_REQUEST_CODE
                         )
                     } catch (e: IntentSender.SendIntentException) {
                         onFailed(SplitInstallErrorCode.INTERNAL_ERROR)
diff --git a/navigation/navigation-dynamic-features-runtime/src/main/java/androidx/navigation/dynamicfeatures/DynamicInstallMonitor.kt b/navigation/navigation-dynamic-features-runtime/src/main/java/androidx/navigation/dynamicfeatures/DynamicInstallMonitor.kt
index e912d75..6e53c5f 100644
--- a/navigation/navigation-dynamic-features-runtime/src/main/java/androidx/navigation/dynamicfeatures/DynamicInstallMonitor.kt
+++ b/navigation/navigation-dynamic-features-runtime/src/main/java/androidx/navigation/dynamicfeatures/DynamicInstallMonitor.kt
@@ -69,8 +69,10 @@
 
     /**
      * The [SplitInstallManager] used to monitor the installation if any was set.
+     * @hide
      */
-    internal var splitInstallManager: SplitInstallManager? = null
+    @get:RestrictTo(RestrictTo.Scope.LIBRARY)
+    var splitInstallManager: SplitInstallManager? = null
 
     /**
      * `true` if the monitor has been used to request an install, else
diff --git a/playground-common/README.md b/playground-common/README.md
index 59bd21f..99fb098 100644
--- a/playground-common/README.md
+++ b/playground-common/README.md
@@ -41,6 +41,21 @@
 method or filter projects from the main AndroidX settings gradle file using the
 `selectProjectsFromAndroidX` method.
 
+### Properties
+When a `gradle.properties` file shows up under a sub project, main AndroidX build ends up
+reading it. For this reason, we can only keep a minimal `gradle.properties` file in these
+sub modules that also support playground setup.
+
+We cannot avoid creating `gradle.properties` as certain properties (e.g. `useAndroidX`) are
+read at configuration time and we cannot set it dynamically.
+
+Properties that will be set dynamically are kept in `playground.properties` file while
+shared properties are kept in `androidx-shared.properties` file.
+The dynamic properties are read in the `playground-include-settings.gradle` file and set
+on each project.
+
+There is a `VerifyPlaygroundGradlePropertiesTask` task that validates the contents of
+`androidx-shared.properties` file as part of the main AndroidX build.
 ### Optional Dependencies
 Even though sub-projects usually declare exact coordinates for their dependencies,
 for tests, it is a common practice to declare `project` dependencies. To avoid needing
diff --git a/playground-common/androidx-shared.properties b/playground-common/androidx-shared.properties
new file mode 100644
index 0000000..606adab
--- /dev/null
+++ b/playground-common/androidx-shared.properties
@@ -0,0 +1,33 @@
+#
+# 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.
+#
+
+# Properties that are copied from main properties file
+# We set playground properties in two steps:
+# * This file is linked into gradle.properties under the project and limited to
+#   just copying properties from the androidx properties file without any change.
+#   Its integrity is validated as part of the buildOnServer task in AndroidX.
+#   (validatePlaygroundGradleProperties task)
+# * Additional settings are in playground.properties which are loaded dynamically
+# This separation is necessary to ensure gradle can read certain properties
+# at configuration time.
+
+android.useAndroidX=true
+# Disable features we do not use
+android.defaults.buildfeatures.aidl=false
+android.defaults.buildfeatures.buildconfig=false
+android.defaults.buildfeatures.renderscript=false
+android.defaults.buildfeatures.resvalues=false
+android.defaults.buildfeatures.shaders=false
diff --git a/playground-common/playground.properties b/playground-common/playground.properties
index 86cffc9..d02bbef 100644
--- a/playground-common/playground.properties
+++ b/playground-common/playground.properties
@@ -14,15 +14,6 @@
 # limitations under the License.
 #
 
-# Project-wide Gradle settings.
-# IDE (e.g. Android Studio) users:
-# Gradle settings configured through the IDE *will override*
-# any settings specified in this file.
-# For more details on how to configure your build environment visit
-# http://www.gradle.org/docs/current/userguide/build_environment.html
-# Specifies the JVM arguments used for the daemon process.
-# The setting is particularly useful for tweaking memory settings.
-org.gradle.jvmargs=-Xmx2048m
 # When configured, Gradle will run in incubating parallel mode.
 # This option should only be used with decoupled projects. More details, visit
 # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
@@ -30,10 +21,8 @@
 # AndroidX package structure to make it clearer which packages are bundled with the
 # Android operating system, and which are packaged with your app"s APK
 # https://developer.android.com/topic/libraries/support-library/androidx-rn
-android.useAndroidX=true
-# Automatically convert third-party libraries to use AndroidX
-android.enableJetifier=true
 # Kotlin code style for this project: "official" or "obsolete":
+org.gradle.jvmargs=-Xmx2048m
 kotlin.code.style=official
 # Disable docs
 androidx.enableDocumentation=false
@@ -43,10 +32,3 @@
 androidx.playground.metalavaBuildId=6604778
 androidx.playground.dokkaBuildId=6656073
 androidx.studio.type=playground
-
-# Disable features we do not use
-android.defaults.buildfeatures.aidl=false
-android.defaults.buildfeatures.buildconfig=false
-android.defaults.buildfeatures.renderscript=false
-android.defaults.buildfeatures.resvalues=false
-android.defaults.buildfeatures.shaders=false
diff --git a/playground-common/setup-playground.sh b/playground-common/setup-playground.sh
index 650ae3d..ee41940 100755
--- a/playground-common/setup-playground.sh
+++ b/playground-common/setup-playground.sh
@@ -17,6 +17,8 @@
 ln -s "${PLAYGROUND_REL_PATH}/gradlew" gradlew
 rm -rf gradlew.bat
 ln -s "${PLAYGROUND_REL_PATH}/gradlew.bat" gradlew.bat
+rm -rf gradle.properties
+ln -s "${PLAYGROUND_REL_PATH}/androidx-shared.properties" gradle.properties
 
 ANDROIDX_IDEA_DIR="${PLAYGROUND_REL_PATH}/../.idea"
 
diff --git a/playground/gradle.properties b/playground/gradle.properties
new file mode 120000
index 0000000..d952fb0
--- /dev/null
+++ b/playground/gradle.properties
@@ -0,0 +1 @@
+../playground-common/androidx-shared.properties
\ No newline at end of file
diff --git a/room/gradle.properties b/room/gradle.properties
new file mode 120000
index 0000000..d952fb0
--- /dev/null
+++ b/room/gradle.properties
@@ -0,0 +1 @@
+../playground-common/androidx-shared.properties
\ No newline at end of file
diff --git a/slices/view/src/main/java/androidx/slice/widget/SliceStyle.java b/slices/view/src/main/java/androidx/slice/widget/SliceStyle.java
index 6772cf3..4f45a45 100644
--- a/slices/view/src/main/java/androidx/slice/widget/SliceStyle.java
+++ b/slices/view/src/main/java/androidx/slice/widget/SliceStyle.java
@@ -78,6 +78,8 @@
     private int mListMinScrollHeight;
     private int mListLargeHeight;
 
+    private boolean mExpandToAvailableHeight;
+
     private RowStyle mRowStyle;
 
     public SliceStyle(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
@@ -142,6 +144,9 @@
                     R.dimen.abc_slice_row_range_inline_height);
             mRowInlineRangeHeight = (int) a.getDimension(
                     R.styleable.SliceView_rowInlineRangeHeight, defaultRowInlineRangeHeight);
+
+            mExpandToAvailableHeight = a.getBoolean(
+                    R.styleable.SliceView_expandToAvailableHeight, false);
         } finally {
             a.recycle();
         }
@@ -253,6 +258,10 @@
         return mRowSelectionHeight;
     }
 
+    public boolean getExpandToAvailableHeight() {
+        return mExpandToAvailableHeight;
+    }
+
     public int getRowHeight(RowContent row, SliceViewPolicy policy) {
         int maxHeight = policy.getMaxSmallHeight() > 0 ? policy.getMaxSmallHeight() : mRowMaxHeight;
 
@@ -335,7 +344,7 @@
         boolean bigEnoughToScroll = desiredHeight - maxLargeHeight >= mListMinScrollHeight;
 
         // Adjust for scrolling
-        int height = bigEnoughToScroll ? maxLargeHeight
+        int height = bigEnoughToScroll && !getExpandToAvailableHeight() ? maxLargeHeight
                 : maxHeight <= 0 ? desiredHeight
                 : Math.min(maxLargeHeight, desiredHeight);
         if (!scrollable) {
diff --git a/slices/view/src/main/java/androidx/slice/widget/SliceView.java b/slices/view/src/main/java/androidx/slice/widget/SliceView.java
index 6e3e26a..df6eabe 100644
--- a/slices/view/src/main/java/androidx/slice/widget/SliceView.java
+++ b/slices/view/src/main/java/androidx/slice/widget/SliceView.java
@@ -395,7 +395,10 @@
                     childrenHeight = requiredHeight;
                 } else {
                     // Not enough space available for slice in current mode
-                    if (getMode() == MODE_LARGE
+                    if (mSliceStyle.getExpandToAvailableHeight()) {
+                        // Don't request more space than we're allowed to have.
+                        requiredHeight = childrenHeight;
+                    } else if (getMode() == MODE_LARGE
                             && childrenHeight >= mLargeHeight + actionHeight) {
                         childrenHeight = mLargeHeight + actionHeight;
                     } else if (childrenHeight <= mMinTemplateHeight) {
diff --git a/slices/view/src/main/res-public/values/public_attrs.xml b/slices/view/src/main/res-public/values/public_attrs.xml
index 5e8825d..599f927f 100644
--- a/slices/view/src/main/res-public/values/public_attrs.xml
+++ b/slices/view/src/main/res-public/values/public_attrs.xml
@@ -58,4 +58,5 @@
     <public type="attr" name="iconSize" />
     <public type="attr" name="imageSize" />
     <public type="attr" name="disableRecyclerViewItemAnimator" />
+    <public type="attr" name="expandToAvailableHeight" />
 </resources>
diff --git a/slices/view/src/main/res/values/attrs.xml b/slices/view/src/main/res/values/attrs.xml
index 8bb15c2..e440ce2 100644
--- a/slices/view/src/main/res/values/attrs.xml
+++ b/slices/view/src/main/res/values/attrs.xml
@@ -69,6 +69,14 @@
         <attr name="rowRangeSingleTextHeight" format="dimension" />
         <!-- Size of row view when range is inline -->
         <attr name="rowInlineRangeHeight" format="dimension" />
+
+        <!-- Removes the height restriction of slices in MODE_LARGE. If the slice
+         is smaller than the available height, wrap_content decides whether the slice takes
+         up the entire height or only the required height. If the slice is bigger than
+         the available height, the height mode decides whether the slice fills the height
+         (height mode AT_MOST/EXACTLY), or expands to fit all items (height mode UNSPECIFIED).
+         -->
+        <attr name="expandToAvailableHeight" format="boolean" />
     </declare-styleable>
 
     <!-- To apply a style for all slices shown within an activity or app you
diff --git a/sqlite/integration-tests/inspection-room-testapp/src/androidTest/java/androidx/sqlite/inspection/RoomInvalidationHookTest.kt b/sqlite/integration-tests/inspection-room-testapp/src/androidTest/java/androidx/sqlite/inspection/RoomInvalidationHookTest.kt
index 54c108d..7152e8e 100644
--- a/sqlite/integration-tests/inspection-room-testapp/src/androidTest/java/androidx/sqlite/inspection/RoomInvalidationHookTest.kt
+++ b/sqlite/integration-tests/inspection-room-testapp/src/androidTest/java/androidx/sqlite/inspection/RoomInvalidationHookTest.kt
@@ -17,9 +17,7 @@
 package androidx.sqlite.inspection
 
 import android.database.sqlite.SQLiteDatabase
-import androidx.inspection.Connection
 import androidx.inspection.InspectorEnvironment
-import androidx.inspection.InspectorFactory
 import androidx.inspection.testing.DefaultTestInspectorEnvironment
 import androidx.inspection.testing.InspectorTester
 import androidx.inspection.testing.TestInspectorExecutors
@@ -45,8 +43,10 @@
 @RunWith(AndroidJUnit4::class)
 class RoomInvalidationHookTest {
     private lateinit var db: TestDatabase
-    private val inspectionExecutors =
-        Pair(Executors.newSingleThreadExecutor(), Executors.newSingleThreadScheduledExecutor())
+
+    private val testJob = Job()
+    private val ioExecutor = Executors.newSingleThreadExecutor()
+    private val testInspectorExecutors = TestInspectorExecutors(testJob, ioExecutor)
 
     @Before
     fun initDb() {
@@ -62,14 +62,14 @@
 
     @After
     fun closeDb() {
-        listOf(inspectionExecutors.first, inspectionExecutors.second)
-            .forEach { inspectionExecutor ->
-                inspectionExecutor.shutdown()
-                assertWithMessage("inspector should not have any leaking tasks")
-                    .that(inspectionExecutor.awaitTermination(10, TimeUnit.SECONDS))
-                    .isTrue()
-                db.close()
-            }
+        testJob.complete()
+        ioExecutor.shutdown()
+        assertWithMessage("inspector should not have any leaking tasks")
+            .that(ioExecutor.awaitTermination(10, TimeUnit.SECONDS))
+            .isTrue()
+
+        testInspectorExecutors.handler().looper.thread.join(10_000)
+        db.close()
     }
 
     /**
@@ -77,28 +77,15 @@
      * invalidation observer on the Room side is invoked.
      */
     @Test
-    fun invalidationHook() = runBlocking<Unit> {
+    fun invalidationHook() = runBlocking<Unit>(testJob) {
         val testEnv = TestInspectorEnvironment(
             roomDatabase = db,
             sqliteDb = db.getSqliteDb(),
-            parentJob = this.coroutineContext[Job]!!
+            inspectorExecutors = testInspectorExecutors
         )
         val tester = InspectorTester(
-            inspectorId = "test",
-            environment = testEnv,
-            factoryOverride = object : InspectorFactory<SqliteInspector>("test") {
-                override fun createInspector(
-                    connection: Connection,
-                    environment: InspectorEnvironment
-                ): SqliteInspector {
-                    return SqliteInspector(
-                        connection,
-                        environment,
-                        inspectionExecutors.first,
-                        inspectionExecutors.second
-                    )
-                }
-            }
+            inspectorId = "androidx.sqlite.inspection",
+            environment = testEnv
         )
         val invalidatedTables = CompletableDeferred<List<String>>()
         db.invalidationTracker.addObserver(object : InvalidationTracker.Observer("TestEntity") {
@@ -151,8 +138,8 @@
 class TestInspectorEnvironment(
     private val roomDatabase: RoomDatabase,
     private val sqliteDb: SQLiteDatabase,
-    parentJob: Job
-) : DefaultTestInspectorEnvironment(TestInspectorExecutors(parentJob)) {
+    inspectorExecutors: TestInspectorExecutors
+) : DefaultTestInspectorEnvironment(inspectorExecutors) {
     override fun registerEntryHook(
         originClass: Class<*>,
         originMethod: String,
diff --git a/sqlite/integration-tests/inspection-sqldelight-testapp/src/androidTest/java/androidx/sqlite/inspection/SqlDelightInvalidationTest.kt b/sqlite/integration-tests/inspection-sqldelight-testapp/src/androidTest/java/androidx/sqlite/inspection/SqlDelightInvalidationTest.kt
index 9de4bb9..5313cbe 100644
--- a/sqlite/integration-tests/inspection-sqldelight-testapp/src/androidTest/java/androidx/sqlite/inspection/SqlDelightInvalidationTest.kt
+++ b/sqlite/integration-tests/inspection-sqldelight-testapp/src/androidTest/java/androidx/sqlite/inspection/SqlDelightInvalidationTest.kt
@@ -17,9 +17,7 @@
 package androidx.sqlite.inspection
 
 import android.database.sqlite.SQLiteDatabase
-import androidx.inspection.Connection
 import androidx.inspection.InspectorEnvironment
-import androidx.inspection.InspectorFactory
 import androidx.inspection.testing.DefaultTestInspectorEnvironment
 import androidx.inspection.testing.InspectorTester
 import androidx.inspection.testing.TestInspectorExecutors
@@ -48,7 +46,6 @@
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
-import java.util.concurrent.Executors
 
 @ExperimentalCoroutinesApi
 @RunWith(AndroidJUnit4::class)
@@ -79,8 +76,11 @@
             val sqliteDb = openedDb.getSqliteDb()
             val query = dao.selectAll()
             val job = this.coroutineContext[Job]!!
-            val tester = sqliteInspectorTester(TestInspectorEnvironment(sqliteDb, listOf(query),
-                job))
+            val tester = InspectorTester(
+                inspectorId = "androidx.sqlite.inspection",
+                environment = TestInspectorEnvironment(sqliteDb, listOf(query),
+                            job)
+            )
             val updates = query.asFlow().mapToList().take(2).produceIn(this)
 
             val firstExpected = TestEntity.Impl(1, "one")
@@ -133,24 +133,6 @@
     } as SQLiteDatabase
 }
 
-suspend fun sqliteInspectorTester(environment: InspectorEnvironment) = InspectorTester(
-    inspectorId = "test",
-    environment = environment,
-    factoryOverride = object : InspectorFactory<SqliteInspector>("test") {
-        override fun createInspector(
-            connection: Connection,
-            environment: InspectorEnvironment
-        ): SqliteInspector {
-            return SqliteInspector(
-                connection,
-                environment,
-                Executors.newSingleThreadExecutor(),
-                Executors.newSingleThreadScheduledExecutor()
-            )
-        }
-    }
-)
-
 @Suppress("UNCHECKED_CAST")
 class TestInspectorEnvironment(
     private val sqliteDb: SQLiteDatabase,
diff --git a/sqlite/sqlite-inspection/src/androidTest/java/androidx/sqlite/inspection/test/CancellationQueryTest.kt b/sqlite/sqlite-inspection/src/androidTest/java/androidx/sqlite/inspection/test/CancellationQueryTest.kt
index db4ee0c..873ccbd 100644
--- a/sqlite/sqlite-inspection/src/androidTest/java/androidx/sqlite/inspection/test/CancellationQueryTest.kt
+++ b/sqlite/sqlite-inspection/src/androidTest/java/androidx/sqlite/inspection/test/CancellationQueryTest.kt
@@ -16,7 +16,6 @@
 
 package androidx.sqlite.inspection.test
 
-import androidx.sqlite.inspection.SqliteInspectorFactory
 import androidx.sqlite.inspection.test.CountingDelegatingExecutorService.Event.FINISHED
 import androidx.sqlite.inspection.test.CountingDelegatingExecutorService.Event.STARTED
 import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -35,16 +34,14 @@
 import org.junit.runner.RunWith
 import java.util.concurrent.Executor
 import java.util.concurrent.Executors.newCachedThreadPool
-import java.util.concurrent.Executors.newSingleThreadScheduledExecutor
 
 @MediumTest
 @RunWith(AndroidJUnit4::class)
 class CancellationQueryTest {
     private val countingExecutorService = CountingDelegatingExecutorService(newCachedThreadPool())
     @get:Rule
-    val environment = SqliteInspectorTestEnvironment(
-        SqliteInspectorFactory(countingExecutorService, newSingleThreadScheduledExecutor())
-    )
+    val environment = SqliteInspectorTestEnvironment(countingExecutorService)
+
     @get:Rule
     val temporaryFolder = TemporaryFolder(getInstrumentation().context.cacheDir)
 
diff --git a/sqlite/sqlite-inspection/src/androidTest/java/androidx/sqlite/inspection/test/SqliteInspectorTestEnvironment.kt b/sqlite/sqlite-inspection/src/androidTest/java/androidx/sqlite/inspection/test/SqliteInspectorTestEnvironment.kt
index 17e8b3d..022b3d5 100644
--- a/sqlite/sqlite-inspection/src/androidTest/java/androidx/sqlite/inspection/test/SqliteInspectorTestEnvironment.kt
+++ b/sqlite/sqlite-inspection/src/androidTest/java/androidx/sqlite/inspection/test/SqliteInspectorTestEnvironment.kt
@@ -22,7 +22,6 @@
 import androidx.inspection.testing.InspectorTester
 import androidx.inspection.testing.DefaultTestInspectorEnvironment
 import androidx.inspection.testing.TestInspectorExecutors
-import androidx.sqlite.inspection.SqliteInspectorFactory
 import androidx.sqlite.inspection.SqliteInspectorProtocol
 import androidx.sqlite.inspection.SqliteInspectorProtocol.Command
 import androidx.sqlite.inspection.SqliteInspectorProtocol.DatabaseOpenedEvent
@@ -34,23 +33,23 @@
 import kotlinx.coroutines.cancelAndJoin
 import kotlinx.coroutines.runBlocking
 import org.junit.rules.ExternalResource
+import java.util.concurrent.Executor
 
 private const val SQLITE_INSPECTOR_ID = "androidx.sqlite.inspection"
 
 class SqliteInspectorTestEnvironment(
-    val factoryOverride: SqliteInspectorFactory? = null
+    val ioExecutorOverride: Executor? = null
 ) : ExternalResource() {
     private lateinit var inspectorTester: InspectorTester
     private lateinit var environment: FakeInspectorEnvironment
     private val job = Job()
 
     override fun before() {
-        environment = FakeInspectorEnvironment(job)
+        environment = FakeInspectorEnvironment(job, TestInspectorExecutors(job, ioExecutorOverride))
         inspectorTester = runBlocking {
             InspectorTester(
                 inspectorId = SQLITE_INSPECTOR_ID,
-                environment = environment,
-                factoryOverride = factoryOverride
+                environment = environment
             )
         }
     }
@@ -132,8 +131,9 @@
  * retrieved in [consumeRegisteredHooks].
  */
 private class FakeInspectorEnvironment(
-    job: Job
-) : DefaultTestInspectorEnvironment(TestInspectorExecutors(job)) {
+    job: Job,
+    executors: TestInspectorExecutors = TestInspectorExecutors(job)
+) : DefaultTestInspectorEnvironment(executors) {
     private val instancesToFind = mutableListOf<Any>()
     private val registeredHooks = mutableListOf<Hook>()
 
diff --git a/sqlite/sqlite-inspection/src/main/java/androidx/sqlite/inspection/SqliteInspectionExecutors.java b/sqlite/sqlite-inspection/src/main/java/androidx/sqlite/inspection/SqliteInspectionExecutors.java
index ceae951..2f66151 100644
--- a/sqlite/sqlite-inspection/src/main/java/androidx/sqlite/inspection/SqliteInspectionExecutors.java
+++ b/sqlite/sqlite-inspection/src/main/java/androidx/sqlite/inspection/SqliteInspectionExecutors.java
@@ -16,67 +16,14 @@
 
 package androidx.sqlite.inspection;
 
-import androidx.annotation.NonNull;
-
-import java.util.Locale;
 import java.util.concurrent.Executor;
-import java.util.concurrent.Executors;
 import java.util.concurrent.Future;
 import java.util.concurrent.FutureTask;
-import java.util.concurrent.ScheduledExecutorService;
-import java.util.concurrent.ThreadFactory;
-import java.util.concurrent.atomic.AtomicLong;
 
 class SqliteInspectionExecutors {
     private SqliteInspectionExecutors() {
     }
 
-    private static final ThreadFactory sThreadFactory = new ThreadFactory() {
-        AtomicLong mNextId = new AtomicLong(0);
-
-        @Override
-        public Thread newThread(@NonNull Runnable target) {
-            Thread thread = new Thread(target, generateThreadName());
-            thread.setDaemon(true); // Don't prevent JVM from exiting
-            return thread;
-        }
-
-        private String generateThreadName() {
-            return String.format(Locale.ROOT, "Studio:SqlIns%d",
-                    mNextId.getAndIncrement());
-        }
-    };
-
-    private static final Executor sDirectExecutor = new Executor() {
-        @Override
-        public void execute(@NonNull Runnable command) {
-            command.run();
-        }
-    };
-
-    private static final Executor sIOExecutor = Executors.newCachedThreadPool(sThreadFactory);
-
-    private static final ScheduledExecutorService sScheduledExecutorService =
-            Executors.newSingleThreadScheduledExecutor(sThreadFactory);
-
-    static Executor directExecutor() {
-        return sDirectExecutor;
-    }
-
-    static Executor ioExecutor() {
-        return sIOExecutor;
-    }
-
-    /**
-     * Single threaded ScheduledExecutor.
-     * <p>
-     * Since single threaded, only use for short tasks, e.g.
-     * scheduling tasks to be executed on another thread.
-     */
-    static ScheduledExecutorService scheduledExecutor() {
-        return sScheduledExecutorService;
-    }
-
     static Future<Void> submit(Executor executor, Runnable runnable) {
         FutureTask<Void> task = new FutureTask<>(runnable, null);
         executor.execute(task);
diff --git a/sqlite/sqlite-inspection/src/main/java/androidx/sqlite/inspection/SqliteInspector.java b/sqlite/sqlite-inspection/src/main/java/androidx/sqlite/inspection/SqliteInspector.java
index 497e580..13c0bc3 100644
--- a/sqlite/sqlite-inspection/src/main/java/androidx/sqlite/inspection/SqliteInspector.java
+++ b/sqlite/sqlite-inspection/src/main/java/androidx/sqlite/inspection/SqliteInspector.java
@@ -19,7 +19,6 @@
 import static android.database.DatabaseUtils.getSqlStatementType;
 
 import static androidx.sqlite.inspection.DatabaseExtensions.isAttemptAtUsingClosedDatabase;
-import static androidx.sqlite.inspection.SqliteInspectionExecutors.directExecutor;
 import static androidx.sqlite.inspection.SqliteInspectorProtocol.ErrorContent.ErrorCode.ERROR_DB_CLOSED_DURING_OPERATION;
 import static androidx.sqlite.inspection.SqliteInspectorProtocol.ErrorContent.ErrorCode.ERROR_ISSUE_WITH_PROCESSING_NEW_DATABASE_CONNECTION;
 import static androidx.sqlite.inspection.SqliteInspectorProtocol.ErrorContent.ErrorCode.ERROR_ISSUE_WITH_PROCESSING_QUERY;
@@ -87,8 +86,6 @@
 import java.util.WeakHashMap;
 import java.util.concurrent.Executor;
 import java.util.concurrent.Future;
-import java.util.concurrent.ScheduledExecutorService;
-import java.util.concurrent.TimeUnit;
 
 /**
  * Inspector to work with SQLite databases
@@ -171,7 +168,7 @@
     private final DatabaseRegistry mDatabaseRegistry;
     private final InspectorEnvironment mEnvironment;
     private final Executor mIOExecutor;
-    private final ScheduledExecutorService mScheduledExecutor;
+
     /**
      * Utility instance that handles communication with Room's InvalidationTracker instances.
      */
@@ -180,12 +177,10 @@
     @NonNull
     private final SqlDelightInvalidation mSqlDelightInvalidation;
 
-    SqliteInspector(@NonNull Connection connection, InspectorEnvironment environment,
-            Executor ioExecutor, ScheduledExecutorService scheduledExecutor) {
+    SqliteInspector(@NonNull Connection connection, @NonNull InspectorEnvironment environment) {
         super(connection);
         mEnvironment = environment;
-        mIOExecutor = ioExecutor;
-        mScheduledExecutor = scheduledExecutor;
+        mIOExecutor = environment.executors().io();
         mRoomInvalidationRegistry = new RoomInvalidationRegistry(mEnvironment);
         mSqlDelightInvalidation = SqlDelightInvalidation.create(mEnvironment);
 
@@ -349,12 +344,12 @@
                     @Override
                     @SuppressWarnings("FutureReturnValueIgnored") // TODO: handle errors from Future
                     public void schedule(final Runnable command, final long delayMs) {
-                        mScheduledExecutor.schedule(new Runnable() {
+                        mEnvironment.executors().handler().postDelayed(new Runnable() {
                             @Override
                             public void run() {
                                 mIOExecutor.execute(command);
                             }
-                        }, delayMs, TimeUnit.MILLISECONDS);
+                        }, delayMs);
                     }
                 };
         final RequestCollapsingThrottler throttler = new RequestCollapsingThrottler(
@@ -553,7 +548,7 @@
                 }
             }
         });
-        callback.addCancellationListener(directExecutor(), new Runnable() {
+        callback.addCancellationListener(mEnvironment.executors().primary(), new Runnable() {
             @Override
             public void run() {
                 cancellationSignal.cancel();
diff --git a/sqlite/sqlite-inspection/src/main/java/androidx/sqlite/inspection/SqliteInspectorFactory.java b/sqlite/sqlite-inspection/src/main/java/androidx/sqlite/inspection/SqliteInspectorFactory.java
index abd17a8..db69c72 100644
--- a/sqlite/sqlite-inspection/src/main/java/androidx/sqlite/inspection/SqliteInspectorFactory.java
+++ b/sqlite/sqlite-inspection/src/main/java/androidx/sqlite/inspection/SqliteInspectorFactory.java
@@ -17,39 +17,25 @@
 package androidx.sqlite.inspection;
 
 import androidx.annotation.NonNull;
-import androidx.annotation.VisibleForTesting;
 import androidx.inspection.Connection;
 import androidx.inspection.InspectorEnvironment;
 import androidx.inspection.InspectorFactory;
 
-import java.util.concurrent.Executor;
-import java.util.concurrent.ScheduledExecutorService;
-
 /**
  * Factory for SqliteInspector
  */
 public final class SqliteInspectorFactory extends InspectorFactory<SqliteInspector> {
     private static final String SQLITE_INSPECTOR_ID = "androidx.sqlite.inspection";
-    private final Executor mIOExecutor;
-    private final ScheduledExecutorService mScheduledExecutorService;
-
-    @VisibleForTesting
-    public SqliteInspectorFactory(@NonNull Executor ioExecutor,
-            @NonNull ScheduledExecutorService scheduledExecutorService) {
-        super(SQLITE_INSPECTOR_ID);
-        mIOExecutor = ioExecutor;
-        mScheduledExecutorService = scheduledExecutorService;
-    }
 
     @SuppressWarnings("unused") // called by ServiceLoader
     public SqliteInspectorFactory() {
-        this(SqliteInspectionExecutors.ioExecutor(), SqliteInspectionExecutors.scheduledExecutor());
+        super(SQLITE_INSPECTOR_ID);
     }
 
     @NonNull
     @Override
     public SqliteInspector createInspector(@NonNull Connection connection,
             @NonNull InspectorEnvironment environment) {
-        return new SqliteInspector(connection, environment, mIOExecutor, mScheduledExecutorService);
+        return new SqliteInspector(connection, environment);
     }
 }
diff --git a/ui/ui-core/src/androidMain/kotlin/androidx/compose/ui/platform/DisposableUiSavedStateRegistry.kt b/ui/ui-core/src/androidMain/kotlin/androidx/compose/ui/platform/DisposableUiSavedStateRegistry.kt
index a21f1db..d549c4d 100644
--- a/ui/ui-core/src/androidMain/kotlin/androidx/compose/ui/platform/DisposableUiSavedStateRegistry.kt
+++ b/ui/ui-core/src/androidMain/kotlin/androidx/compose/ui/platform/DisposableUiSavedStateRegistry.kt
@@ -67,7 +67,7 @@
 
     val androidxRegistry = savedStateRegistryOwner.savedStateRegistry
     val bundle = androidxRegistry.consumeRestoredStateForKey(key)
-    val restored: Map<String, Any>? = bundle?.toMap()
+    val restored: Map<String, List<Any?>>? = bundle?.toMap()
 
     val uiSavedStateRegistry = UiSavedStateRegistry(restored) {
         canBeSavedToBundle(it)
@@ -142,33 +142,23 @@
     SizeF::class.java
 )
 
-private val ValuesKey = "values"
-private val KeysKey = "keys"
-
-private fun Bundle.toMap(): Map<String, Any>? {
-    val keys = getStringArrayList(KeysKey)
-    @Suppress("UNCHECKED_CAST")
-    val values = getParcelableArrayList<Parcelable>(ValuesKey) as ArrayList<Any>?
-    check(keys != null && values != null && keys.size == values.size) {
-        "Invalid bundle passed as restored state"
-    }
-    val map = mutableMapOf<String, Any>()
-    for (i in keys.indices) {
-        map[keys[i]] = values[i]
+private fun Bundle.toMap(): Map<String, List<Any?>>? {
+    val map = mutableMapOf<String, List<Any?>>()
+    this.keySet().forEach { key ->
+        map[key] = getParcelableArrayList<Parcelable?>(key) as List<Any?>
     }
     return map
 }
 
-private fun Map<String, Any>.toBundle(): Bundle {
-    val keys = ArrayList<String>(size)
-    val values = ArrayList<Any>(size)
-    forEach { (key, value) ->
-        keys.add(key)
-        values.add(value)
-    }
+private fun Map<String, List<Any?>>.toBundle(): Bundle {
     val bundle = Bundle()
-    bundle.putStringArrayList(KeysKey, keys)
-    @Suppress("UNCHECKED_CAST")
-    bundle.putParcelableArrayList(ValuesKey, values as ArrayList<Parcelable>)
+    forEach { (key, list) ->
+        val arrayList = if (list is ArrayList<*>) list else ArrayList(list)
+        @Suppress("UNCHECKED_CAST")
+        bundle.putParcelableArrayList(
+            key,
+            arrayList as ArrayList<Parcelable?>
+        )
+    }
     return bundle
 }
diff --git a/ui/ui-saved-instance-state/api/current.txt b/ui/ui-saved-instance-state/api/current.txt
index bcbdb06..7e0a56e 100644
--- a/ui/ui-saved-instance-state/api/current.txt
+++ b/ui/ui-saved-instance-state/api/current.txt
@@ -34,13 +34,13 @@
   public interface UiSavedStateRegistry {
     method public boolean canBeSaved(Object value);
     method public Object? consumeRestored(String key);
-    method public java.util.Map<java.lang.String,java.lang.Object> performSave();
+    method public java.util.Map<java.lang.String,java.util.List<java.lang.Object>> performSave();
     method public void registerProvider(String key, kotlin.jvm.functions.Function0<?> valueProvider);
-    method public void unregisterProvider(String key);
+    method public void unregisterProvider(String key, kotlin.jvm.functions.Function0<?> valueProvider);
   }
 
   public final class UiSavedStateRegistryKt {
-    method public static androidx.compose.runtime.savedinstancestate.UiSavedStateRegistry UiSavedStateRegistry(java.util.Map<java.lang.String,?>? restoredValues, kotlin.jvm.functions.Function1<java.lang.Object,java.lang.Boolean> canBeSaved);
+    method public static androidx.compose.runtime.savedinstancestate.UiSavedStateRegistry UiSavedStateRegistry(java.util.Map<java.lang.String,? extends java.util.List<?>>? restoredValues, kotlin.jvm.functions.Function1<java.lang.Object,java.lang.Boolean> canBeSaved);
     method public static androidx.compose.runtime.ProvidableAmbient<androidx.compose.runtime.savedinstancestate.UiSavedStateRegistry> getUiSavedStateRegistryAmbient();
   }
 
diff --git a/ui/ui-saved-instance-state/api/public_plus_experimental_current.txt b/ui/ui-saved-instance-state/api/public_plus_experimental_current.txt
index bcbdb06..7e0a56e 100644
--- a/ui/ui-saved-instance-state/api/public_plus_experimental_current.txt
+++ b/ui/ui-saved-instance-state/api/public_plus_experimental_current.txt
@@ -34,13 +34,13 @@
   public interface UiSavedStateRegistry {
     method public boolean canBeSaved(Object value);
     method public Object? consumeRestored(String key);
-    method public java.util.Map<java.lang.String,java.lang.Object> performSave();
+    method public java.util.Map<java.lang.String,java.util.List<java.lang.Object>> performSave();
     method public void registerProvider(String key, kotlin.jvm.functions.Function0<?> valueProvider);
-    method public void unregisterProvider(String key);
+    method public void unregisterProvider(String key, kotlin.jvm.functions.Function0<?> valueProvider);
   }
 
   public final class UiSavedStateRegistryKt {
-    method public static androidx.compose.runtime.savedinstancestate.UiSavedStateRegistry UiSavedStateRegistry(java.util.Map<java.lang.String,?>? restoredValues, kotlin.jvm.functions.Function1<java.lang.Object,java.lang.Boolean> canBeSaved);
+    method public static androidx.compose.runtime.savedinstancestate.UiSavedStateRegistry UiSavedStateRegistry(java.util.Map<java.lang.String,? extends java.util.List<?>>? restoredValues, kotlin.jvm.functions.Function1<java.lang.Object,java.lang.Boolean> canBeSaved);
     method public static androidx.compose.runtime.ProvidableAmbient<androidx.compose.runtime.savedinstancestate.UiSavedStateRegistry> getUiSavedStateRegistryAmbient();
   }
 
diff --git a/ui/ui-saved-instance-state/api/restricted_current.txt b/ui/ui-saved-instance-state/api/restricted_current.txt
index bcbdb06..7e0a56e 100644
--- a/ui/ui-saved-instance-state/api/restricted_current.txt
+++ b/ui/ui-saved-instance-state/api/restricted_current.txt
@@ -34,13 +34,13 @@
   public interface UiSavedStateRegistry {
     method public boolean canBeSaved(Object value);
     method public Object? consumeRestored(String key);
-    method public java.util.Map<java.lang.String,java.lang.Object> performSave();
+    method public java.util.Map<java.lang.String,java.util.List<java.lang.Object>> performSave();
     method public void registerProvider(String key, kotlin.jvm.functions.Function0<?> valueProvider);
-    method public void unregisterProvider(String key);
+    method public void unregisterProvider(String key, kotlin.jvm.functions.Function0<?> valueProvider);
   }
 
   public final class UiSavedStateRegistryKt {
-    method public static androidx.compose.runtime.savedinstancestate.UiSavedStateRegistry UiSavedStateRegistry(java.util.Map<java.lang.String,?>? restoredValues, kotlin.jvm.functions.Function1<java.lang.Object,java.lang.Boolean> canBeSaved);
+    method public static androidx.compose.runtime.savedinstancestate.UiSavedStateRegistry UiSavedStateRegistry(java.util.Map<java.lang.String,? extends java.util.List<?>>? restoredValues, kotlin.jvm.functions.Function1<java.lang.Object,java.lang.Boolean> canBeSaved);
     method public static androidx.compose.runtime.ProvidableAmbient<androidx.compose.runtime.savedinstancestate.UiSavedStateRegistry> getUiSavedStateRegistryAmbient();
   }
 
diff --git a/ui/ui-saved-instance-state/src/androidAndroidTest/kotlin/androidx/compose/runtime/savedinstancestate/RememberSavedInstanceStateTest.kt b/ui/ui-saved-instance-state/src/androidAndroidTest/kotlin/androidx/compose/runtime/savedinstancestate/RememberSavedInstanceStateTest.kt
index 77a8166..564c693 100644
--- a/ui/ui-saved-instance-state/src/androidAndroidTest/kotlin/androidx/compose/runtime/savedinstancestate/RememberSavedInstanceStateTest.kt
+++ b/ui/ui-saved-instance-state/src/androidAndroidTest/kotlin/androidx/compose/runtime/savedinstancestate/RememberSavedInstanceStateTest.kt
@@ -166,9 +166,9 @@
         var registryFactory by mutableStateOf<(UiSavedStateRegistry) -> UiSavedStateRegistry>(
             value = {
                 object : DelegateRegistry(it) {
-                    override fun unregisterProvider(key: String) {
+                    override fun unregisterProvider(key: String, valueProvider: () -> Any?) {
                         unregisterCalledForKey = key
-                        super.unregisterProvider(key)
+                        super.unregisterProvider(key, valueProvider)
                     }
                 }
             }
@@ -219,8 +219,8 @@
                             registerLatch.countDown()
                         }
 
-                        override fun unregisterProvider(key: String) {
-                            super.unregisterProvider(key)
+                        override fun unregisterProvider(key: String, valueProvider: () -> Any?) {
+                            super.unregisterProvider(key, valueProvider)
                             registeredKeys.remove(key)
                         }
                     }
@@ -277,9 +277,9 @@
             WrapRegistry(
                 wrap = {
                     object : DelegateRegistry(it) {
-                        override fun unregisterProvider(key: String) {
+                        override fun unregisterProvider(key: String, valueProvider: () -> Any?) {
                             latch.countDown()
-                            super.unregisterProvider(key)
+                            super.unregisterProvider(key, valueProvider)
                         }
                     }
                 }
diff --git a/ui/ui-saved-instance-state/src/androidAndroidTest/kotlin/androidx/compose/runtime/savedinstancestate/RestorationInVariousScenariosTest.kt b/ui/ui-saved-instance-state/src/androidAndroidTest/kotlin/androidx/compose/runtime/savedinstancestate/RestorationInVariousScenariosTest.kt
new file mode 100644
index 0000000..d17888f
--- /dev/null
+++ b/ui/ui-saved-instance-state/src/androidAndroidTest/kotlin/androidx/compose/runtime/savedinstancestate/RestorationInVariousScenariosTest.kt
@@ -0,0 +1,283 @@
+/*
+ * 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.runtime.savedinstancestate
+
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.MutableState
+import androidx.compose.runtime.key
+import androidx.test.filters.MediumTest
+import androidx.ui.test.StateRestorationTester
+import androidx.ui.test.createComposeRule
+import androidx.ui.test.runOnIdle
+import androidx.ui.test.runOnUiThread
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@MediumTest
+@RunWith(JUnit4::class)
+class RestorationInVariousScenariosTest {
+
+    @get:Rule
+    val composeTestRule = createComposeRule()
+
+    private val restorationTester = StateRestorationTester(composeTestRule)
+
+    @Test
+    fun insideForLoop() {
+        val states = arrayOfNulls<MutableState<Int>>(2)
+        restorationTester.setContent {
+            for (i in 0..1) {
+                states[i] = savedInstanceState { 0 }
+            }
+        }
+
+        runOnUiThread {
+            assertThat(states[0]!!.value).isEqualTo(0)
+            assertThat(states[1]!!.value).isEqualTo(0)
+
+            states[0]!!.value = 1
+            states[1]!!.value = 2
+
+            // we null it to ensure recomposition happened
+            states[0] = null
+            states[1] = null
+        }
+
+        restorationTester.emulateSavedInstanceStateRestore()
+
+        runOnUiThread {
+            assertThat(states[0]!!.value).isEqualTo(1)
+            assertThat(states[1]!!.value).isEqualTo(2)
+        }
+    }
+
+    @Test
+    fun insideForLoop_withKey() {
+        val states = arrayOfNulls<MutableState<Int>>(2)
+        restorationTester.setContent {
+            for (i in 0..1) {
+                key(i) {
+                    states[i] = savedInstanceState { 0 }
+                }
+            }
+        }
+
+        runOnUiThread {
+            assertThat(states[0]!!.value).isEqualTo(0)
+            assertThat(states[1]!!.value).isEqualTo(0)
+
+            states[0]!!.value = 1
+            states[1]!!.value = 2
+
+            // we null it to ensure recomposition happened
+            states[0] = null
+            states[1] = null
+        }
+
+        restorationTester.emulateSavedInstanceStateRestore()
+
+        runOnUiThread {
+            assertThat(states[0]!!.value).isEqualTo(1)
+            assertThat(states[1]!!.value).isEqualTo(2)
+        }
+    }
+
+    @Test
+    fun insideForLoop_withExtraFunction() {
+        val states = arrayOfNulls<MutableState<Int>>(2)
+        restorationTester.setContent {
+            for (i in 0..1) {
+                FunctionWithState(states, i)
+            }
+        }
+
+        runOnUiThread {
+            assertThat(states[0]!!.value).isEqualTo(0)
+            assertThat(states[1]!!.value).isEqualTo(0)
+
+            states[0]!!.value = 1
+            states[1]!!.value = 2
+
+            // we null it to ensure recomposition happened
+            states[0] = null
+            states[1] = null
+        }
+
+        restorationTester.emulateSavedInstanceStateRestore()
+
+        runOnUiThread {
+            assertThat(states[0]!!.value).isEqualTo(1)
+            assertThat(states[1]!!.value).isEqualTo(2)
+        }
+    }
+
+    @Test
+    fun changingLoopCountWithExtraStateAfter() {
+        var number = 2
+        val statesInLoop = arrayOfNulls<MutableState<Int>?>(2)
+        var stateOutside: MutableState<String>? = null
+        restorationTester.setContent {
+            repeat(number) {
+                statesInLoop[it] = savedInstanceState { 0 }
+            }
+            stateOutside = savedInstanceState { "0" }
+        }
+
+        runOnIdle {
+            statesInLoop[0]!!.value = 1
+            statesInLoop[0] = null
+            stateOutside!!.value = "1"
+            stateOutside = null
+            number = 1
+        }
+
+        restorationTester.emulateSavedInstanceStateRestore()
+
+        runOnIdle {
+            assertThat(statesInLoop[0]?.value).isEqualTo(1)
+            assertThat(stateOutside?.value).isEqualTo("1")
+        }
+    }
+
+    @Test
+    fun twoStates() {
+        val states = arrayOfNulls<MutableState<Int>>(2)
+        restorationTester.setContent {
+            states[0] = savedInstanceState { 0 }
+            states[1] = savedInstanceState { 0 }
+        }
+
+        runOnUiThread {
+            assertThat(states[0]!!.value).isEqualTo(0)
+            assertThat(states[1]!!.value).isEqualTo(0)
+
+            states[0]!!.value = 1
+            states[1]!!.value = 2
+
+            // we null it to ensure recomposition happened
+            states[0] = null
+            states[1] = null
+        }
+
+        restorationTester.emulateSavedInstanceStateRestore()
+
+        runOnUiThread {
+            assertThat(states[0]!!.value).isEqualTo(1)
+            assertThat(states[1]!!.value).isEqualTo(2)
+        }
+    }
+
+    @Test
+    fun twoStates_firstStateIsConditional() {
+        var needFirst = true
+        val states = arrayOfNulls<MutableState<Int>>(2)
+        restorationTester.setContent {
+            if (needFirst) {
+                states[0] = savedInstanceState { 0 }
+            }
+            states[1] = savedInstanceState { 0 }
+        }
+
+        runOnUiThread {
+            assertThat(states[0]!!.value).isEqualTo(0)
+            assertThat(states[1]!!.value).isEqualTo(0)
+
+            states[1]!!.value = 1
+
+            // we null it to ensure recomposition happened
+            states[0] = null
+            states[1] = null
+
+            needFirst = false
+        }
+
+        restorationTester.emulateSavedInstanceStateRestore()
+
+        runOnUiThread {
+            assertThat(states[0]).isNull()
+            assertThat(states[1]!!.value).isEqualTo(1)
+        }
+    }
+
+    @Test
+    fun twoStates_withExtraFunction() {
+        val states = arrayOfNulls<MutableState<Int>>(2)
+        restorationTester.setContent {
+            FunctionWithState(states, 0)
+            FunctionWithState(states, 1)
+        }
+
+        runOnUiThread {
+            assertThat(states[0]!!.value).isEqualTo(0)
+            assertThat(states[1]!!.value).isEqualTo(0)
+
+            states[0]!!.value = 1
+            states[1]!!.value = 2
+
+            // we null it to ensure recomposition happened
+            states[0] = null
+            states[1] = null
+        }
+
+        restorationTester.emulateSavedInstanceStateRestore()
+
+        runOnUiThread {
+            assertThat(states[0]!!.value).isEqualTo(1)
+            assertThat(states[1]!!.value).isEqualTo(2)
+        }
+    }
+
+    @Test
+    fun twoStates_withExtraFunction_firstStateIsConditional() {
+        var needFirst = true
+        val states = arrayOfNulls<MutableState<Int>>(2)
+        restorationTester.setContent {
+            if (needFirst) {
+                FunctionWithState(states, 0)
+            }
+            FunctionWithState(states, 1)
+        }
+
+        runOnUiThread {
+            assertThat(states[0]!!.value).isEqualTo(0)
+            assertThat(states[1]!!.value).isEqualTo(0)
+
+            states[1]!!.value = 1
+
+            // we null it to ensure recomposition happened
+            states[0] = null
+            states[1] = null
+
+            needFirst = false
+        }
+
+        restorationTester.emulateSavedInstanceStateRestore()
+
+        runOnUiThread {
+            assertThat(states[0]).isNull()
+            assertThat(states[1]!!.value).isEqualTo(1)
+        }
+    }
+
+    @Composable
+    fun FunctionWithState(states: Array<MutableState<Int>?>, index: Int) {
+        states[index] = savedInstanceState { 0 }
+    }
+}
diff --git a/ui/ui-saved-instance-state/src/commonMain/kotlin/androidx/compose/runtime/savedinstancestate/RememberSavedInstanceState.kt b/ui/ui-saved-instance-state/src/commonMain/kotlin/androidx/compose/runtime/savedinstancestate/RememberSavedInstanceState.kt
index f0dae9b..0cb1e05 100644
--- a/ui/ui-saved-instance-state/src/commonMain/kotlin/androidx/compose/runtime/savedinstancestate/RememberSavedInstanceState.kt
+++ b/ui/ui-saved-instance-state/src/commonMain/kotlin/androidx/compose/runtime/savedinstancestate/RememberSavedInstanceState.kt
@@ -17,10 +17,11 @@
 package androidx.compose.runtime.savedinstancestate
 
 import androidx.compose.runtime.Composable
-import androidx.compose.runtime.CompositionLifecycleObserver
 import androidx.compose.runtime.ExperimentalComposeApi
 import androidx.compose.runtime.MutableState
 import androidx.compose.runtime.currentComposer
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.onPreCommit
 import androidx.compose.runtime.remember
 
 /**
@@ -58,66 +59,63 @@
     key: String? = null,
     init: () -> T
 ): T {
+    // key is the one provided by the user or the one generated by the compose runtime
     val finalKey = if (!key.isNullOrEmpty()) {
         key
     } else {
         currentComposer.currentCompoundKeyHash.toString()
     }
+    @Suppress("UNCHECKED_CAST")
+    (saver as Saver<T, Any>)
+
     val registry = UiSavedStateRegistryAmbient.current
-    val valueProvider = remember(*inputs) { ValueProvider<T>() }
-    return valueProvider.updateAndReturnValue(registry, saver, finalKey, init)
+    // value is restored using the registry or created via [init] lambda
+    val value = remember(*inputs) {
+        // TODO not restore when the input values changed (use hashKeys?) b/152014032
+        val restored = registry?.consumeRestored(finalKey)?.let {
+            saver.restore(it)
+        }
+        restored ?: init()
+    }
+
+    // save the latest passed saver object into a state object to be able to use it when we will
+    // be saving the value. keeping value in mutableStateOf() allows us to properly handle
+    // possible compose transactions cancellations
+    val saverHolder = remember { mutableStateOf(saver) }
+    saverHolder.value = saver
+
+    // re-register if the registry or key has been changed
+    onPreCommit(registry, finalKey) {
+        if (registry != null) {
+            val valueProvider = {
+                with(saverHolder.value) { SaverScopeImpl(registry::canBeSaved).save(value) }
+            }
+            registry.requireCanBeSaved(valueProvider())
+            registry.registerProvider(finalKey, valueProvider)
+            onDispose {
+                registry.unregisterProvider(finalKey, valueProvider)
+            }
+        }
+    }
+    return value
 }
 
-private class ValueProvider<T : Any> : SaverScope, CompositionLifecycleObserver {
-
-    private var registry: UiSavedStateRegistry? = null
-    private var saver: Saver<T, out Any>? = null
-    private var key: String? = null
-    private var value: T? = null
-
-    @Suppress("UNCHECKED_CAST")
-    private fun saver(): Saver<T, Any> = (saver as Saver<T, Any>)
-
-    fun updateAndReturnValue(
-        registry: UiSavedStateRegistry?,
-        saver: Saver<T, out Any>,
-        key: String,
-        init: () -> T
-    ): T {
-        val oldRegistry = this.registry
-        val oldKey = this.key
-        this.saver = saver
-        this.registry = registry
-        this.key = key
-        val value = value ?: run {
-            // TODO not restore when the input values changed (use hashKeys?) b/152014032
-            val restored = registry?.consumeRestored(key)?.let {
-                saver().restore(it)
+private fun UiSavedStateRegistry.requireCanBeSaved(value: Any?) {
+    if (value != null && !canBeSaved(value)) {
+        throw IllegalArgumentException(
+            if (value is MutableState<*>) {
+                "Please use savedInstanceState() if you want to save a MutableState"
+            } else {
+                "$value cannot be saved using the current UiSavedStateRegistry. The default " +
+                        "implementation only supports types which can be stored inside the Bundle" +
+                        ". Please consider implementing a custom Saver for this class and pass it" +
+                        " to savedInstanceState() or rememberSavedInstanceState()."
             }
-            val result = restored ?: init()
-            this.value = result
-            result
-        }
-        if (oldRegistry !== registry || oldKey != key) {
-            oldRegistry?.unregisterProvider(oldKey!!)
-            registry?.registerProvider(key) {
-                val result = with(saver()) { save(value) }
-                if (result != null) {
-                    check(registry.canBeSaved(result))
-                }
-                result
-            }
-        }
-        return value
+        )
     }
+}
 
-    override fun canBeSaved(value: Any) = registry!!.canBeSaved(value)
-
-    override fun onLeave() {
-        registry?.unregisterProvider(key!!)
-    }
-
-    override fun onEnter() {
-        // no-op
-    }
+// TODO this will not be needed when we make SaverScope "fun interface"
+private class SaverScopeImpl(val canBeSaved: (Any) -> Boolean) : SaverScope {
+    override fun canBeSaved(value: Any) = canBeSaved.invoke(value)
 }
diff --git a/ui/ui-saved-instance-state/src/commonMain/kotlin/androidx/compose/runtime/savedinstancestate/Saver.kt b/ui/ui-saved-instance-state/src/commonMain/kotlin/androidx/compose/runtime/savedinstancestate/Saver.kt
index 1736596..8e5c4fa 100644
--- a/ui/ui-saved-instance-state/src/commonMain/kotlin/androidx/compose/runtime/savedinstancestate/Saver.kt
+++ b/ui/ui-saved-instance-state/src/commonMain/kotlin/androidx/compose/runtime/savedinstancestate/Saver.kt
@@ -85,8 +85,7 @@
 }
 
 /**
- * The default implementation of [Saver] which does not perform any conversion, but asserts that
- * the passed value can be saved.
+ * The default implementation of [Saver] which does not perform any conversion.
  *
  * It is used by [savedInstanceState] and [rememberSavedInstanceState] by default.
  *
@@ -97,16 +96,6 @@
     (AutoSaver as Saver<T, Any>)
 
 private val AutoSaver = Saver<Any?, Any>(
-    save = {
-        if (it != null) {
-            require(canBeSaved(it)) {
-                "$it cannot be saved using the current UiSavedInstanceStateRegistry. The default" +
-                        " implementation only supports types which can be stored inside the " +
-                        "Bundle. Please consider implementing a custom Saver for this class " +
-                        "and pass it to savedInstanceState() or rememberSavedInstanceState()."
-            }
-        }
-        it
-    },
+    save = { it },
     restore = { it }
 )
diff --git a/ui/ui-saved-instance-state/src/commonMain/kotlin/androidx/compose/runtime/savedinstancestate/UiSavedStateRegistry.kt b/ui/ui-saved-instance-state/src/commonMain/kotlin/androidx/compose/runtime/savedinstancestate/UiSavedStateRegistry.kt
index dff81a3..4dc2bcc 100644
--- a/ui/ui-saved-instance-state/src/commonMain/kotlin/androidx/compose/runtime/savedinstancestate/UiSavedStateRegistry.kt
+++ b/ui/ui-saved-instance-state/src/commonMain/kotlin/androidx/compose/runtime/savedinstancestate/UiSavedStateRegistry.kt
@@ -33,8 +33,13 @@
     /**
      * Registers the value provider.
      *
-     * The same [key] cannot be registered twice, if you need to update the provider call
-     * [unregisterProvider] first and then register again.
+     * There are could be multiple providers registered for the same [key]. In this case the
+     * order in which they were registered matters.
+     *
+     * Say we registered two providers for the key. One provides "1", second provides "2".
+     * [performSave] in this case will have listOf("1", "2) as a value for the key in the map.
+     * And later, when the registry will be recreated with the previously saved values, the first
+     * execution of [consumeRestored] would consume "1" and the second one "2".
      *
      * @param key Key to use for storing the value
      * @param valueProvider Provides the current value, to be executed when [performSave]
@@ -46,8 +51,9 @@
      * Unregisters the value provider previously registered via [registerProvider].
      *
      * @param key Key of the value which shouldn't be saved anymore
+     * @param valueProvider The provider previously passed to [registerProvider]
      */
-    fun unregisterProvider(key: String)
+    fun unregisterProvider(key: String, valueProvider: () -> Any?)
 
     /**
      * Returns true if the value can be saved using this Registry.
@@ -58,9 +64,10 @@
     fun canBeSaved(value: Any): Boolean
 
     /**
-     * Executes all the registered value providers and combines these values into a key-value map.
+     * Executes all the registered value providers and combines these values into a map. We have
+     * a list of values for each key as it is allowed to have multiple providers for the same key.
      */
-    fun performSave(): Map<String, Any>
+    fun performSave(): Map<String, List<Any?>>
 }
 
 /**
@@ -70,7 +77,7 @@
  * @param canBeSaved Function which returns true if the given value can be saved by the registry
  */
 fun UiSavedStateRegistry(
-    restoredValues: Map<String, Any>?,
+    restoredValues: Map<String, List<Any?>>?,
     canBeSaved: (Any) -> Boolean
 ): UiSavedStateRegistry = UiSavedStateRegistryImpl(restoredValues, canBeSaved)
 
@@ -80,44 +87,70 @@
 val UiSavedStateRegistryAmbient = staticAmbientOf<UiSavedStateRegistry?> { null }
 
 private class UiSavedStateRegistryImpl(
-    restored: Map<String, Any>?,
+    restored: Map<String, List<Any?>>?,
     private val canBeSaved: (Any) -> Boolean
 ) : UiSavedStateRegistry {
 
-    private val restored: MutableMap<String, Any> = restored?.toMutableMap() ?: mutableMapOf()
-    private val valueProviders = mutableMapOf<String, () -> Any?>()
+    private val restored: MutableMap<String, List<Any?>> =
+        restored?.toMutableMap() ?: mutableMapOf()
+    private val valueProviders = mutableMapOf<String, MutableList<() -> Any?>>()
 
     override fun canBeSaved(value: Any): Boolean = canBeSaved.invoke(value)
 
-    override fun consumeRestored(key: String): Any? = restored.remove(key)
+    override fun consumeRestored(key: String): Any? {
+        val list = restored.remove(key)
+        return if (list != null && list.isNotEmpty()) {
+            if (list.size > 1) {
+                restored[key] = list.subList(1, list.size)
+            }
+            list[0]
+        } else {
+            null
+        }
+    }
 
     override fun registerProvider(key: String, valueProvider: () -> Any?) {
         require(key.isNotBlank()) { "Registered key is empty or blank" }
-        require(!valueProviders.contains(key)) {
-            "Key $key was already registered. Please call " +
-                    "unregister before registering again"
-        }
         @Suppress("UNCHECKED_CAST")
-        valueProviders[key] = valueProvider
+        valueProviders.getOrPut(key) { mutableListOf() }.add(valueProvider)
     }
 
-    override fun unregisterProvider(key: String) {
-        require(valueProviders.contains(key)) {
-            "Key $key wasn't registered, but unregister " +
-                    "requested"
+    override fun unregisterProvider(key: String, valueProvider: () -> Any?) {
+        val list = valueProviders.remove(key)
+        val found = list?.remove(valueProvider)
+        require(found == true) {
+            "The given key $key , valueProvider pair wasn't previously registered"
         }
-        valueProviders.remove(key)
+        if (list.isNotEmpty()) {
+            // if there are other providers for this key return list back to the map
+            valueProviders[key] = list
+        }
     }
 
-    override fun performSave(): Map<String, Any> {
+    override fun performSave(): Map<String, List<Any?>> {
         val map = restored.toMutableMap()
-        valueProviders.forEach {
-            val value = it.value()
-            if (value != null) {
-                check(canBeSaved(value))
-                map[it.key] = value
+        valueProviders.forEach { (key, list) ->
+            if (list.size == 1) {
+                val value = list[0].invoke()
+                if (value != null) {
+                    check(canBeSaved(value))
+                    map[key] = arrayListOf<Any?>(value)
+                }
+            } else {
+                // if we have multiple providers we should store null values as well to preserve
+                // the order in which providers were registered. say there were two providers.
+                // the first provider returned null(nothing to save) and the second one returned
+                // "1". when we will be restoring the first provider would restore null (it is the
+                // same as to have nothing to restore) and the second one restore "1".
+                map[key] = list.map {
+                    val value = it.invoke()
+                    if (value != null) {
+                        check(canBeSaved(value))
+                    }
+                    value
+                }
             }
         }
         return map
     }
-}
\ No newline at end of file
+}
diff --git a/ui/ui-saved-instance-state/src/test/java/androidx/compose/runtime/savedinstancestate/AutoSaverTest.kt b/ui/ui-saved-instance-state/src/test/java/androidx/compose/runtime/savedinstancestate/AutoSaverTest.kt
index a464f9d..af4f817 100644
--- a/ui/ui-saved-instance-state/src/test/java/androidx/compose/runtime/savedinstancestate/AutoSaverTest.kt
+++ b/ui/ui-saved-instance-state/src/test/java/androidx/compose/runtime/savedinstancestate/AutoSaverTest.kt
@@ -35,15 +35,6 @@
                 .isEqualTo(2)
         }
     }
-
-    @Test(expected = IllegalArgumentException::class)
-    fun exceptionWhenCantBeSaved() {
-        val saver = autoSaver<Int>()
-
-        with(saver) {
-            disallowingScope.save(2)
-        }
-    }
 }
 
 val allowingScope = object : SaverScope {
diff --git a/ui/ui-saved-instance-state/src/test/java/androidx/compose/runtime/savedinstancestate/UiSavedStateRegistryTest.kt b/ui/ui-saved-instance-state/src/test/java/androidx/compose/runtime/savedinstancestate/UiSavedStateRegistryTest.kt
index f92cd0d..d432171 100644
--- a/ui/ui-saved-instance-state/src/test/java/androidx/compose/runtime/savedinstancestate/UiSavedStateRegistryTest.kt
+++ b/ui/ui-saved-instance-state/src/test/java/androidx/compose/runtime/savedinstancestate/UiSavedStateRegistryTest.kt
@@ -33,7 +33,7 @@
         registry.registerProvider("key") { 10 }
 
         registry.performSave().apply {
-            assertThat(get("key")).isEqualTo(10)
+            assertThat(get("key")).isEqualTo(listOf(10))
         }
     }
 
@@ -41,8 +41,9 @@
     fun unregisteredValuesAreNotSaved() {
         val registry = createRegistry()
 
-        registry.registerProvider("key") { 10 }
-        registry.unregisterProvider("key")
+        val provider = { 10 }
+        registry.registerProvider("key", provider)
+        registry.unregisterProvider("key", provider)
 
         registry.performSave().apply {
             assertThat(containsKey("key")).isFalse()
@@ -53,12 +54,13 @@
     fun registerAgainAfterUnregister() {
         val registry = createRegistry()
 
-        registry.registerProvider("key") { "value1" }
-        registry.unregisterProvider("key")
+        val provider1 = { "value1" }
+        registry.registerProvider("key", provider1)
+        registry.unregisterProvider("key", provider1)
         registry.registerProvider("key") { "value2" }
 
         registry.performSave().apply {
-            assertThat(get("key")).isEqualTo("value2")
+            assertThat(get("key")).isEqualTo(listOf("value2"))
         }
     }
 
@@ -67,27 +69,20 @@
         val registry = createRegistry()
 
         registry.registerProvider("key1") { 100L }
-        registry.registerProvider("key2") { 100L }
+        val provider2 = { 100 }
+        registry.registerProvider("key2", provider2)
         registry.registerProvider("key3") { "value" }
         registry.registerProvider("key4") { listOf("item") }
-        registry.unregisterProvider("key2")
+        registry.unregisterProvider("key2", provider2)
 
         registry.performSave().apply {
-            assertThat(get("key1")).isEqualTo(100L)
+            assertThat(get("key1")).isEqualTo(listOf(100L))
             assertThat(containsKey("key2")).isFalse()
-            assertThat(get("key3")).isEqualTo("value")
-            assertThat(get("key4")).isEqualTo(listOf("item"))
+            assertThat(get("key3")).isEqualTo(listOf("value"))
+            assertThat(get("key4")).isEqualTo(listOf(listOf("item")))
         }
     }
 
-    @Test(expected = IllegalArgumentException::class)
-    fun registeringTheSameKeysTwiceIsNotAllowed() {
-        val registry = createRegistry()
-
-        registry.registerProvider("key") { 100L }
-        registry.registerProvider("key") { 100L }
-    }
-
     @Test
     fun nullValuesAreNotSaved() {
         val registry = createRegistry()
@@ -115,7 +110,7 @@
 
     @Test
     fun restoreSimpleValues() {
-        val restored = mapOf("key1" to "value", "key2" to 2f)
+        val restored = mapOf("key1" to listOf("value"), "key2" to listOf(2f))
         val registry = createRegistry(restored)
 
         assertThat(registry.consumeRestored("key1")).isEqualTo("value")
@@ -124,7 +119,7 @@
 
     @Test
     fun restoreClearsTheStoredValue() {
-        val restored = mapOf("key" to "value")
+        val restored = mapOf("key" to listOf("value"))
         val registry = createRegistry(restored)
 
         assertThat(registry.consumeRestored("key")).isEqualTo("value")
@@ -133,14 +128,14 @@
 
     @Test
     fun unusedRestoredValueSavedAgain() {
-        val restored = mapOf("key1" to "value")
+        val restored = mapOf("key1" to listOf("value"))
         val registry = createRegistry(restored)
 
         registry.registerProvider("key2") { 1 }
 
         registry.performSave().apply {
-            assertThat(get("key1")).isEqualTo("value")
-            assertThat(get("key2")).isEqualTo(1)
+            assertThat(get("key1")).isEqualTo(listOf("value"))
+            assertThat(get("key2")).isEqualTo(listOf(1))
         }
     }
 
@@ -169,8 +164,36 @@
         registry.performSave()
     }
 
+    @Test
+    fun registeringTheSameKeysTwice() {
+        val registry = createRegistry()
+
+        registry.registerProvider("key") { 100L }
+        registry.registerProvider("key") { 200L }
+
+        val restoredRegistry = createRegistry(registry.performSave())
+        assertThat(restoredRegistry.consumeRestored("key")).isEqualTo(100L)
+        assertThat(restoredRegistry.consumeRestored("key")).isEqualTo(200L)
+        assertThat(restoredRegistry.consumeRestored("key")).isNull()
+    }
+
+    @Test
+    fun registeringAndUnregisteringTheSameKeys() {
+        val registry = createRegistry()
+
+        registry.registerProvider("key") { 1L }
+        val provider2 = { 2 }
+        registry.registerProvider("key", provider2)
+        registry.registerProvider("key") { 3 }
+        registry.unregisterProvider("key", provider2)
+
+        val restoredRegistry = createRegistry(registry.performSave())
+        assertThat(restoredRegistry.consumeRestored("key")).isEqualTo(1L)
+        assertThat(restoredRegistry.consumeRestored("key")).isEqualTo(3L)
+    }
+
     private fun createRegistry(
-        restored: Map<String, Any>? = null,
+        restored: Map<String, List<Any?>>? = null,
         canBeSaved: (Any) -> Boolean = { true }
     ) = UiSavedStateRegistry(restored, canBeSaved)
 }
\ No newline at end of file
diff --git a/ui/ui-test/src/androidMain/kotlin/androidx/ui/test/StateRestorationTester.kt b/ui/ui-test/src/androidMain/kotlin/androidx/ui/test/StateRestorationTester.kt
index 6fcbab8..adccbde 100644
--- a/ui/ui-test/src/androidMain/kotlin/androidx/ui/test/StateRestorationTester.kt
+++ b/ui/ui-test/src/androidMain/kotlin/androidx/ui/test/StateRestorationTester.kt
@@ -98,7 +98,7 @@
         var shouldEmitChildren by mutableStateOf(true)
             private set
         private var currentRegistry: UiSavedStateRegistry = original
-        private var savedMap: Map<String, Any> = emptyMap()
+        private var savedMap: Map<String, List<Any?>> = emptyMap()
 
         fun saveStateAndDisposeChildren() {
             savedMap = currentRegistry.performSave()
@@ -118,7 +118,8 @@
         override fun registerProvider(key: String, valueProvider: () -> Any?) =
             currentRegistry.registerProvider(key, valueProvider)
 
-        override fun unregisterProvider(key: String) = currentRegistry.unregisterProvider(key)
+        override fun unregisterProvider(key: String, valueProvider: () -> Any?) =
+            currentRegistry.unregisterProvider(key, valueProvider)
 
         override fun canBeSaved(value: Any) = currentRegistry.canBeSaved(value)
 
diff --git a/ui/ui-tooling/src/androidTest/java/androidx/ui/tooling/ComposeViewAdapterTest.kt b/ui/ui-tooling/src/androidTest/java/androidx/ui/tooling/ComposeViewAdapterTest.kt
index f67cccc..bb77cf2 100644
--- a/ui/ui-tooling/src/androidTest/java/androidx/ui/tooling/ComposeViewAdapterTest.kt
+++ b/ui/ui-tooling/src/androidTest/java/androidx/ui/tooling/ComposeViewAdapterTest.kt
@@ -18,10 +18,15 @@
 
 import android.app.Activity
 import android.os.Bundle
+import androidx.compose.animation.core.InternalAnimationApi
+import androidx.compose.animation.core.TransitionAnimation
 import androidx.ui.tooling.preview.ComposeViewAdapter
 import androidx.ui.tooling.preview.ViewInfo
+import androidx.ui.tooling.preview.animation.PreviewAnimationClock
 import androidx.ui.tooling.test.R
 import org.junit.Assert.assertArrayEquals
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertFalse
 import org.junit.Assert.assertTrue
 import org.junit.Before
 import org.junit.Rule
@@ -74,6 +79,29 @@
         }
     }
 
+    @OptIn(InternalAnimationApi::class)
+    @Test
+    fun transitionAnimationsAreSubscribedToTheClock() {
+        val clock = PreviewAnimationClock()
+
+        activityTestRule.runOnUiThread {
+            composeViewAdapter.init(
+                "androidx.ui.tooling.TestAnimationPreviewKt",
+                "PressStateAnimation"
+            )
+            composeViewAdapter.clock = clock
+            assertTrue(clock.observersToAnimations.isEmpty())
+
+            composeViewAdapter.findAndSubscribeTransitions()
+            assertFalse(clock.observersToAnimations.isEmpty())
+
+            val observer = clock.observersToAnimations.keys.single()
+            val transitionAnimation =
+                (observer as TransitionAnimation<*>.TransitionAnimationClockObserver).animation
+            assertEquals("colorAnim", transitionAnimation.label)
+        }
+    }
+
     @Test
     fun lineNumberMapping() {
         val viewInfos = assertRendersCorrectly(
diff --git a/ui/ui-tooling/src/androidTest/java/androidx/ui/tooling/TestAnimationPreview.kt b/ui/ui-tooling/src/androidTest/java/androidx/ui/tooling/TestAnimationPreview.kt
new file mode 100644
index 0000000..8596e0a
--- /dev/null
+++ b/ui/ui-tooling/src/androidTest/java/androidx/ui/tooling/TestAnimationPreview.kt
@@ -0,0 +1,70 @@
+/*
+ * 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.ui.tooling
+
+import androidx.compose.animation.ColorPropKey
+import androidx.compose.animation.core.spring
+import androidx.compose.animation.core.transitionDefinition
+import androidx.compose.animation.transition
+import androidx.compose.foundation.Canvas
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.gesture.pressIndicatorGestureFilter
+import androidx.compose.ui.graphics.Color
+import androidx.ui.tooling.preview.Preview
+
+private enum class PressState { Pressed, Released }
+
+private val color = ColorPropKey()
+
+private val definition = transitionDefinition<PressState> {
+    state(PressState.Released) {
+        this[color] = Color.Red
+    }
+    state(PressState.Pressed) {
+        this[color] = Color.Blue
+    }
+    transition {
+        color using spring(
+            stiffness = 50f
+        )
+    }
+}
+
+@Preview
+@Composable
+fun PressStateAnimation() {
+    val toState = remember { mutableStateOf(PressState.Released) }
+    val pressIndicator =
+        Modifier.pressIndicatorGestureFilter(
+            onStart = { toState.value = PressState.Pressed },
+            onStop = { toState.value = PressState.Released },
+            onCancel = { toState.value = PressState.Released })
+
+    val state = transition(label = "colorAnim", definition = definition, toState = toState.value)
+    ColorRect(pressIndicator, color = state[color])
+}
+
+@Composable
+private fun ColorRect(modifier: Modifier = Modifier, color: Color) {
+    Canvas(modifier.fillMaxSize()) {
+        drawRect(color)
+    }
+}
\ No newline at end of file
diff --git a/ui/ui-tooling/src/main/java/androidx/ui/tooling/preview/ComposeViewAdapter.kt b/ui/ui-tooling/src/main/java/androidx/ui/tooling/preview/ComposeViewAdapter.kt
index e1ae408..9b52095 100644
--- a/ui/ui-tooling/src/main/java/androidx/ui/tooling/preview/ComposeViewAdapter.kt
+++ b/ui/ui-tooling/src/main/java/androidx/ui/tooling/preview/ComposeViewAdapter.kt
@@ -25,6 +25,8 @@
 import android.util.Log
 import android.widget.FrameLayout
 import androidx.annotation.VisibleForTesting
+import androidx.compose.animation.TransitionModel
+import androidx.compose.animation.core.InternalAnimationApi
 import androidx.compose.runtime.AtomicReference
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.Composition
@@ -120,6 +122,11 @@
     private val slotTableRecord = SlotTableRecord.create()
 
     /**
+     * Simple function name of the Composable being previewed.
+     */
+    private var composableName = ""
+
+    /**
      * Saved exception from the last composition. Since we can not handle the exception during the
      * composition, we save it and throw it during onLayout, this allows Studio to catch it and
      * display it to the user.
@@ -204,6 +211,72 @@
                 walkTable(it)
             }
         }
+
+        if (::clock.isInitialized && composableName.isNotEmpty()) {
+            // TODO(b/160126628): support other APIs, e.g. animate
+            findAndSubscribeTransitions()
+        }
+    }
+
+    /**
+     * Finds all the transition animations defined in the Compose tree where the root is the
+     * `@Composable` being previewed. We only return animations defined in the user code, i.e.
+     * the ones we've got source information for.
+     */
+    @OptIn(InternalAnimationApi::class)
+    @VisibleForTesting
+    internal fun findAndSubscribeTransitions() {
+        val slotTrees = slotTableRecord.store.map { it.asTree() }
+        slotTrees.map { tree -> tree.firstOrNull { it.name == composableName } }
+            .firstOrNull()?.let { composable ->
+                // Find all the AnimationClockObservers corresponding to transition animations
+                val observers = composable.findAll {
+                    // Find `transition` calls in the user code, i.e. when source location is known
+                    it.name == "transition" && it.location != null
+                }.mapNotNull {
+                    val rememberCall =
+                        it.firstOrNull { it.name == "remember" } ?: return@mapNotNull null
+                    val transitionModel = rememberCall.data.firstOrNull { data ->
+                        data is TransitionModel<*>
+                    } as? TransitionModel<*>
+                    transitionModel?.anim?.animationClockObserver
+                }
+                // Subscribe all the observers found to the `PreviewAnimationClock`
+                observers.forEach { clock.subscribe(it) }
+            }
+    }
+
+    private fun Group.firstOrNull(predicate: (Group) -> Boolean): Group? {
+        return findGroupsThatMatchPredicate(this, predicate, true).firstOrNull()
+    }
+
+    private fun Group.findAll(predicate: (Group) -> Boolean): List<Group> {
+        return findGroupsThatMatchPredicate(this, predicate)
+    }
+
+    /**
+     * Search [Group]s that match a given [predicate], starting from a given [root]. An optional
+     * boolean parameter can be set if we're interested in a single occurrence. If it's set, we
+     * return early after finding the first matching [Group].
+     */
+    private fun findGroupsThatMatchPredicate(
+        root: Group,
+        predicate: (Group) -> Boolean,
+        findOnlyFirst: Boolean = false
+    ): List<Group> {
+        val result = mutableListOf<Group>()
+        val stack = mutableListOf(root)
+        while (stack.isNotEmpty()) {
+            val current = stack.removeLast()
+            if (predicate(current)) {
+                if (findOnlyFirst) {
+                    return listOf(current)
+                }
+                result.add(current)
+            }
+            stack.addAll(current.children)
+        }
+        return result
     }
 
     override fun dispatchDraw(canvas: Canvas?) {
@@ -235,7 +308,8 @@
      *
      * @suppress
      */
-    private lateinit var clock: PreviewAnimationClock
+    @VisibleForTesting
+    internal lateinit var clock: PreviewAnimationClock
 
     /**
      * Wraps a given [Preview] method an does any necessary setup.
@@ -279,6 +353,7 @@
         ViewTreeViewModelStoreOwner.set(this, FakeViewModelStoreOwner)
         this.debugPaintBounds = debugPaintBounds
         this.debugViewInfos = debugViewInfos
+        this.composableName = methodName
 
         composition = setContent(Recomposer.current()) {
             WrapPreview {
diff --git a/work/gradle.properties b/work/gradle.properties
new file mode 120000
index 0000000..d952fb0
--- /dev/null
+++ b/work/gradle.properties
@@ -0,0 +1 @@
+../playground-common/androidx-shared.properties
\ No newline at end of file