Merge "Add fake dragging to ViewPager2" into androidx-master-dev
diff --git a/activity/api/1.0.0-alpha06.txt b/activity/api/1.0.0-alpha06.txt
index 1d5064e..c46e0cd 100644
--- a/activity/api/1.0.0-alpha06.txt
+++ b/activity/api/1.0.0-alpha06.txt
@@ -3,20 +3,27 @@
 
   public class ComponentActivity extends androidx.core.app.ComponentActivity implements androidx.lifecycle.LifecycleOwner androidx.savedstate.SavedStateRegistryOwner androidx.lifecycle.ViewModelStoreOwner {
     ctor public ComponentActivity();
-    method public void addOnBackPressedCallback(androidx.activity.OnBackPressedCallback);
-    method public void addOnBackPressedCallback(androidx.lifecycle.LifecycleOwner, androidx.activity.OnBackPressedCallback);
+    method @Deprecated public void addOnBackPressedCallback(androidx.activity.OnBackPressedCallback);
+    method @Deprecated public void addOnBackPressedCallback(androidx.lifecycle.LifecycleOwner, androidx.activity.OnBackPressedCallback);
     method @Deprecated public Object? getLastCustomNonConfigurationInstance();
     method public androidx.lifecycle.Lifecycle getLifecycle();
+    method public final androidx.activity.OnBackPressedDispatcher getOnBackPressedDispatcher();
     method public final androidx.savedstate.SavedStateRegistry getSavedStateRegistry();
     method public androidx.lifecycle.ViewModelStore getViewModelStore();
     method @Deprecated public Object? onRetainCustomNonConfigurationInstance();
     method public final Object? onRetainNonConfigurationInstance();
-    method public void removeOnBackPressedCallback(androidx.activity.OnBackPressedCallback);
+    method @Deprecated public void removeOnBackPressedCallback(androidx.activity.OnBackPressedCallback);
   }
 
   public interface OnBackPressedCallback {
     method public boolean handleOnBackPressed();
   }
 
+  public final class OnBackPressedDispatcher {
+    method public androidx.arch.core.util.Cancellable addCallback(androidx.activity.OnBackPressedCallback);
+    method public androidx.arch.core.util.Cancellable addCallback(androidx.lifecycle.LifecycleOwner, androidx.activity.OnBackPressedCallback);
+    method public boolean onBackPressed();
+  }
+
 }
 
diff --git a/activity/api/current.txt b/activity/api/current.txt
index 1d5064e..c46e0cd 100644
--- a/activity/api/current.txt
+++ b/activity/api/current.txt
@@ -3,20 +3,27 @@
 
   public class ComponentActivity extends androidx.core.app.ComponentActivity implements androidx.lifecycle.LifecycleOwner androidx.savedstate.SavedStateRegistryOwner androidx.lifecycle.ViewModelStoreOwner {
     ctor public ComponentActivity();
-    method public void addOnBackPressedCallback(androidx.activity.OnBackPressedCallback);
-    method public void addOnBackPressedCallback(androidx.lifecycle.LifecycleOwner, androidx.activity.OnBackPressedCallback);
+    method @Deprecated public void addOnBackPressedCallback(androidx.activity.OnBackPressedCallback);
+    method @Deprecated public void addOnBackPressedCallback(androidx.lifecycle.LifecycleOwner, androidx.activity.OnBackPressedCallback);
     method @Deprecated public Object? getLastCustomNonConfigurationInstance();
     method public androidx.lifecycle.Lifecycle getLifecycle();
+    method public final androidx.activity.OnBackPressedDispatcher getOnBackPressedDispatcher();
     method public final androidx.savedstate.SavedStateRegistry getSavedStateRegistry();
     method public androidx.lifecycle.ViewModelStore getViewModelStore();
     method @Deprecated public Object? onRetainCustomNonConfigurationInstance();
     method public final Object? onRetainNonConfigurationInstance();
-    method public void removeOnBackPressedCallback(androidx.activity.OnBackPressedCallback);
+    method @Deprecated public void removeOnBackPressedCallback(androidx.activity.OnBackPressedCallback);
   }
 
   public interface OnBackPressedCallback {
     method public boolean handleOnBackPressed();
   }
 
+  public final class OnBackPressedDispatcher {
+    method public androidx.arch.core.util.Cancellable addCallback(androidx.activity.OnBackPressedCallback);
+    method public androidx.arch.core.util.Cancellable addCallback(androidx.lifecycle.LifecycleOwner, androidx.activity.OnBackPressedCallback);
+    method public boolean onBackPressed();
+  }
+
 }
 
diff --git a/activity/build.gradle b/activity/build.gradle
index f687bc8..96270d9 100644
--- a/activity/build.gradle
+++ b/activity/build.gradle
@@ -15,6 +15,7 @@
 
 dependencies {
     api(project(":annotation"))
+    api(project(":arch:core-common"))
     api(project(":core")) {
         exclude group: 'androidx.annotation'
         exclude group: 'com.google.guava', module: 'listenablefuture'
diff --git a/activity/src/androidTest/AndroidManifest.xml b/activity/src/androidTest/AndroidManifest.xml
index 9eb99d5..4d2f379 100644
--- a/activity/src/androidTest/AndroidManifest.xml
+++ b/activity/src/androidTest/AndroidManifest.xml
@@ -23,7 +23,6 @@
         <activity android:name="androidx.activity.EagerOverrideLifecycleComponentActivity"/>
         <activity android:name="androidx.activity.LazyOverrideLifecycleComponentActivity"/>
         <activity android:name="androidx.activity.ViewModelActivity"/>
-        <activity android:name="androidx.activity.OnBackPressedComponentActivity"/>
         <activity android:name="androidx.activity.SavedStateActivity"/>
         <activity android:name="androidx.activity.ContentViewActivity"/>
         <activity android:name="androidx.activity.AutoRestarterActivity"/>
diff --git a/activity/src/androidTest/java/androidx/activity/ComponentActivityOnBackPressedTest.kt b/activity/src/androidTest/java/androidx/activity/ComponentActivityOnBackPressedTest.kt
deleted file mode 100644
index 396947f..0000000
--- a/activity/src/androidTest/java/androidx/activity/ComponentActivityOnBackPressedTest.kt
+++ /dev/null
@@ -1,217 +0,0 @@
-/*
- * Copyright 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.activity
-
-import androidx.lifecycle.GenericLifecycleObserver
-import androidx.lifecycle.Lifecycle
-import androidx.lifecycle.LifecycleOwner
-import androidx.lifecycle.LifecycleRegistry
-import androidx.test.annotation.UiThreadTest
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.SmallTest
-import androidx.test.rule.ActivityTestRule
-import com.google.common.truth.Truth.assertWithMessage
-import org.junit.Rule
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.Mockito.mock
-import java.util.concurrent.CountDownLatch
-
-@SmallTest
-@RunWith(AndroidJUnit4::class)
-class ComponentActivityOnBackPressedTest {
-
-    @get:Rule
-    val activityRule = ActivityTestRule(OnBackPressedComponentActivity::class.java)
-
-    @UiThreadTest
-    @Test
-    fun testAddOnBackPressedListener() {
-        val activity = activityRule.activity
-
-        val onBackPressedCallback = CountingOnBackPressedCallback()
-
-        activity.addOnBackPressedCallback(onBackPressedCallback)
-        activity.onBackPressed()
-        assertWithMessage("Count should be incremented after handleOnBackPressed")
-            .that(onBackPressedCallback.count)
-            .isEqualTo(1)
-    }
-
-    @UiThreadTest
-    @Test
-    fun testRemoveOnBackPressedListener() {
-        val activity = activityRule.activity
-
-        val onBackPressedCallback = CountingOnBackPressedCallback()
-
-        activity.addOnBackPressedCallback(onBackPressedCallback)
-        activity.onBackPressed()
-        assertWithMessage("Count should be incremented after handleOnBackPressed")
-            .that(onBackPressedCallback.count)
-            .isEqualTo(1)
-
-        activity.removeOnBackPressedCallback(onBackPressedCallback)
-        activity.onBackPressed()
-        // Check that the count still equals 1
-        assertWithMessage("Count shouldn't be incremented after removal")
-            .that(onBackPressedCallback.count)
-            .isEqualTo(1)
-    }
-
-    @UiThreadTest
-    @Test
-    fun testMultipleCalls() {
-        val activity = activityRule.activity
-
-        val onBackPressedCallback = CountingOnBackPressedCallback()
-
-        activity.addOnBackPressedCallback(onBackPressedCallback)
-        activity.onBackPressed()
-        activity.onBackPressed()
-        assertWithMessage("Count should be incremented after each handleOnBackPressed")
-            .that(onBackPressedCallback.count)
-            .isEqualTo(2)
-    }
-
-    @UiThreadTest
-    @Test
-    fun testMostRecentGetsPriority() {
-        val activity = activityRule.activity
-
-        val onBackPressedCallback = CountingOnBackPressedCallback()
-        val mostRecentOnBackPressedCallback = CountingOnBackPressedCallback()
-
-        activity.addOnBackPressedCallback(onBackPressedCallback)
-        activity.addOnBackPressedCallback(mostRecentOnBackPressedCallback)
-        activity.onBackPressed()
-        assertWithMessage("Most recent callback should be incremented")
-            .that(mostRecentOnBackPressedCallback.count)
-            .isEqualTo(1)
-        assertWithMessage("Only the most recent callback should be incremented")
-            .that(onBackPressedCallback.count)
-            .isEqualTo(0)
-    }
-
-    @UiThreadTest
-    @Test
-    fun testPassthroughListener() {
-        val activity = activityRule.activity
-
-        val onBackPressedCallback = CountingOnBackPressedCallback()
-        val passThroughOnBackPressedCallback = CountingOnBackPressedCallback(returnValue = false)
-
-        activity.addOnBackPressedCallback(onBackPressedCallback)
-        activity.addOnBackPressedCallback(passThroughOnBackPressedCallback)
-        activity.onBackPressed()
-        assertWithMessage("Most recent callback should be incremented")
-            .that(passThroughOnBackPressedCallback.count)
-            .isEqualTo(1)
-        assertWithMessage("Previous callbacks should be incremented if more recent callbacks " +
-                "return false")
-            .that(onBackPressedCallback.count)
-            .isEqualTo(1)
-    }
-
-    @UiThreadTest
-    @Test
-    fun testLifecycleCallback() {
-        val activity = activityRule.activity
-
-        val onBackPressedCallback = CountingOnBackPressedCallback()
-        val lifecycleOnBackPressedCallback = CountingOnBackPressedCallback()
-        val lifecycleOwner = object : LifecycleOwner {
-            val lifecycleRegistry = LifecycleRegistry(this)
-
-            override fun getLifecycle() = lifecycleRegistry
-        }
-
-        activity.addOnBackPressedCallback(onBackPressedCallback)
-        activity.addOnBackPressedCallback(lifecycleOwner, lifecycleOnBackPressedCallback)
-        activity.onBackPressed()
-        assertWithMessage("Non-started callbacks shouldn't have their count incremented")
-            .that(lifecycleOnBackPressedCallback.count)
-            .isEqualTo(0)
-        assertWithMessage("Previous callbacks should be incremented if more recent callbacks " +
-                "aren't started")
-            .that(onBackPressedCallback.count)
-            .isEqualTo(1)
-
-        // Now start the Lifecycle
-        lifecycleOwner.lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_START)
-        activity.onBackPressed()
-        assertWithMessage("Once the callbacks is started, the count should increment")
-            .that(lifecycleOnBackPressedCallback.count)
-            .isEqualTo(1)
-        assertWithMessage("Only the most recent callback should be incremented")
-            .that(onBackPressedCallback.count)
-            .isEqualTo(1)
-
-        // Now stop the Lifecycle
-        lifecycleOwner.lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_STOP)
-        activity.onBackPressed()
-        assertWithMessage("Non-started callbacks shouldn't have their count incremented")
-            .that(lifecycleOnBackPressedCallback.count)
-            .isEqualTo(1)
-        assertWithMessage("Previous callbacks should be incremented if more recent callbacks " +
-                "aren't started")
-            .that(onBackPressedCallback.count)
-            .isEqualTo(2)
-
-        // Now destroy the Lifecycle
-        lifecycleOwner.lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_DESTROY)
-        @Suppress("INACCESSIBLE_TYPE")
-        assertWithMessage("onDestroy should trigger the removal of any associated callbacks")
-            .that(activity.mOnBackPressedCallbacks)
-            .hasSize(1)
-        activity.onBackPressed()
-        assertWithMessage("Non-started callbacks shouldn't have their count incremented")
-            .that(lifecycleOnBackPressedCallback.count)
-            .isEqualTo(1)
-        assertWithMessage("Previous callbacks should be incremented if more recent callbacks " +
-                "aren't started")
-            .that(onBackPressedCallback.count)
-            .isEqualTo(3)
-    }
-}
-
-class CountingOnBackPressedCallback(val returnValue: Boolean = true) :
-    OnBackPressedCallback {
-    var count = 0
-
-    override fun handleOnBackPressed(): Boolean {
-        count++
-        return returnValue
-    }
-}
-
-class OnBackPressedComponentActivity : ComponentActivity() {
-    val activityCallbackLifecycleOwner: LifecycleOwner = mock(LifecycleOwner::class.java)
-    val lifecycleObserver: GenericLifecycleObserver = mock(GenericLifecycleObserver::class.java)
-    val destroyCountDownLatch = CountDownLatch(1)
-
-    init {
-        lifecycle.addObserver(lifecycleObserver)
-    }
-
-    override fun onDestroy() {
-        lifecycleObserver.onStateChanged(activityCallbackLifecycleOwner,
-            Lifecycle.Event.ON_DESTROY)
-        super.onDestroy()
-        destroyCountDownLatch.countDown()
-    }
-}
diff --git a/activity/src/androidTest/java/androidx/activity/OnBackPressedDispatcherTest.kt b/activity/src/androidTest/java/androidx/activity/OnBackPressedDispatcherTest.kt
new file mode 100644
index 0000000..ca8fcdc
--- /dev/null
+++ b/activity/src/androidTest/java/androidx/activity/OnBackPressedDispatcherTest.kt
@@ -0,0 +1,247 @@
+/*
+ * 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.activity
+
+import androidx.arch.core.util.Cancellable
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.LifecycleOwner
+import androidx.lifecycle.LifecycleRegistry
+import androidx.test.annotation.UiThreadTest
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.google.common.truth.Truth.assertWithMessage
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class OnBackPressedHandlerTest {
+
+    lateinit var dispatcher: OnBackPressedDispatcher
+
+    @Before
+    fun setup() {
+        dispatcher = OnBackPressedDispatcher()
+    }
+
+    @UiThreadTest
+    @Test
+    fun testAddCallback() {
+        val onBackPressedCallback = CountingOnBackPressedCallback()
+
+        dispatcher.addCallback(onBackPressedCallback)
+        assertWithMessage("Handler should return true when handling onBackPressed")
+            .that(dispatcher.onBackPressed())
+            .isTrue()
+        assertWithMessage("Count should be incremented after onBackPressed")
+            .that(onBackPressedCallback.count)
+            .isEqualTo(1)
+    }
+
+    @UiThreadTest
+    @Test
+    fun testCancelSubscription() {
+        val onBackPressedCallback = CountingOnBackPressedCallback()
+
+        val subscription = dispatcher.addCallback(onBackPressedCallback)
+        assertWithMessage("Handler should return true when handling onBackPressed")
+            .that(dispatcher.onBackPressed())
+            .isTrue()
+        assertWithMessage("Count should be incremented after onBackPressed")
+            .that(onBackPressedCallback.count)
+            .isEqualTo(1)
+
+        subscription.cancel()
+        assertWithMessage("Cancellable should be cancelled after cancel()")
+            .that(subscription.isCancelled)
+            .isTrue()
+        assertWithMessage("Handler should return false when no OnBackPressedCallbacks " +
+                "are registered")
+            .that(dispatcher.onBackPressed())
+            .isFalse()
+        // Check that the count still equals 1
+        assertWithMessage("Count shouldn't be incremented after removal")
+            .that(onBackPressedCallback.count)
+            .isEqualTo(1)
+    }
+
+    @UiThreadTest
+    @Test
+    fun testMultipleCalls() {
+        val onBackPressedCallback = CountingOnBackPressedCallback()
+
+        dispatcher.addCallback(onBackPressedCallback)
+        assertWithMessage("Handler should return true when handling onBackPressed")
+            .that(dispatcher.onBackPressed())
+            .isTrue()
+        assertWithMessage("Handler should return true when handling onBackPressed")
+            .that(dispatcher.onBackPressed())
+            .isTrue()
+        assertWithMessage("Count should be incremented after each onBackPressed")
+            .that(onBackPressedCallback.count)
+            .isEqualTo(2)
+    }
+
+    @UiThreadTest
+    @Test
+    fun testMostRecentGetsPriority() {
+        val onBackPressedCallback = CountingOnBackPressedCallback()
+        val mostRecentOnBackPressedCallback = CountingOnBackPressedCallback()
+
+        dispatcher.addCallback(onBackPressedCallback)
+        dispatcher.addCallback(mostRecentOnBackPressedCallback)
+        assertWithMessage("Handler should return true when handling onBackPressed")
+            .that(dispatcher.onBackPressed())
+            .isTrue()
+        assertWithMessage("Most recent callback should be incremented")
+            .that(mostRecentOnBackPressedCallback.count)
+            .isEqualTo(1)
+        assertWithMessage("Only the most recent callback should be incremented")
+            .that(onBackPressedCallback.count)
+            .isEqualTo(0)
+    }
+
+    @UiThreadTest
+    @Test
+    fun testPassthroughListener() {
+        val onBackPressedCallback = CountingOnBackPressedCallback()
+        val passThroughOnBackPressedCallback = CountingOnBackPressedCallback(returnValue = false)
+
+        dispatcher.addCallback(onBackPressedCallback)
+        dispatcher.addCallback(passThroughOnBackPressedCallback)
+        assertWithMessage("Handler should return true when handling onBackPressed")
+            .that(dispatcher.onBackPressed())
+            .isTrue()
+        assertWithMessage("Most recent callback should be incremented")
+            .that(passThroughOnBackPressedCallback.count)
+            .isEqualTo(1)
+        assertWithMessage("Previous callbacks should be incremented if more recent callbacks " +
+                "return false")
+            .that(onBackPressedCallback.count)
+            .isEqualTo(1)
+    }
+
+    @UiThreadTest
+    @Test
+    fun testLifecycleCallback() {
+        val onBackPressedCallback = CountingOnBackPressedCallback()
+        val lifecycleOnBackPressedCallback = CountingOnBackPressedCallback()
+        val lifecycleOwner = object : LifecycleOwner {
+            val lifecycleRegistry = LifecycleRegistry(this)
+
+            override fun getLifecycle() = lifecycleRegistry
+        }
+
+        dispatcher.addCallback(onBackPressedCallback)
+        val observeSubscription = dispatcher.addCallback(lifecycleOwner,
+            lifecycleOnBackPressedCallback)
+        assertWithMessage("Handler should return true when handling onBackPressed")
+            .that(dispatcher.onBackPressed())
+            .isTrue()
+        assertWithMessage("Non-started callbacks shouldn't have their count incremented")
+            .that(lifecycleOnBackPressedCallback.count)
+            .isEqualTo(0)
+        assertWithMessage("Previous callbacks should be incremented if more recent callbacks " +
+                "aren't started")
+            .that(onBackPressedCallback.count)
+            .isEqualTo(1)
+
+        // Now start the Lifecycle
+        lifecycleOwner.lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_START)
+        assertWithMessage("Handler should return true when handling onBackPressed")
+            .that(dispatcher.onBackPressed())
+            .isTrue()
+        assertWithMessage("Once the callbacks is started, the count should increment")
+            .that(lifecycleOnBackPressedCallback.count)
+            .isEqualTo(1)
+        assertWithMessage("Only the most recent callback should be incremented")
+            .that(onBackPressedCallback.count)
+            .isEqualTo(1)
+
+        // Now stop the Lifecycle
+        lifecycleOwner.lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_STOP)
+        assertWithMessage("Handler should return true when handling onBackPressed")
+            .that(dispatcher.onBackPressed())
+            .isTrue()
+        assertWithMessage("Non-started callbacks shouldn't have their count incremented")
+            .that(lifecycleOnBackPressedCallback.count)
+            .isEqualTo(1)
+        assertWithMessage("Previous callbacks should be incremented if more recent callbacks " +
+                "aren't started")
+            .that(onBackPressedCallback.count)
+            .isEqualTo(2)
+
+        // Now destroy the Lifecycle
+        lifecycleOwner.lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_DESTROY)
+        @Suppress("INACCESSIBLE_TYPE")
+        assertWithMessage("onDestroy should trigger the removal of any associated callbacks")
+            .that(observeSubscription.isCancelled)
+            .isTrue()
+        assertWithMessage("Handler should return true when handling onBackPressed")
+            .that(dispatcher.onBackPressed())
+            .isTrue()
+        assertWithMessage("Non-started callbacks shouldn't have their count incremented")
+            .that(lifecycleOnBackPressedCallback.count)
+            .isEqualTo(1)
+        assertWithMessage("Previous callbacks should be incremented if more recent callbacks " +
+                "aren't started")
+            .that(onBackPressedCallback.count)
+            .isEqualTo(3)
+    }
+
+    @UiThreadTest
+    @Test
+    fun testLifecycleCallback_whenDestroyed() {
+        val lifecycleOnBackPressedCallback = CountingOnBackPressedCallback()
+        val lifecycleOwner = object : LifecycleOwner {
+            val lifecycleRegistry = LifecycleRegistry(this)
+
+            override fun getLifecycle() = lifecycleRegistry
+        }
+        // Start the Lifecycle as DESTROYED
+        lifecycleOwner.lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_DESTROY)
+
+        val subscription = dispatcher.addCallback(lifecycleOwner,
+            lifecycleOnBackPressedCallback)
+        assertWithMessage("Dispatcher should return Cancellable.CANCELLED if the Lifecycle is " +
+                "DESTROYED")
+            .that(subscription)
+            .isSameAs(Cancellable.CANCELLED)
+        assertWithMessage("Cancellable should be immediately cancelled if the Lifecycle is " +
+                "DESTROYED")
+            .that(subscription.isCancelled)
+            .isTrue()
+        assertWithMessage("Handler should return false when no OnBackPressedCallbacks " +
+                "are registered")
+            .that(dispatcher.onBackPressed())
+            .isFalse()
+        assertWithMessage("Count shouldn't be incremented when the Cancellable is cancelled")
+            .that(lifecycleOnBackPressedCallback.count)
+            .isEqualTo(0)
+    }
+}
+
+class CountingOnBackPressedCallback(val returnValue: Boolean = true) :
+    OnBackPressedCallback {
+    var count = 0
+
+    override fun handleOnBackPressed(): Boolean {
+        count++
+        return returnValue
+    }
+}
diff --git a/activity/src/main/java/androidx/activity/ComponentActivity.java b/activity/src/main/java/androidx/activity/ComponentActivity.java
index a3bbef7..1619920 100644
--- a/activity/src/main/java/androidx/activity/ComponentActivity.java
+++ b/activity/src/main/java/androidx/activity/ComponentActivity.java
@@ -27,6 +27,7 @@
 import androidx.annotation.ContentView;
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
+import androidx.arch.core.util.Cancellable;
 import androidx.lifecycle.GenericLifecycleObserver;
 import androidx.lifecycle.Lifecycle;
 import androidx.lifecycle.LifecycleOwner;
@@ -39,8 +40,7 @@
 import androidx.savedstate.SavedStateRegistryOwner;
 
 import java.util.HashMap;
-import java.util.Iterator;
-import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.WeakHashMap;
 
 /**
  * Base class for activities that enables composition of higher level components.
@@ -66,9 +66,12 @@
     // Lazily recreated from NonConfigurationInstances by getViewModelStore()
     private ViewModelStore mViewModelStore;
 
-    @SuppressWarnings("WeakerAccess") /* synthetic access */
-    final CopyOnWriteArrayList<LifecycleAwareOnBackPressedCallback> mOnBackPressedCallbacks =
-            new CopyOnWriteArrayList<>();
+    private final OnBackPressedDispatcher mOnBackPressedDispatcher = new OnBackPressedDispatcher();
+    /**
+     * Used for the deprecated {@link #removeOnBackPressedCallback(OnBackPressedCallback)}.
+     */
+    private final WeakHashMap<OnBackPressedCallback, Cancellable>
+            mOnBackPressedCallbackCancellables = new WeakHashMap<>();
 
     // Cache the ContentView layoutIds for Activities.
     private static final HashMap<Class, Integer> sAnnotationIds = new HashMap<>();
@@ -258,26 +261,33 @@
 
     /**
      * Called when the activity has detected the user's press of the back
-     * key. Any {@link OnBackPressedCallback} added via
-     * {@link #addOnBackPressedCallback(LifecycleOwner, OnBackPressedCallback)} will be given a
+     * key. The {@link #getOnBackPressedDispatcher() OnBackPressedDispatcher} will be given a
      * chance to handle the back button before the default behavior of
      * {@link android.app.Activity#onBackPressed()} is invoked.
      *
-     * @see #addOnBackPressedCallback(LifecycleOwner, OnBackPressedCallback)
+     * @see #getOnBackPressedDispatcher()
      */
     @Override
     public void onBackPressed() {
-        for (OnBackPressedCallback onBackPressedCallback : mOnBackPressedCallbacks) {
-            if (onBackPressedCallback.handleOnBackPressed()) {
-                return;
-            }
+        if (mOnBackPressedDispatcher.onBackPressed()) {
+            return;
         }
-        // If none of the registered OnBackPressedCallbacks handled the back button,
+        // If the OnBackPressedDispatcher doesn't handle the back button,
         // delegate to the super implementation
         super.onBackPressed();
     }
 
     /**
+     * Retrieve the {@link OnBackPressedDispatcher} that will be triggered when
+     * {@link #onBackPressed()} is called.
+     * @return The {@link OnBackPressedDispatcher} associated with this ComponentActivity.
+     */
+    @NonNull
+    public final OnBackPressedDispatcher getOnBackPressedDispatcher() {
+        return mOnBackPressedDispatcher;
+    }
+
+    /**
      * Add a new {@link OnBackPressedCallback}. Callbacks are invoked in order of recency, so
      * this newly added {@link OnBackPressedCallback} will be the first callback to receive a
      * callback if {@link #onBackPressed()} is called. Only if this callback returns
@@ -295,9 +305,15 @@
      *
      * @see #onBackPressed()
      * @see #removeOnBackPressedCallback(OnBackPressedCallback)
+     * @deprecated Use {@link #getOnBackPressedDispatcher() and
+     * {@link OnBackPressedDispatcher#addCallback(LifecycleOwner, OnBackPressedCallback)}},
+     * explicitly passing in this Activity object as the {@link LifecycleOwner}.
      */
+    @Deprecated
     public void addOnBackPressedCallback(@NonNull OnBackPressedCallback onBackPressedCallback) {
-        addOnBackPressedCallback(this, onBackPressedCallback);
+        mOnBackPressedCallbackCancellables.put(onBackPressedCallback,
+                getOnBackPressedDispatcher()
+                        .addCallback(this, onBackPressedCallback));
     }
 
     /**
@@ -319,18 +335,15 @@
      *
      * @see #onBackPressed()
      * @see #removeOnBackPressedCallback(OnBackPressedCallback)
+     * @deprecated Use {@link #getOnBackPressedDispatcher() and
+     * {@link OnBackPressedDispatcher#addCallback(LifecycleOwner, OnBackPressedCallback)}}.
      */
+    @Deprecated
     public void addOnBackPressedCallback(@NonNull LifecycleOwner owner,
             @NonNull OnBackPressedCallback onBackPressedCallback) {
-        Lifecycle lifecycle = owner.getLifecycle();
-        if (lifecycle.getCurrentState() == Lifecycle.State.DESTROYED) {
-            // Already destroyed, nothing to do
-            return;
-        }
-        // Add new callbacks to the front of the list so that
-        // the most recently added callbacks get priority
-        mOnBackPressedCallbacks.add(0, new LifecycleAwareOnBackPressedCallback(
-                lifecycle, onBackPressedCallback));
+        mOnBackPressedCallbackCancellables.put(onBackPressedCallback,
+                getOnBackPressedDispatcher()
+                        .addCallback(owner, onBackPressedCallback));
     }
 
     /**
@@ -345,21 +358,16 @@
      *
      * @param onBackPressedCallback The callback to remove
      * @see #addOnBackPressedCallback(LifecycleOwner, OnBackPressedCallback)
+     * @deprecated Use {@link Cancellable#cancel()} on the
+     * {@link Cancellable} returned by {@link #getOnBackPressedDispatcher() and
+     * {@link OnBackPressedDispatcher#addCallback }}.
      */
+    @SuppressWarnings("DeprecatedIsStillUsed") /* See mOnBackPressedCallbackCancellables */
+    @Deprecated
     public void removeOnBackPressedCallback(@NonNull OnBackPressedCallback onBackPressedCallback) {
-        Iterator<LifecycleAwareOnBackPressedCallback> iterator =
-                mOnBackPressedCallbacks.iterator();
-        LifecycleAwareOnBackPressedCallback callbackToRemove = null;
-        while (iterator.hasNext()) {
-            LifecycleAwareOnBackPressedCallback callback = iterator.next();
-            if (callback.getOnBackPressedCallback().equals(onBackPressedCallback)) {
-                callbackToRemove = callback;
-                break;
-            }
-        }
-        if (callbackToRemove != null) {
-            callbackToRemove.onRemoved();
-            mOnBackPressedCallbacks.remove(callbackToRemove);
+        Cancellable cancellable = mOnBackPressedCallbackCancellables.remove(onBackPressedCallback);
+        if (cancellable != null) {
+            cancellable.cancel();
         }
     }
 
@@ -368,48 +376,4 @@
     public final SavedStateRegistry getSavedStateRegistry() {
         return mSavedStateRegistryController.getSavedStateRegistry();
     }
-
-    private class LifecycleAwareOnBackPressedCallback implements
-            OnBackPressedCallback,
-            GenericLifecycleObserver {
-        private final Lifecycle mLifecycle;
-        private final OnBackPressedCallback mOnBackPressedCallback;
-
-        LifecycleAwareOnBackPressedCallback(@NonNull Lifecycle lifecycle,
-                @NonNull OnBackPressedCallback onBackPressedCallback) {
-            mLifecycle = lifecycle;
-            mOnBackPressedCallback = onBackPressedCallback;
-            mLifecycle.addObserver(this);
-        }
-
-        Lifecycle getLifecycle() {
-            return mLifecycle;
-        }
-
-        OnBackPressedCallback getOnBackPressedCallback() {
-            return mOnBackPressedCallback;
-        }
-
-        @Override
-        public boolean handleOnBackPressed() {
-            if (mLifecycle.getCurrentState().isAtLeast(Lifecycle.State.STARTED)) {
-                return mOnBackPressedCallback.handleOnBackPressed();
-            }
-            return false;
-        }
-
-        @Override
-        public void onStateChanged(LifecycleOwner source, Lifecycle.Event event) {
-            if (event == Lifecycle.Event.ON_DESTROY) {
-                synchronized (mOnBackPressedCallbacks) {
-                    mLifecycle.removeObserver(this);
-                    mOnBackPressedCallbacks.remove(this);
-                }
-            }
-        }
-
-        public void onRemoved() {
-            mLifecycle.removeObserver(this);
-        }
-    }
 }
diff --git a/activity/src/main/java/androidx/activity/OnBackPressedCallback.java b/activity/src/main/java/androidx/activity/OnBackPressedCallback.java
index 3eed965..fff6476 100644
--- a/activity/src/main/java/androidx/activity/OnBackPressedCallback.java
+++ b/activity/src/main/java/androidx/activity/OnBackPressedCallback.java
@@ -16,20 +16,17 @@
 
 package androidx.activity;
 
-import androidx.lifecycle.LifecycleOwner;
-
 /**
- * Interface for handling {@link ComponentActivity#onBackPressed()} callbacks without
+ * Interface for handling {@link OnBackPressedDispatcher#onBackPressed()} callbacks without
  * strongly coupling that implementation to a subclass of {@link ComponentActivity}.
  *
- * @see ComponentActivity#addOnBackPressedCallback(LifecycleOwner, OnBackPressedCallback)
- * @see ComponentActivity#removeOnBackPressedCallback(OnBackPressedCallback)
+ * @see ComponentActivity#getOnBackPressedDispatcher()
  */
 public interface OnBackPressedCallback {
     /**
-     * Callback for handling the {@link ComponentActivity#onBackPressed()} event.
+     * Callback for handling the {@link OnBackPressedDispatcher#onBackPressed()} event.
      *
-     * @return True if you handled the {@link ComponentActivity#onBackPressed()} event. No
+     * @return True if you handled the {@link OnBackPressedDispatcher#onBackPressed()} event. No
      * further {@link OnBackPressedCallback} instances will be called if you return true.
      */
     boolean handleOnBackPressed();
diff --git a/activity/src/main/java/androidx/activity/OnBackPressedDispatcher.java b/activity/src/main/java/androidx/activity/OnBackPressedDispatcher.java
new file mode 100644
index 0000000..b11e8d2
--- /dev/null
+++ b/activity/src/main/java/androidx/activity/OnBackPressedDispatcher.java
@@ -0,0 +1,212 @@
+/*
+ * 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.activity;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.arch.core.util.Cancellable;
+import androidx.lifecycle.GenericLifecycleObserver;
+import androidx.lifecycle.Lifecycle;
+import androidx.lifecycle.LifecycleOwner;
+
+import java.util.ArrayDeque;
+import java.util.Iterator;
+
+/**
+ * Dispatcher that can be used to register {@link OnBackPressedCallback} instances for handling
+ * the {@link ComponentActivity#onBackPressed()} callback via composition.
+ * <pre>
+ * public class FormEntryFragment extends Fragment {
+ *     {@literal @}Override
+ *     public void onAttach({@literal @}NonNull Context context) {
+ *         super.onAttach(context);
+ *         requireActivity().getOnBackPressedDispatcher().addCallback(this,
+ *                 new OnBackPressedCallback() {
+ *                     {@literal @}Override
+ *                     public boolean handleOnBackPressed() {
+ *                         showAreYouSureDialog();
+ *                         return true;
+ *                     }
+ *                 });
+ *     }
+ * }
+ * </pre>
+ */
+public final class OnBackPressedDispatcher {
+
+    @SuppressWarnings("WeakerAccess") /* synthetic access */
+    final ArrayDeque<OnBackPressedCallback> mOnBackPressedCallbacks = new ArrayDeque<>();
+
+    OnBackPressedDispatcher() {
+    }
+
+    /**
+     * Add a new {@link OnBackPressedCallback}. Callbacks are invoked in the reverse order in which
+     * they are added, so this newly added {@link OnBackPressedCallback} will be the first
+     * callback to receive a callback if {@link #onBackPressed()} is called.
+     * <p>
+     * This method is <strong>not</strong> {@link Lifecycle} aware - if you'd like to ensure that
+     * you only get callbacks when at least {@link Lifecycle.State#STARTED started}, use
+     * {@link #addCallback(LifecycleOwner, OnBackPressedCallback)}.
+     *
+     * @param onBackPressedCallback The callback to add
+     * @return a {@link Cancellable} which can be used to {@link Cancellable#cancel() cancel}
+     * the callback and remove it from the set of OnBackPressedCallbacks. The callback won't be
+     * called for any future {@link #onBackPressed()} calls, but may still receive a
+     * callback if {@link Cancellable#cancel()} is called during the dispatch of an ongoing
+     * {@link #onBackPressed()} call.
+     *
+     * @see #onBackPressed()
+     */
+    @NonNull
+    public Cancellable addCallback(@NonNull OnBackPressedCallback onBackPressedCallback) {
+        synchronized (mOnBackPressedCallbacks) {
+            mOnBackPressedCallbacks.add(onBackPressedCallback);
+        }
+        return new OnBackPressedCancellable(onBackPressedCallback);
+    }
+
+    /**
+     * Receive callbacks to a new {@link OnBackPressedCallback} when the given
+     * {@link LifecycleOwner} is at least {@link Lifecycle.State#STARTED started}.
+     * <p>
+     * This will automatically call {@link #addCallback(OnBackPressedCallback)} and
+     * {@link Cancellable#cancel()} as the lifecycle state changes.
+     * As a corollary, if your lifecycle is already at least
+     * {@link Lifecycle.State#STARTED started}, calling this method will result in an immediate
+     * call to {@link #addCallback(OnBackPressedCallback)}.
+     * <p>
+     * When the {@link LifecycleOwner} is {@link Lifecycle.State#DESTROYED destroyed}, it will
+     * automatically be removed from the list of callbacks. The only time you would need to
+     * manually call {@link Cancellable#cancel()} on the returned {@link Cancellable} is if
+     * you'd like to remove the callback prior to destruction of the associated lifecycle.
+     *
+     * <p>If the Lifecycle is already
+     * {@link Lifecycle.State#DESTROYED destroyed} when this method is called, this will
+     * return{@link Cancellable#CANCELLED} and the callback will not be added.
+     *
+     * @param owner The LifecycleOwner which controls when the callback should be invoked
+     * @param onBackPressedCallback The callback to add
+     * @return a {@link Cancellable} which can be used to {@link Cancellable#cancel() cancel}
+     * the callback and remove the associated {@link androidx.lifecycle.LifecycleObserver}
+     * and the OnBackPressedCallback. The callback won't be called for any future
+     * {@link #onBackPressed()} calls, but may still receive a callback if
+     * {@link Cancellable#cancel()} is called during the dispatch of an ongoing
+     * {@link #onBackPressed()} call.
+     *
+     * @see #onBackPressed()
+     */
+    @NonNull
+    public Cancellable addCallback(@NonNull LifecycleOwner owner,
+            @NonNull OnBackPressedCallback onBackPressedCallback) {
+        Lifecycle lifecycle = owner.getLifecycle();
+        if (lifecycle.getCurrentState() == Lifecycle.State.DESTROYED) {
+            return Cancellable.CANCELLED;
+        }
+        return new LifecycleOnBackPressedCancellable(lifecycle, onBackPressedCallback);
+    }
+
+    /**
+     * Trigger a call to the currently added {@link OnBackPressedCallback callbacks} in reverse
+     * order in which they were added. Only if the most recently added callback returns
+     * <code>false</code> from its {@link OnBackPressedCallback#handleOnBackPressed()}
+     * will any previously added callback be called.
+     *
+     * @return True if an added {@link OnBackPressedCallback} handled the back button.
+     */
+    public boolean onBackPressed() {
+        synchronized (mOnBackPressedCallbacks) {
+            Iterator<OnBackPressedCallback> iterator =
+                    mOnBackPressedCallbacks.descendingIterator();
+            while (iterator.hasNext()) {
+                if (iterator.next().handleOnBackPressed()) {
+                    return true;
+                }
+            }
+            return false;
+        }
+    }
+
+    private class OnBackPressedCancellable implements Cancellable {
+        private final OnBackPressedCallback mOnBackPressedCallback;
+        private boolean mCancelled;
+
+        OnBackPressedCancellable(OnBackPressedCallback onBackPressedCallback) {
+            mOnBackPressedCallback = onBackPressedCallback;
+        }
+
+        @Override
+        public void cancel() {
+            synchronized (mOnBackPressedCallbacks) {
+                mOnBackPressedCallbacks.remove(mOnBackPressedCallback);
+                mCancelled = true;
+            }
+        }
+
+        @Override
+        public boolean isCancelled() {
+            return mCancelled;
+        }
+    }
+
+    private class LifecycleOnBackPressedCancellable implements GenericLifecycleObserver,
+            Cancellable {
+        private final Lifecycle mLifecycle;
+        private final OnBackPressedCallback mOnBackPressedCallback;
+
+        @Nullable
+        private Cancellable mCurrentCancellable;
+        private boolean mCancelled = false;
+
+        LifecycleOnBackPressedCancellable(@NonNull Lifecycle lifecycle,
+                @NonNull OnBackPressedCallback onBackPressedCallback) {
+            mLifecycle = lifecycle;
+            mOnBackPressedCallback = onBackPressedCallback;
+            lifecycle.addObserver(this);
+        }
+
+        @Override
+        public void onStateChanged(@NonNull LifecycleOwner source,
+                @NonNull Lifecycle.Event event) {
+            if (event == Lifecycle.Event.ON_START) {
+                mCurrentCancellable = addCallback(mOnBackPressedCallback);
+            } else if (event == Lifecycle.Event.ON_STOP) {
+                // Should always be non-null
+                if (mCurrentCancellable != null) {
+                    mCurrentCancellable.cancel();
+                }
+            } else if (event == Lifecycle.Event.ON_DESTROY) {
+                cancel();
+            }
+        }
+
+        @Override
+        public void cancel() {
+            mLifecycle.removeObserver(this);
+            if (mCurrentCancellable != null) {
+                mCurrentCancellable.cancel();
+                mCurrentCancellable = null;
+            }
+            mCancelled = true;
+        }
+
+        @Override
+        public boolean isCancelled() {
+            return mCancelled;
+        }
+    }
+}
diff --git a/appcompat/resources/build.gradle b/appcompat/resources/build.gradle
index ebdf4d3..b78317e 100644
--- a/appcompat/resources/build.gradle
+++ b/appcompat/resources/build.gradle
@@ -25,11 +25,10 @@
 
 dependencies {
     api(project(":annotation"))
-
     api("androidx.core:core:1.0.1")
     implementation("androidx.collection:collection:1.0.0")
-    api("androidx.vectordrawable:vectordrawable:1.0.1")
-    api("androidx.vectordrawable:vectordrawable-animated:1.0.0")
+    api(project(":vectordrawable"))
+    api(project(":vectordrawable-animated"))
 
     androidTestImplementation(TEST_EXT_JUNIT)
     androidTestImplementation(TEST_CORE)
diff --git a/appcompat/src/androidTest/java/androidx/appcompat/widget/AppCompatSpinnerTest.java b/appcompat/src/androidTest/java/androidx/appcompat/widget/AppCompatSpinnerTest.java
index d4267b0..f9695e0 100644
--- a/appcompat/src/androidTest/java/androidx/appcompat/widget/AppCompatSpinnerTest.java
+++ b/appcompat/src/androidTest/java/androidx/appcompat/widget/AppCompatSpinnerTest.java
@@ -19,8 +19,10 @@
 import static androidx.appcompat.testutils.TestUtilsMatchers.isCombinedBackground;
 import static androidx.test.espresso.Espresso.onView;
 import static androidx.test.espresso.action.ViewActions.click;
+import static androidx.test.espresso.assertion.ViewAssertions.doesNotExist;
 import static androidx.test.espresso.assertion.ViewAssertions.matches;
 import static androidx.test.espresso.matcher.RootMatchers.isPlatformPopup;
+import static androidx.test.espresso.matcher.ViewMatchers.isAssignableFrom;
 import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
 import static androidx.test.espresso.matcher.ViewMatchers.withId;
 import static androidx.test.espresso.matcher.ViewMatchers.withText;
@@ -32,6 +34,7 @@
 import android.content.pm.ActivityInfo;
 import android.content.res.Resources;
 import android.os.SystemClock;
+import android.view.View;
 
 import androidx.annotation.ColorInt;
 import androidx.annotation.ColorRes;
@@ -40,6 +43,11 @@
 import androidx.appcompat.test.R;
 import androidx.core.content.ContextCompat;
 import androidx.core.content.res.ResourcesCompat;
+import androidx.test.espresso.ViewAction;
+import androidx.test.espresso.action.CoordinatesProvider;
+import androidx.test.espresso.action.GeneralSwipeAction;
+import androidx.test.espresso.action.Press;
+import androidx.test.espresso.action.Swipe;
 import androidx.test.filters.LargeTest;
 import androidx.test.filters.MediumTest;
 
@@ -186,4 +194,74 @@
         mActivity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
         onView(withText(EARTH)).check(matches(isDisplayed()));
     }
+
+    @LargeTest
+    @Test
+    public void testSlowScroll() {
+        onView(withId(R.id.spinner_dropdown_popup_with_scroll)).perform(click());
+
+        final AppCompatSpinner spinner = mContainer
+                .findViewById(R.id.spinner_dropdown_popup_with_scroll);
+        String secondItem = (String) spinner.getAdapter().getItem(1);
+
+        onView(isAssignableFrom(DropDownListView.class)).perform(slowScrollPopup());
+
+        // when we scroll slowly a second time the popup list might jump back to the first element
+        onView(isAssignableFrom(DropDownListView.class)).perform(slowScrollPopup());
+
+        // because we scroll twice with one element height each,
+        // the second item should not be visible
+        onView(withText(secondItem))
+                .check(doesNotExist());
+    }
+
+    private ViewAction slowScrollPopup() {
+        return new GeneralSwipeAction(Swipe.SLOW,
+                new CoordinatesProvider() {
+                    @Override
+                    public float[] calculateCoordinates(View view) {
+                        final float[] middleLocation = getViewMiddleLocation(view);
+                        return new float[] {
+                                middleLocation[0],
+                                middleLocation[1]
+                        };
+                    }
+                },
+                new CoordinatesProvider() {
+                    @Override
+                    public float[] calculateCoordinates(View view) {
+                        final float[] middleLocation = getViewMiddleLocation(view);
+                        return new float[] {
+                                middleLocation[0],
+                                middleLocation[1] - getElementSize(view)
+                        };
+                    }
+                },
+                Press.PINPOINT
+        );
+    }
+
+    private float[] getViewMiddleLocation(View view) {
+        final DropDownListView list = (DropDownListView) view;
+
+        final int[] location = new int[2];
+        list.getLocationOnScreen(location);
+
+        final float x = location[0] + list.getWidth() / 2f;
+        final float y = location[1] + list.getHeight() / 2f;
+
+        return new float[] {x, y};
+    }
+
+    private int getElementSize(View view) {
+        final DropDownListView list = (DropDownListView) view;
+
+        final View child = list.getChildAt(0);
+        final int[] location = new int[2];
+        child.getLocationOnScreen(location);
+
+        // espresso doesn't actually scroll for the full amount specified
+        // so we add a little bit more to be safe
+        return child.getHeight() * 2;
+    }
 }
diff --git a/appcompat/src/androidTest/java/androidx/appcompat/widget/ToolbarTest.java b/appcompat/src/androidTest/java/androidx/appcompat/widget/ToolbarTest.java
index 047a560..eb6ee0e 100644
--- a/appcompat/src/androidTest/java/androidx/appcompat/widget/ToolbarTest.java
+++ b/appcompat/src/androidTest/java/androidx/appcompat/widget/ToolbarTest.java
@@ -32,7 +32,6 @@
 import androidx.appcompat.content.res.AppCompatResources;
 import androidx.appcompat.test.R;
 import androidx.appcompat.testutils.TestUtils;
-import androidx.test.espresso.Espresso;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.LargeTest;
 import androidx.test.platform.app.InstrumentationRegistry;
@@ -155,18 +154,9 @@
     }
 
     @Test
-    public void testToolbarOverflowIconWithThemedCSL() throws Throwable {
+    public void testToolbarOverflowIconWithThemedCSL() {
         final Toolbar toolbar = mActivity.findViewById(R.id.toolbar_themedcsl_colorcontrolnormal);
 
-        // Inflate a menu so that the overflow is displayed
-        mActivityRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                toolbar.inflateMenu(R.menu.popup_menu);
-            }
-        });
-        Espresso.onIdle();
-
         // Assert that the overflow icon is tinted magenta, as per the theme
         final Drawable icon = toolbar.getOverflowIcon();
         assertNotNull(icon);
diff --git a/appcompat/src/androidTest/res/layout/appcompat_spinner_activity.xml b/appcompat/src/androidTest/res/layout/appcompat_spinner_activity.xml
index da55bdf..38228a9 100644
--- a/appcompat/src/androidTest/res/layout/appcompat_spinner_activity.xml
+++ b/appcompat/src/androidTest/res/layout/appcompat_spinner_activity.xml
@@ -93,6 +93,13 @@
                 android:layout_height="wrap_content"
                 android:entries="@array/planets_array"
                 android:spinnerMode="dropdown" />
+
+        <androidx.appcompat.widget.AppCompatSpinner
+                android:id="@+id/spinner_dropdown_popup_with_scroll"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:entries="@array/numbers_array"
+                android:spinnerMode="dropdown" />
     </LinearLayout>
 
 </ScrollView>
diff --git a/appcompat/src/androidTest/res/layout/appcompat_toolbar_activity.xml b/appcompat/src/androidTest/res/layout/appcompat_toolbar_activity.xml
index c1bab6a..0d11cab 100644
--- a/appcompat/src/androidTest/res/layout/appcompat_toolbar_activity.xml
+++ b/appcompat/src/androidTest/res/layout/appcompat_toolbar_activity.xml
@@ -56,6 +56,7 @@
         android:layout_height="wrap_content"
         android:minHeight="?attr/actionBarSize"
         android:theme="@style/ThemeOverlay.ThemedCslMagenta"
+        app:menu="@menu/popup_menu"
         app:subtitle="Subtitle"
         app:title="Title" />
 
diff --git a/appcompat/src/androidTest/res/values/strings.xml b/appcompat/src/androidTest/res/values/strings.xml
index 964c32b..0188024 100644
--- a/appcompat/src/androidTest/res/values/strings.xml
+++ b/appcompat/src/androidTest/res/values/strings.xml
@@ -78,6 +78,48 @@
         <item>Neptune</item>
         <item>Pluto</item>
     </string-array>
+    <string-array name="numbers_array">
+        <item>0</item>
+        <item>1</item>
+        <item>2</item>
+        <item>3</item>
+        <item>4</item>
+        <item>5</item>
+        <item>6</item>
+        <item>7</item>
+        <item>8</item>
+        <item>9</item>
+        <item>10</item>
+        <item>11</item>
+        <item>12</item>
+        <item>13</item>
+        <item>14</item>
+        <item>15</item>
+        <item>16</item>
+        <item>17</item>
+        <item>18</item>
+        <item>19</item>
+        <item>20</item>
+        <item>21</item>
+        <item>22</item>
+        <item>23</item>
+        <item>24</item>
+        <item>25</item>
+        <item>26</item>
+        <item>27</item>
+        <item>28</item>
+        <item>29</item>
+        <item>30</item>
+        <item>31</item>
+        <item>32</item>
+        <item>33</item>
+        <item>34</item>
+        <item>35</item>
+        <item>36</item>
+        <item>37</item>
+        <item>38</item>
+        <item>39</item>
+    </string-array>
 
     <string name="night_mode">DAY</string>
 
diff --git a/appcompat/src/main/java/androidx/appcompat/widget/AppCompatSpinner.java b/appcompat/src/main/java/androidx/appcompat/widget/AppCompatSpinner.java
index 86886bd..1033e46 100644
--- a/appcompat/src/main/java/androidx/appcompat/widget/AppCompatSpinner.java
+++ b/appcompat/src/main/java/androidx/appcompat/widget/AppCompatSpinner.java
@@ -1015,12 +1015,6 @@
         }
 
         @Override
-        @SuppressLint("SyntheticAccessor")
-        public void show() {
-            showPopup();
-        }
-
-        @Override
         public void show(int textDirection, int textAlignment) {
             final boolean wasShowing = isShowing();
 
diff --git a/arch/core-common/api/2.1.0-alpha01.txt b/arch/core-common/api/2.1.0-alpha01.txt
index dba1d6a7..4068f2c 100644
--- a/arch/core-common/api/2.1.0-alpha01.txt
+++ b/arch/core-common/api/2.1.0-alpha01.txt
@@ -1,6 +1,12 @@
 // Signature format: 3.0
 package androidx.arch.core.util {
 
+  public interface Cancellable {
+    method public void cancel();
+    method public boolean isCancelled();
+    field public static final androidx.arch.core.util.Cancellable CANCELLED;
+  }
+
   public interface Function<I, O> {
     method public O! apply(I!);
   }
diff --git a/arch/core-common/api/current.txt b/arch/core-common/api/current.txt
index dba1d6a7..4068f2c 100644
--- a/arch/core-common/api/current.txt
+++ b/arch/core-common/api/current.txt
@@ -1,6 +1,12 @@
 // Signature format: 3.0
 package androidx.arch.core.util {
 
+  public interface Cancellable {
+    method public void cancel();
+    method public boolean isCancelled();
+    field public static final androidx.arch.core.util.Cancellable CANCELLED;
+  }
+
   public interface Function<I, O> {
     method public O! apply(I!);
   }
diff --git a/arch/core-common/src/main/java/androidx/arch/core/util/Cancellable.java b/arch/core-common/src/main/java/androidx/arch/core/util/Cancellable.java
new file mode 100644
index 0000000..232f208
--- /dev/null
+++ b/arch/core-common/src/main/java/androidx/arch/core/util/Cancellable.java
@@ -0,0 +1,55 @@
+/*
+ * 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.arch.core.util;
+
+import androidx.annotation.NonNull;
+
+/**
+ * Token representing a cancellable operation.
+ */
+public interface Cancellable {
+    /**
+     * An instance of Cancellable that is always cancelled - i.e., {@link #isCancelled()} will
+     * always return true.
+     */
+    @NonNull
+    Cancellable CANCELLED = new Cancellable() {
+        @Override
+        public void cancel() {
+        }
+
+        @Override
+        public boolean isCancelled() {
+            return true;
+        }
+    };
+
+    /**
+     * Cancel the subscription. This call should be idempotent, making it safe to
+     * call multiple times.
+     */
+    void cancel();
+
+    /**
+     * Returns true if the subscription has been cancelled. This is inherently a
+     * racy operation if you are calling {@link #cancel()} on another thread, so this
+     * should be treated as a 'best effort' signal.
+     *
+     * @return Whether the subscription has been cancelled.
+     */
+    boolean isCancelled();
+}
diff --git a/biometric/res/values/strings.xml b/biometric/res/values/strings.xml
index e961bcb..1ee1534 100644
--- a/biometric/res/values/strings.xml
+++ b/biometric/res/values/strings.xml
@@ -32,6 +32,8 @@
     <string name="fingerprint_error_hw_not_present">This device does not have a fingerprint sensor</string>
     <!-- Generic error message shown when the fingerprint authentication operation is canceled due to user input. Generally not shown to the user. [CHAR LIMIT=NONE] -->
     <string name="fingerprint_error_user_canceled">Fingerprint operation canceled by user.</string>
+    <!-- Error message shown after too many failed authentication attempts. [CHAR LIMIT=NONE] -->
+    <string name="fingerprint_error_lockout">Too many attempts. Please try again later.</string>
     <!-- Generic error message shown when an unknown error has occurred. [CHAR LIMIT=NONE] -->
     <string name="default_error_msg">Unknown error</string>
 </resources>
diff --git a/biometric/src/main/java/androidx/biometric/BiometricPrompt.java b/biometric/src/main/java/androidx/biometric/BiometricPrompt.java
index da6bf70..efdd360 100644
--- a/biometric/src/main/java/androidx/biometric/BiometricPrompt.java
+++ b/biometric/src/main/java/androidx/biometric/BiometricPrompt.java
@@ -21,6 +21,7 @@
 import android.content.DialogInterface;
 import android.os.Build;
 import android.os.Bundle;
+import android.os.Handler;
 import android.text.TextUtils;
 import android.util.Log;
 
@@ -52,6 +53,9 @@
 
     private static final String TAG = "BiometricPromptCompat";
     private static final boolean DEBUG = false;
+    // In order to keep consistent behavior between versions, we need to send
+    // FingerprintDialogFragment a message indicating whether or not to dismiss the UI instantly.
+    private static final int DELAY_MILLIS = 500;
 
     static final String DIALOG_FRAGMENT_TAG = "FingerprintDialogFragment";
     static final String FINGERPRINT_HELPER_FRAGMENT_TAG = "FingerprintHelperFragment";
@@ -499,8 +503,12 @@
                 mFingerprintHelperFragment = FingerprintHelperFragment.newInstance();
             }
             mFingerprintHelperFragment.setCallback(mExecutor, mAuthenticationCallback);
-            mFingerprintHelperFragment.setHandler(mFingerprintDialogFragment.getHandler());
+            final Handler fingerprintDialogHandler = mFingerprintDialogFragment.getHandler();
+            mFingerprintHelperFragment.setHandler(fingerprintDialogHandler);
             mFingerprintHelperFragment.setCryptoObject(crypto);
+            fingerprintDialogHandler.sendMessageDelayed(
+                    fingerprintDialogHandler.obtainMessage(
+                            FingerprintDialogFragment.DISPLAYED_FOR_500_MS), DELAY_MILLIS);
 
             if (fragmentManager.findFragmentByTag(FINGERPRINT_HELPER_FRAGMENT_TAG) == null) {
                 // If the fragment hasn't been added before, add it. It will also start the
diff --git a/biometric/src/main/java/androidx/biometric/FingerprintDialogFragment.java b/biometric/src/main/java/androidx/biometric/FingerprintDialogFragment.java
index 763be0a..c8354ca 100644
--- a/biometric/src/main/java/androidx/biometric/FingerprintDialogFragment.java
+++ b/biometric/src/main/java/androidx/biometric/FingerprintDialogFragment.java
@@ -65,9 +65,14 @@
     // Show an error in the help area, and dismiss the dialog afterwards
     protected static final int MSG_SHOW_ERROR = 2;
     // Dismisses the authentication dialog
-    protected static final int MSG_DISMISS_DIALOG = 3;
+    protected static final int MSG_DISMISS_DIALOG_ERROR = 3;
     // Resets the help message
     protected static final int MSG_RESET_MESSAGE = 4;
+    // Dismisses the authentication dialog after success.
+    protected static final int MSG_DISMISS_DIALOG_AUTHENTICATED = 5;
+    // The amount of time required that this fragment be displayed for in order that
+    // we show an error message on top of the UI.
+    protected static final int DISPLAYED_FOR_500_MS = 6;
 
     // States for icon animation
     private static final int STATE_NONE = 0;
@@ -93,12 +98,18 @@
                 case MSG_SHOW_ERROR:
                     handleShowError(msg.arg1, (CharSequence) msg.obj);
                     break;
-                case MSG_DISMISS_DIALOG:
-                    handleDismissDialog();
+                case MSG_DISMISS_DIALOG_ERROR:
+                    handleDismissDialogError();
+                    break;
+                case MSG_DISMISS_DIALOG_AUTHENTICATED:
+                    dismiss();
                     break;
                 case MSG_RESET_MESSAGE:
                     handleResetMessage();
                     break;
+                case DISPLAYED_FOR_500_MS:
+                    mDismissInstantly = false;
+                    break;
             }
         }
     }
@@ -113,6 +124,13 @@
 
     private Context mContext;
     private Dialog mDialog;
+    /**
+     * This flag is used to control the instant dismissal of the dialog fragment. In the case where
+     * the user is already locked out this dialog will not appear. In the case where the user is
+     * being locked out for the first time an error message will be displayed on the UI before
+     * dismissing.
+     */
+    protected boolean mDismissInstantly = true;
 
     // This should be re-set by the BiometricPromptCompat each time the lifecycle changes.
     DialogInterface.OnClickListener mNegativeButtonListener;
@@ -328,11 +346,31 @@
         mErrorText.setText(msg);
 
         // Dismiss the dialog after a delay
-        mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_DISMISS_DIALOG), HIDE_DIALOG_DELAY);
+        mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_DISMISS_DIALOG_ERROR),
+                HIDE_DIALOG_DELAY);
     }
 
-    void handleDismissDialog() {
-        dismiss();
+    void dismissAfterDelay() {
+        mErrorText.setTextColor(mErrorColor);
+        mErrorText.setText(
+                R.string.fingerprint_error_lockout);
+        mHandler.postDelayed(new Runnable() {
+            @Override
+            public void run() {
+                dismiss();
+            }
+        }, HIDE_DIALOG_DELAY);
+    }
+
+    void handleDismissDialogError() {
+        if (mDismissInstantly) {
+            dismiss();
+        } else {
+            dismissAfterDelay();
+        }
+        // Always set this to true. In case the user tries to authenticate again the UI will not be
+        // shown.
+        mDismissInstantly = true;
     }
 
     void handleResetMessage() {
diff --git a/biometric/src/main/java/androidx/biometric/FingerprintHelperFragment.java b/biometric/src/main/java/androidx/biometric/FingerprintHelperFragment.java
index 12d5bce..468d819 100644
--- a/biometric/src/main/java/androidx/biometric/FingerprintHelperFragment.java
+++ b/biometric/src/main/java/androidx/biometric/FingerprintHelperFragment.java
@@ -70,7 +70,7 @@
 
                 private void dismissAndForwardResult(final int errMsgId,
                         final CharSequence errString) {
-                    mHandler.obtainMessage(FingerprintDialogFragment.MSG_DISMISS_DIALOG)
+                    mHandler.obtainMessage(FingerprintDialogFragment.MSG_DISMISS_DIALOG_ERROR)
                             .sendToTarget();
                     mExecutor.execute(new Runnable() {
                         @Override
@@ -124,7 +124,8 @@
                 public void onAuthenticationSucceeded(
                         final FingerprintManagerCompat.AuthenticationResult result) {
                     mHandler.obtainMessage(
-                            FingerprintDialogFragment.MSG_DISMISS_DIALOG).sendToTarget();
+                            FingerprintDialogFragment.MSG_DISMISS_DIALOG_AUTHENTICATED)
+                            .sendToTarget();
                     mExecutor.execute(new Runnable() {
                         @Override
                         public void run() {
@@ -182,7 +183,8 @@
             FingerprintManagerCompat fingerprintManagerCompat = FingerprintManagerCompat.from(
                     mContext);
             if (handlePreAuthenticationErrors(fingerprintManagerCompat)) {
-                mHandler.obtainMessage(FingerprintDialogFragment.MSG_DISMISS_DIALOG).sendToTarget();
+                mHandler.obtainMessage(
+                        FingerprintDialogFragment.MSG_DISMISS_DIALOG_ERROR).sendToTarget();
                 cleanup();
             } else {
                 fingerprintManagerCompat.authenticate(
diff --git a/buildSrc/src/main/kotlin/androidx/build/DiffAndDocs.kt b/buildSrc/src/main/kotlin/androidx/build/DiffAndDocs.kt
index 4c8706c..8c6efff 100644
--- a/buildSrc/src/main/kotlin/androidx/build/DiffAndDocs.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/DiffAndDocs.kt
@@ -567,6 +567,7 @@
 ): TaskProvider<GenerateDocsTask> =
         project.tasks.register(taskName, GenerateDocsTask::class.java) {
             it.apply {
+                exclude("**/R.java")
                 dependsOn(generateSdkApiTask, doclavaConfig)
                 group = JavaBasePlugin.DOCUMENTATION_GROUP
                 description = "Generates Java documentation in the style of d.android.com. To generate offline " +
diff --git a/buildSrc/src/main/kotlin/androidx/build/LibraryVersions.kt b/buildSrc/src/main/kotlin/androidx/build/LibraryVersions.kt
index bc192d8..1dbcbd3 100644
--- a/buildSrc/src/main/kotlin/androidx/build/LibraryVersions.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/LibraryVersions.kt
@@ -68,7 +68,7 @@
     val MEDIA2_EXOPLAYER = Version("1.0.0-alpha02")
     val MEDIA2_WIDGET = Version("1.0.0-alpha07")
     val MEDIAROUTER = Version("1.1.0-alpha03")
-    val NAVIGATION = Version("2.1.0-alpha01")
+    val NAVIGATION = Version("2.1.0-alpha02")
     val NAVIGATION_TESTING = Version("1.0.0-alpha08") // Unpublished
     val PAGING = Version("2.2.0-alpha01")
     val PALETTE = Version("1.1.0-alpha01")
diff --git a/buildSrc/src/main/kotlin/androidx/build/PublishDocsRules.kt b/buildSrc/src/main/kotlin/androidx/build/PublishDocsRules.kt
index ac7f567..b448b23 100644
--- a/buildSrc/src/main/kotlin/androidx/build/PublishDocsRules.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/PublishDocsRules.kt
@@ -70,7 +70,7 @@
     prebuilts(LibraryGroups.LIFECYCLE, "2.1.0-alpha03")
     prebuilts(LibraryGroups.LOADER, "1.1.0-beta01")
     prebuilts(LibraryGroups.LOCALBROADCASTMANAGER, "1.1.0-alpha01")
-    prebuilts(LibraryGroups.MEDIA, "media", "1.1.0-alpha02")
+    prebuilts(LibraryGroups.MEDIA, "media", "1.1.0-alpha03")
     // TODO: Rename media-widget to media2-widget after 1.0.0-alpha06
     prebuilts(LibraryGroups.MEDIA, "media-widget", "1.0.0-alpha06")
     ignore(LibraryGroups.MEDIA2.group, "media2-widget")
@@ -90,7 +90,8 @@
     prebuilts(LibraryGroups.RECYCLERVIEW, "recyclerview", "1.1.0-alpha03")
     prebuilts(LibraryGroups.RECYCLERVIEW, "recyclerview-selection", "1.1.0-alpha01")
     prebuilts(LibraryGroups.REMOTECALLBACK, "1.0.0-alpha01")
-    prebuilts(LibraryGroups.ROOM, "2.1.0-alpha05")
+    ignore(LibraryGroups.ROOM.group, "room-common-java8")
+    prebuilts(LibraryGroups.ROOM, "2.1.0-alpha06")
     prebuilts(LibraryGroups.SAVEDSTATE, "1.0.0-alpha02")
     prebuilts(LibraryGroups.SHARETARGET, "1.0.0-alpha01")
     prebuilts(LibraryGroups.SLICE, "slice-builders", "1.0.0")
@@ -113,7 +114,7 @@
     prebuilts(LibraryGroups.WEAR, "1.0.0")
             .addStubs("wear/wear_stubs/com.google.android.wearable-stubs.jar")
     prebuilts(LibraryGroups.WEBKIT, "1.0.0")
-    prebuilts(LibraryGroups.WORKMANAGER, "2.0.0-rc01")
+    prebuilts(LibraryGroups.WORKMANAGER, "2.0.0")
     default(Ignore)
 }
 
diff --git a/buildSrc/src/main/kotlin/androidx/build/dependencies/Dependencies.kt b/buildSrc/src/main/kotlin/androidx/build/dependencies/Dependencies.kt
index e21a1e6..6d4dac2 100644
--- a/buildSrc/src/main/kotlin/androidx/build/dependencies/Dependencies.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/dependencies/Dependencies.kt
@@ -87,9 +87,9 @@
         "androidx.lifecycle:lifecycle-livedata:2.0.0"
 const val ARCH_LIFECYCLE_VIEWMODEL = "androidx.lifecycle:lifecycle-viewmodel:2.0.0"
 const val ARCH_LIFECYCLE_EXTENSIONS = "androidx.lifecycle:lifecycle-extensions:2.0.0"
-const val ARCH_CORE_COMMON = "androidx.arch.core:core-common:2.0.0@jar"
-const val ARCH_CORE_RUNTIME = "androidx.arch.core:core-runtime:2.0.0"
-const val ARCH_CORE_TESTING = "androidx.arch.core:core-testing:2.0.0"
+const val ARCH_CORE_COMMON = "androidx.arch.core:core-common:2.0.1@jar"
+const val ARCH_CORE_RUNTIME = "androidx.arch.core:core-runtime:2.0.1"
+const val ARCH_CORE_TESTING = "androidx.arch.core:core-testing:2.0.1"
 
 const val SAFE_ARGS_ANDROID_GRADLE_PLUGIN = "com.android.tools.build:gradle:3.3.0"
 const val SAFE_ARGS_KOTLIN_GRADLE_PLUGIN = "org.jetbrains.kotlin:kotlin-gradle-plugin:1.3.20"
@@ -102,4 +102,7 @@
 const val ARCH_ROOM_RXJAVA = "androidx.room:room-rxjava2:2.0.0"
 const val ARCH_ROOM_TESTING = "androidx.room:room-testing:2.0.0"
 
+const val WORK_ARCH_CORE_RUNTIME = "androidx.arch.core:core-runtime:2.0.0"
+const val WORK_ARCH_CORE_TESTING = "androidx.arch.core:core-testing:2.0.0"
+
 const val ROBOLECTRIC = "org.robolectric:robolectric:4.1"
diff --git a/car/core/api/1.0.0-alpha7.txt b/car/core/api/1.0.0-alpha7.txt
index a5d0f09..c3570ec 100644
--- a/car/core/api/1.0.0-alpha7.txt
+++ b/car/core/api/1.0.0-alpha7.txt
@@ -366,6 +366,7 @@
     ctor public PagedListView(android.content.Context!, android.util.AttributeSet!, int, int);
     method public void addItemDecoration(androidx.recyclerview.widget.RecyclerView.ItemDecoration);
     method public void addOnItemTouchListener(androidx.recyclerview.widget.RecyclerView.OnItemTouchListener);
+    method public void addOnScrollListener(androidx.recyclerview.widget.RecyclerView.OnScrollListener);
     method public androidx.recyclerview.widget.RecyclerView.Adapter<? extends androidx.recyclerview.widget.RecyclerView.ViewHolder>? getAdapter();
     method public int getListContentBottomOffset();
     method public int getListContentTopOffset();
@@ -384,8 +385,10 @@
     method public void pageDown();
     method public void pageUp();
     method public int positionOf(android.view.View?);
+    method public void registerCallback(androidx.car.widget.PagedListView.Callback);
     method public void removeItemDecoration(androidx.recyclerview.widget.RecyclerView.ItemDecoration);
     method public void removeOnItemTouchListener(androidx.recyclerview.widget.RecyclerView.OnItemTouchListener);
+    method public void removeOnScrollListener(androidx.recyclerview.widget.RecyclerView.OnScrollListener);
     method public void scrollToPosition(int);
     method public void setAdapter(androidx.recyclerview.widget.RecyclerView.Adapter<? extends androidx.recyclerview.widget.RecyclerView.ViewHolder>);
     method public void setDividerColor(@ColorRes int);
@@ -397,16 +400,23 @@
     method public void setListContentBottomOffset(@Px int);
     method public void setListContentTopOffset(@Px int);
     method public void setMaxPages(int);
-    method public void setOnScrollListener(androidx.car.widget.PagedListView.OnScrollListener!);
+    method @Deprecated public void setOnScrollListener(androidx.car.widget.PagedListView.OnScrollListener!);
     method public void setScrollBarContainerWidth(int);
     method public void setScrollBarTopMargin(int);
     method public void setScrollbarThumbEnabled(boolean);
     method public void setUpButtonIcon(android.graphics.drawable.Drawable!);
     method public void showAlphaJump();
     method public void snapToPosition(int);
+    method public void unregisterCallback(androidx.car.widget.PagedListView.Callback);
     field public static final int UNLIMITED_PAGES = -1; // 0xffffffff
   }
 
+  public static interface PagedListView.Callback {
+    method public default void onReachBottom();
+    method public default void onScrollDownButtonClicked();
+    method public default void onScrollUpButtonClicked();
+  }
+
   public static interface PagedListView.DividerVisibilityManager {
     method public boolean getShowDivider(int);
   }
@@ -427,13 +437,13 @@
     method public void setPositionOffset(int);
   }
 
-  public abstract static class PagedListView.OnScrollListener {
-    ctor public PagedListView.OnScrollListener();
-    method public void onReachBottom();
-    method public void onScrollDownButtonClicked();
-    method public void onScrollStateChanged(androidx.recyclerview.widget.RecyclerView!, int);
-    method public void onScrollUpButtonClicked();
-    method public void onScrolled(androidx.recyclerview.widget.RecyclerView!, int, int);
+  @Deprecated public abstract static class PagedListView.OnScrollListener {
+    ctor @Deprecated public PagedListView.OnScrollListener();
+    method @Deprecated public void onReachBottom();
+    method @Deprecated public void onScrollDownButtonClicked();
+    method @Deprecated public void onScrollStateChanged(androidx.recyclerview.widget.RecyclerView!, int);
+    method @Deprecated public void onScrollUpButtonClicked();
+    method @Deprecated public void onScrolled(androidx.recyclerview.widget.RecyclerView!, int, int);
   }
 
   public class PagedScrollBarView extends android.view.ViewGroup {
@@ -567,6 +577,7 @@
     method public void setEnabled(boolean);
     method public void setPrimaryActionEmptyIcon();
     method public void setPrimaryActionIcon(android.graphics.drawable.Icon, int);
+    method public void setPrimaryActionIcon(android.graphics.drawable.Drawable, int);
     method public void setPrimaryActionNoIcon();
     method public void setShowSwitchDivider(boolean);
     method public void setSwitchOnCheckedChangeListener(android.widget.CompoundButton.OnCheckedChangeListener?);
diff --git a/car/core/api/current.txt b/car/core/api/current.txt
index a5d0f09..c3570ec 100644
--- a/car/core/api/current.txt
+++ b/car/core/api/current.txt
@@ -366,6 +366,7 @@
     ctor public PagedListView(android.content.Context!, android.util.AttributeSet!, int, int);
     method public void addItemDecoration(androidx.recyclerview.widget.RecyclerView.ItemDecoration);
     method public void addOnItemTouchListener(androidx.recyclerview.widget.RecyclerView.OnItemTouchListener);
+    method public void addOnScrollListener(androidx.recyclerview.widget.RecyclerView.OnScrollListener);
     method public androidx.recyclerview.widget.RecyclerView.Adapter<? extends androidx.recyclerview.widget.RecyclerView.ViewHolder>? getAdapter();
     method public int getListContentBottomOffset();
     method public int getListContentTopOffset();
@@ -384,8 +385,10 @@
     method public void pageDown();
     method public void pageUp();
     method public int positionOf(android.view.View?);
+    method public void registerCallback(androidx.car.widget.PagedListView.Callback);
     method public void removeItemDecoration(androidx.recyclerview.widget.RecyclerView.ItemDecoration);
     method public void removeOnItemTouchListener(androidx.recyclerview.widget.RecyclerView.OnItemTouchListener);
+    method public void removeOnScrollListener(androidx.recyclerview.widget.RecyclerView.OnScrollListener);
     method public void scrollToPosition(int);
     method public void setAdapter(androidx.recyclerview.widget.RecyclerView.Adapter<? extends androidx.recyclerview.widget.RecyclerView.ViewHolder>);
     method public void setDividerColor(@ColorRes int);
@@ -397,16 +400,23 @@
     method public void setListContentBottomOffset(@Px int);
     method public void setListContentTopOffset(@Px int);
     method public void setMaxPages(int);
-    method public void setOnScrollListener(androidx.car.widget.PagedListView.OnScrollListener!);
+    method @Deprecated public void setOnScrollListener(androidx.car.widget.PagedListView.OnScrollListener!);
     method public void setScrollBarContainerWidth(int);
     method public void setScrollBarTopMargin(int);
     method public void setScrollbarThumbEnabled(boolean);
     method public void setUpButtonIcon(android.graphics.drawable.Drawable!);
     method public void showAlphaJump();
     method public void snapToPosition(int);
+    method public void unregisterCallback(androidx.car.widget.PagedListView.Callback);
     field public static final int UNLIMITED_PAGES = -1; // 0xffffffff
   }
 
+  public static interface PagedListView.Callback {
+    method public default void onReachBottom();
+    method public default void onScrollDownButtonClicked();
+    method public default void onScrollUpButtonClicked();
+  }
+
   public static interface PagedListView.DividerVisibilityManager {
     method public boolean getShowDivider(int);
   }
@@ -427,13 +437,13 @@
     method public void setPositionOffset(int);
   }
 
-  public abstract static class PagedListView.OnScrollListener {
-    ctor public PagedListView.OnScrollListener();
-    method public void onReachBottom();
-    method public void onScrollDownButtonClicked();
-    method public void onScrollStateChanged(androidx.recyclerview.widget.RecyclerView!, int);
-    method public void onScrollUpButtonClicked();
-    method public void onScrolled(androidx.recyclerview.widget.RecyclerView!, int, int);
+  @Deprecated public abstract static class PagedListView.OnScrollListener {
+    ctor @Deprecated public PagedListView.OnScrollListener();
+    method @Deprecated public void onReachBottom();
+    method @Deprecated public void onScrollDownButtonClicked();
+    method @Deprecated public void onScrollStateChanged(androidx.recyclerview.widget.RecyclerView!, int);
+    method @Deprecated public void onScrollUpButtonClicked();
+    method @Deprecated public void onScrolled(androidx.recyclerview.widget.RecyclerView!, int, int);
   }
 
   public class PagedScrollBarView extends android.view.ViewGroup {
@@ -567,6 +577,7 @@
     method public void setEnabled(boolean);
     method public void setPrimaryActionEmptyIcon();
     method public void setPrimaryActionIcon(android.graphics.drawable.Icon, int);
+    method public void setPrimaryActionIcon(android.graphics.drawable.Drawable, int);
     method public void setPrimaryActionNoIcon();
     method public void setShowSwitchDivider(boolean);
     method public void setSwitchOnCheckedChangeListener(android.widget.CompoundButton.OnCheckedChangeListener?);
diff --git a/car/core/api/restricted_1.0.0-alpha7.txt b/car/core/api/restricted_1.0.0-alpha7.txt
index 8a43c31..7dc7128 100644
--- a/car/core/api/restricted_1.0.0-alpha7.txt
+++ b/car/core/api/restricted_1.0.0-alpha7.txt
@@ -44,7 +44,7 @@
     method public static void apply(android.content.Context!, androidx.car.uxrestrictions.CarUxRestrictions!, android.widget.TextView!);
   }
 
-  @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class DropShadowScrollListener extends androidx.car.widget.PagedListView.OnScrollListener {
+  @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class DropShadowScrollListener extends androidx.recyclerview.widget.RecyclerView.OnScrollListener {
     ctor public DropShadowScrollListener(android.view.View!);
   }
 
diff --git a/car/core/api/restricted_current.txt b/car/core/api/restricted_current.txt
index 8a43c31..7dc7128 100644
--- a/car/core/api/restricted_current.txt
+++ b/car/core/api/restricted_current.txt
@@ -44,7 +44,7 @@
     method public static void apply(android.content.Context!, androidx.car.uxrestrictions.CarUxRestrictions!, android.widget.TextView!);
   }
 
-  @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class DropShadowScrollListener extends androidx.car.widget.PagedListView.OnScrollListener {
+  @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class DropShadowScrollListener extends androidx.recyclerview.widget.RecyclerView.OnScrollListener {
     ctor public DropShadowScrollListener(android.view.View!);
   }
 
diff --git a/car/core/res/values/styles.xml b/car/core/res/values/styles.xml
index c3eb2cd..517cc0c 100644
--- a/car/core/res/values/styles.xml
+++ b/car/core/res/values/styles.xml
@@ -72,7 +72,7 @@
     </style>
 
     <style name="TextAppearance.Car.Body1.Medium.Dark">
-        <item name="android:textColor">@color/car_body1_light</item>
+        <item name="android:textColor">@color/car_body1_dark</item>
     </style>
 
     <!-- An alternate styling for body text that is both a different color and size than
diff --git a/car/core/src/androidTest/java/androidx/car/widget/PagedListViewTest.java b/car/core/src/androidTest/java/androidx/car/widget/PagedListViewTest.java
index ef41766..32b9931 100644
--- a/car/core/src/androidTest/java/androidx/car/widget/PagedListViewTest.java
+++ b/car/core/src/androidTest/java/androidx/car/widget/PagedListViewTest.java
@@ -36,6 +36,9 @@
 import static org.hamcrest.Matchers.not;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
 
 import android.content.pm.PackageManager;
 import android.content.res.Resources;
@@ -179,6 +182,76 @@
     }
 
     @Test
+    public void testScrollButtonCallback() {
+        int itemCount = ITEMS_PER_PAGE * 3;
+        setUpPagedListView(itemCount);
+
+        PagedListView.Callback mockedCallbackOne = mock(PagedListView.Callback.class);
+        PagedListView.Callback mockedCallbackTwo = mock(PagedListView.Callback.class);
+        PagedListView.Callback mockedCallbackThree = mock(PagedListView.Callback.class);
+
+        mPagedListView.registerCallback(mockedCallbackOne);
+        mPagedListView.registerCallback(mockedCallbackTwo);
+        mPagedListView.registerCallback(mockedCallbackThree);
+
+        // Move one page down.
+        onView(withId(R.id.page_down)).perform(click());
+        verify(mockedCallbackOne, times(1)).onScrollDownButtonClicked();
+        verify(mockedCallbackTwo, times(1)).onScrollDownButtonClicked();
+        verify(mockedCallbackThree, times(1)).onScrollDownButtonClicked();
+
+        // Move one page up.
+        onView(withId(R.id.page_up)).perform(click());
+        verify(mockedCallbackOne, times(1)).onScrollUpButtonClicked();
+        verify(mockedCallbackTwo, times(1)).onScrollUpButtonClicked();
+        verify(mockedCallbackThree, times(1)).onScrollUpButtonClicked();
+
+        mPagedListView.unregisterCallback(mockedCallbackOne);
+        onView(withId(R.id.page_down)).perform(click());
+        verify(mockedCallbackOne, times(1)).onScrollDownButtonClicked();
+        verify(mockedCallbackTwo, times(2)).onScrollDownButtonClicked();
+        verify(mockedCallbackThree, times(2)).onScrollDownButtonClicked();
+    }
+
+    @Test
+    public void testMultipleScrollButtonCallback() {
+        int itemCount = ITEMS_PER_PAGE * 4;
+        setUpPagedListView(itemCount);
+
+        PagedListView.Callback mockedCallback = mock(PagedListView.Callback.class);
+        mPagedListView.registerCallback(mockedCallback);
+
+        // Move one page down.
+        onView(withId(R.id.page_down)).perform(click());
+        onView(withId(R.id.page_down)).perform(click());
+        onView(withId(R.id.page_down)).perform(click());
+        verify(mockedCallback, times(3)).onScrollDownButtonClicked();
+    }
+
+    @Test
+    public void testReachBottomCallback() {
+        int itemCount = ITEMS_PER_PAGE * 2;
+        setUpPagedListView(itemCount);
+
+        PagedListView.Callback mockedCallback = mock(PagedListView.Callback.class);
+        mPagedListView.registerCallback(mockedCallback);
+
+        // Moving down to bottom of list.
+        onView(withId(R.id.page_down)).perform(click());
+        onView(withId(R.id.page_down)).perform(click());
+
+        verify(mockedCallback, times(1)).onReachBottom();
+
+        // Moving up should not cause a onReachBottom event.
+        onView(withId(R.id.page_up)).perform(click());
+        verify(mockedCallback, times(1)).onReachBottom();
+
+        // Move to bottom of list again.
+        onView(withId(R.id.page_down)).perform(click());
+        verify(mockedCallback, times(2)).onReachBottom();
+    }
+
+    @Test
     public void testPageUpButtonDisabledAtTop() {
         int itemCount = ITEMS_PER_PAGE * 3;
         setUpPagedListView(itemCount);
diff --git a/car/core/src/androidTest/java/androidx/car/widget/SwitchListItemTest.java b/car/core/src/androidTest/java/androidx/car/widget/SwitchListItemTest.java
index cd842bd..ee19dda 100644
--- a/car/core/src/androidTest/java/androidx/car/widget/SwitchListItemTest.java
+++ b/car/core/src/androidTest/java/androidx/car/widget/SwitchListItemTest.java
@@ -474,7 +474,7 @@
     }
 
     @Test
-    public void testSetPrimaryActionIcon() {
+    public void testSetPrimaryActionIcon_withIcon() {
         SwitchListItem item = new SwitchListItem(mActivity);
         item.setPrimaryActionIcon(
                 Icon.createWithResource(mActivity, android.R.drawable.sym_def_app_icon),
@@ -487,6 +487,19 @@
     }
 
     @Test
+    public void testSetPrimaryActionIcon_withDrawable() {
+        SwitchListItem item = new SwitchListItem(mActivity);
+        item.setPrimaryActionIcon(
+                mActivity.getDrawable(android.R.drawable.sym_def_app_icon),
+                SwitchListItem.PRIMARY_ACTION_ICON_SIZE_LARGE);
+
+        List<SwitchListItem> items = Arrays.asList(item);
+        setupPagedListView(items);
+
+        assertThat(getViewHolderAtPosition(0).getPrimaryIcon().getDrawable(), is(notNullValue()));
+    }
+
+    @Test
     public void testPrimaryIconSizesInIncreasingOrder() {
         SwitchListItem small = new SwitchListItem(mActivity);
         small.setPrimaryActionIcon(
diff --git a/car/core/src/main/java/androidx/car/app/CarListDialog.java b/car/core/src/main/java/androidx/car/app/CarListDialog.java
index 47bf005..b49ef1b 100644
--- a/car/core/src/main/java/androidx/car/app/CarListDialog.java
+++ b/car/core/src/main/java/androidx/car/app/CarListDialog.java
@@ -164,7 +164,7 @@
             return;
         }
 
-        mList.setOnScrollListener(new DropShadowScrollListener(mTitleView));
+        mList.addOnScrollListener(new DropShadowScrollListener(mTitleView));
     }
 
     @Override
diff --git a/car/core/src/main/java/androidx/car/drawer/CarDrawerController.java b/car/core/src/main/java/androidx/car/drawer/CarDrawerController.java
index afcec38..74743c2 100644
--- a/car/core/src/main/java/androidx/car/drawer/CarDrawerController.java
+++ b/car/core/src/main/java/androidx/car/drawer/CarDrawerController.java
@@ -117,7 +117,7 @@
                 theme.resolveAttribute(R.attr.drawerToolbarId, outValue, true)
                         ? outValue.resourceId
                         : R.id.drawer_toolbar);
-        mDrawerList.setOnScrollListener(new DropShadowScrollListener(toolbar));
+        mDrawerList.addOnScrollListener(new DropShadowScrollListener(toolbar));
 
         @IdRes int backButtonId = theme.resolveAttribute(R.attr.drawerBackButtonId, outValue, true)
                 ? outValue.resourceId
diff --git a/car/core/src/main/java/androidx/car/util/DropShadowScrollListener.java b/car/core/src/main/java/androidx/car/util/DropShadowScrollListener.java
index 4d3a4fd..1c3567e 100644
--- a/car/core/src/main/java/androidx/car/util/DropShadowScrollListener.java
+++ b/car/core/src/main/java/androidx/car/util/DropShadowScrollListener.java
@@ -37,7 +37,7 @@
  * @hide
  */
 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
-public class DropShadowScrollListener extends PagedListView.OnScrollListener {
+public class DropShadowScrollListener extends RecyclerView.OnScrollListener {
     private static final String TAG = "DropShadowScrollListener";
     private static final int ANIMATION_DURATION_MS = 100;
 
diff --git a/car/core/src/main/java/androidx/car/widget/PagedListView.java b/car/core/src/main/java/androidx/car/widget/PagedListView.java
index f5c1a70..8ad0ec7 100644
--- a/car/core/src/main/java/androidx/car/widget/PagedListView.java
+++ b/car/core/src/main/java/androidx/car/widget/PagedListView.java
@@ -51,6 +51,8 @@
 import androidx.recyclerview.widget.RecyclerView;
 
 import java.lang.annotation.Retention;
+import java.util.ArrayList;
+import java.util.List;
 
 /**
  * View that wraps a {@link RecyclerView} and a scroll bar that has
@@ -106,7 +108,8 @@
      * which point we'll construct it and add it to the view hierarchy as a child of this frame
      * layout.
      */
-    @Nullable private AlphaJumpOverlayView mAlphaJumpView;
+    @Nullable
+    private AlphaJumpOverlayView mAlphaJumpView;
 
     private int mRowsPerPage = -1;
     private RecyclerView.Adapter<? extends RecyclerView.ViewHolder> mAdapter;
@@ -114,6 +117,8 @@
     /** Maximum number of pages to show. */
     private int mMaxPages = UNLIMITED_PAGES;
 
+    /** Package private to allow access to nested classes.  */
+    final List<Callback> mCallbacks = new ArrayList<>();
     OnScrollListener mOnScrollListener;
 
     /** Used to check if there are more items added to the list. */
@@ -244,7 +249,34 @@
         mSnapHelper = new PagedSnapHelper(context);
         mSnapHelper.attachToRecyclerView(mRecyclerView);
 
-        mRecyclerView.addOnScrollListener(mRecyclerViewOnScrollListener);
+        mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
+            @Override
+            public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) {
+                if (mOnScrollListener != null) {
+                    mOnScrollListener.onScrollStateChanged(recyclerView, newState);
+                }
+                if (newState == RecyclerView.SCROLL_STATE_IDLE) {
+                    mHandler.postDelayed(mPaginationRunnable, PAGINATION_HOLD_DELAY_MS);
+                }
+            }
+
+            @Override
+            public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
+                if (mOnScrollListener != null) {
+                    mOnScrollListener.onScrolled(recyclerView, dx, dy);
+
+                    if (!isAtStart() && isAtEnd()) {
+                        mOnScrollListener.onReachBottom();
+                    }
+                }
+                if (!isAtStart() && isAtEnd()) {
+                    for (Callback callback : mCallbacks) {
+                        callback.onReachBottom();
+                    }
+                }
+                updatePaginationButtons(false);
+            }
+        });
         mRecyclerView.getRecycledViewPool().setMaxRecycledViews(0, 12);
 
         if (a.getBoolean(R.styleable.PagedListView_verticallyCenterListContent, false)) {
@@ -298,12 +330,18 @@
                 switch (direction) {
                     case PagedScrollBarView.PaginationListener.PAGE_UP:
                         pageUp();
+                        for (Callback callback : mCallbacks) {
+                            callback.onScrollUpButtonClicked();
+                        }
                         if (mOnScrollListener != null) {
                             mOnScrollListener.onScrollUpButtonClicked();
                         }
                         break;
                     case PagedScrollBarView.PaginationListener.PAGE_DOWN:
                         pageDown();
+                        for (Callback callback : mCallbacks) {
+                            callback.onScrollDownButtonClicked();
+                        }
                         if (mOnScrollListener != null) {
                             mOnScrollListener.onScrollDownButtonClicked();
                         }
@@ -831,11 +869,53 @@
      * PagedListView.
      *
      * @param listener The scroll listener to set.
+     * @deprecated Use {@link #addOnScrollListener(RecyclerView.OnScrollListener)} to be notified
+     * of scroll events within the PagedListView. To be notified of other PagedListView events, use
+     * {@link #registerCallback(Callback)}.
      */
+    @Deprecated
     public void setOnScrollListener(OnScrollListener listener) {
         mOnScrollListener = listener;
     }
 
+    /**
+     * Adds a {@link RecyclerView.OnScrollListener} that will be notified of scroll events
+     * within the PagedListView.
+     *
+     * @param listener The scroll listener to add.
+     */
+    public void addOnScrollListener(@NonNull RecyclerView.OnScrollListener listener) {
+        mRecyclerView.addOnScrollListener(listener);
+    }
+
+    /**
+     * Remove a {@link RecyclerView.OnScrollListener} that was notified of scroll events
+     * within the PagedListView.
+     *
+     * @param listener The scroll listener to remove.
+     */
+    public void removeOnScrollListener(@NonNull RecyclerView.OnScrollListener listener) {
+        mRecyclerView.removeOnScrollListener(listener);
+    }
+
+    /**
+     * Add a {@link Callback} that will be notified of PagedListView events.
+     *
+     * @param callback The callback to add.
+     */
+    public void registerCallback(@NonNull Callback callback) {
+        mCallbacks.add(callback);
+    }
+
+    /**
+     * Remove a {@link Callback} that was notified of PagedListView events.
+     *
+     * @param callback The callback to remove.
+     */
+    public void unregisterCallback(@NonNull Callback callback) {
+        mCallbacks.remove(callback);
+    }
+
     /** Returns the page the given position is on, starting with page 0. */
     public int getPage(int position) {
         if (mRowsPerPage == -1) {
@@ -1246,31 +1326,6 @@
         }
     }
 
-    private final RecyclerView.OnScrollListener mRecyclerViewOnScrollListener =
-            new RecyclerView.OnScrollListener() {
-                @Override
-                public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
-                    if (mOnScrollListener != null) {
-                        mOnScrollListener.onScrolled(recyclerView, dx, dy);
-
-                        if (!isAtStart() && isAtEnd()) {
-                            mOnScrollListener.onReachBottom();
-                        }
-                    }
-                    updatePaginationButtons(false);
-                }
-
-                @Override
-                public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
-                    if (mOnScrollListener != null) {
-                        mOnScrollListener.onScrollStateChanged(recyclerView, newState);
-                    }
-                    if (newState == RecyclerView.SCROLL_STATE_IDLE) {
-                        mHandler.postDelayed(mPaginationRunnable, PAGINATION_HOLD_DELAY_MS);
-                    }
-                }
-            };
-
     final Runnable mPaginationRunnable =
             new Runnable() {
                 @Override
@@ -1291,25 +1346,56 @@
     private final Runnable mUpdatePaginationRunnable =
             () -> updatePaginationButtons(true /*animate*/);
 
-    /** Used to listen for {@code PagedListView} scroll events. */
+    /** Used to listen for {@code PagedListView} events. */
+    public interface Callback {
+        /**
+         * Called when the {@code PagedListView} has been scrolled so that the last item is
+         * completely visible.
+         */
+        default void onReachBottom() {
+        }
+
+        /** Called when scroll up button is clicked */
+        default void onScrollUpButtonClicked() {
+        }
+
+        /** Called when scroll down button is clicked */
+        default void onScrollDownButtonClicked() {
+        }
+    }
+
+    /**
+     * Used to listen for {@code PagedListView} scroll events.
+     *
+     * @deprecated Use {@link RecyclerView.OnScrollListener} to be notified of scroll events within
+     * the PagedListView. To be notified of other PagedListView events, use {@link Callback}.
+     */
+    @Deprecated
     public abstract static class OnScrollListener {
         /**
          * Called when the {@code PagedListView} has been scrolled so that the last item is
          * completely visible.
          */
-        public void onReachBottom() {}
+        public void onReachBottom() {
+        }
+
         /** Called when scroll up button is clicked */
-        public void onScrollUpButtonClicked() {}
+        public void onScrollUpButtonClicked() {
+        }
+
         /** Called when scroll down button is clicked */
-        public void onScrollDownButtonClicked() {}
+        public void onScrollDownButtonClicked() {
+        }
 
         /**
          * Called when RecyclerView.OnScrollListener#onScrolled is called. See
          * RecyclerView.OnScrollListener
          */
-        public void onScrolled(RecyclerView recyclerView, int dx, int dy) {}
+        public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
+        }
 
         /** See RecyclerView.OnScrollListener */
-        public void onScrollStateChanged(RecyclerView recyclerView, int newState) {}
+        public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
+        }
     }
 }
diff --git a/car/core/src/main/java/androidx/car/widget/SwitchListItem.java b/car/core/src/main/java/androidx/car/widget/SwitchListItem.java
index d0ae858..d34820d 100644
--- a/car/core/src/main/java/androidx/car/widget/SwitchListItem.java
+++ b/car/core/src/main/java/androidx/car/widget/SwitchListItem.java
@@ -20,6 +20,7 @@
 
 import android.content.Context;
 import android.content.res.Resources;
+import android.graphics.drawable.Drawable;
 import android.graphics.drawable.Icon;
 import android.os.Handler;
 import android.os.Looper;
@@ -114,6 +115,7 @@
 
     @PrimaryActionType private int mPrimaryActionType = PRIMARY_ACTION_TYPE_NO_ICON;
     private Icon mPrimaryActionIcon;
+    private Drawable mPrimaryActionIconDrawable;
     @PrimaryActionIconSize private int mPrimaryActionIconSize = PRIMARY_ACTION_ICON_SIZE_SMALL;
 
     private CharSequence mTitle;
@@ -202,6 +204,9 @@
     /**
      * Sets {@code Primary Action} to be represented by an icon.
      *
+     * <p>If both this method and {@link #setPrimaryActionIcon(Drawable,int)} are called, then
+     * this method will take precedence.
+     *
      * @param icon An icon to set as primary action.
      * @param size small/medium/large. Available as {@link #PRIMARY_ACTION_ICON_SIZE_SMALL},
      *             {@link #PRIMARY_ACTION_ICON_SIZE_MEDIUM},
@@ -215,6 +220,24 @@
     }
 
     /**
+     * Sets {@code Primary Action} to be represented by an icon.
+     *
+     * <p>If both this method and {@link #setPrimaryActionIcon(Icon,int)} are called, then
+     * the other method will take precedence.
+     *
+     * @param drawable the Drawable to set.
+     * @param size small/medium/large. Available as {@link #PRIMARY_ACTION_ICON_SIZE_SMALL},
+     *             {@link #PRIMARY_ACTION_ICON_SIZE_MEDIUM},
+     *             {@link #PRIMARY_ACTION_ICON_SIZE_LARGE}.
+     */
+    public void setPrimaryActionIcon(@NonNull Drawable drawable, @PrimaryActionIconSize int size) {
+        mPrimaryActionType = PRIMARY_ACTION_TYPE_ICON;
+        mPrimaryActionIconDrawable = drawable;
+        mPrimaryActionIconSize = size;
+        markDirty();
+    }
+
+    /**
      * Sets {@code Primary Action} to be empty icon.
      *
      * <p>{@code Text} would have a start margin as if {@code Primary Action} were set to primary
@@ -324,9 +347,13 @@
             case PRIMARY_ACTION_TYPE_ICON:
                 mBinders.add(vh -> {
                     vh.getPrimaryIcon().setVisibility(View.VISIBLE);
-                    mPrimaryActionIcon.loadDrawableAsync(getContext(),
-                            drawable -> vh.getPrimaryIcon().setImageDrawable(drawable),
-                            new Handler(Looper.getMainLooper()));
+                    if (mPrimaryActionIcon != null) {
+                        mPrimaryActionIcon.loadDrawableAsync(getContext(),
+                                drawable -> vh.getPrimaryIcon().setImageDrawable(drawable),
+                                new Handler(Looper.getMainLooper()));
+                    } else {
+                        vh.getPrimaryIcon().setImageDrawable(mPrimaryActionIconDrawable);
+                    }
                 });
                 break;
             case PRIMARY_ACTION_TYPE_EMPTY_ICON:
diff --git a/core/api/1.1.0-alpha06.txt b/core/api/1.1.0-alpha06.txt
index 0ece2e3..84939d0 100644
--- a/core/api/1.1.0-alpha06.txt
+++ b/core/api/1.1.0-alpha06.txt
@@ -633,10 +633,9 @@
     method public androidx.core.app.Person.Builder setUri(String?);
   }
 
-  public final class RemoteActionCompat {
+  public final class RemoteActionCompat implements androidx.versionedparcelable.VersionedParcelable {
     ctor public RemoteActionCompat(androidx.core.graphics.drawable.IconCompat, CharSequence, CharSequence, android.app.PendingIntent);
     ctor public RemoteActionCompat(androidx.core.app.RemoteActionCompat);
-    method public static androidx.core.app.RemoteActionCompat createFromBundle(android.os.Bundle);
     method @RequiresApi(26) public static androidx.core.app.RemoteActionCompat createFromRemoteAction(android.app.RemoteAction);
     method public android.app.PendingIntent getActionIntent();
     method public CharSequence getContentDescription();
@@ -646,7 +645,6 @@
     method public void setEnabled(boolean);
     method public void setShouldShowIcon(boolean);
     method public boolean shouldShowIcon();
-    method public android.os.Bundle toBundle();
     method @RequiresApi(26) public android.app.RemoteAction toRemoteAction();
   }
 
diff --git a/core/api/current.txt b/core/api/current.txt
index 0ece2e3..84939d0 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -633,10 +633,9 @@
     method public androidx.core.app.Person.Builder setUri(String?);
   }
 
-  public final class RemoteActionCompat {
+  public final class RemoteActionCompat implements androidx.versionedparcelable.VersionedParcelable {
     ctor public RemoteActionCompat(androidx.core.graphics.drawable.IconCompat, CharSequence, CharSequence, android.app.PendingIntent);
     ctor public RemoteActionCompat(androidx.core.app.RemoteActionCompat);
-    method public static androidx.core.app.RemoteActionCompat createFromBundle(android.os.Bundle);
     method @RequiresApi(26) public static androidx.core.app.RemoteActionCompat createFromRemoteAction(android.app.RemoteAction);
     method public android.app.PendingIntent getActionIntent();
     method public CharSequence getContentDescription();
@@ -646,7 +645,6 @@
     method public void setEnabled(boolean);
     method public void setShouldShowIcon(boolean);
     method public boolean shouldShowIcon();
-    method public android.os.Bundle toBundle();
     method @RequiresApi(26) public android.app.RemoteAction toRemoteAction();
   }
 
diff --git a/core/src/androidTest/java/androidx/core/app/RemoteActionCompatTest.java b/core/src/androidTest/java/androidx/core/app/RemoteActionCompatTest.java
index 47da629..a299a46 100644
--- a/core/src/androidTest/java/androidx/core/app/RemoteActionCompatTest.java
+++ b/core/src/androidTest/java/androidx/core/app/RemoteActionCompatTest.java
@@ -21,65 +21,65 @@
 
 import android.app.PendingIntent;
 import android.content.Intent;
+import android.os.Parcel;
 import android.support.v4.BaseInstrumentationTestCase;
 
 import androidx.core.graphics.drawable.IconCompat;
-import androidx.test.core.app.ApplicationProvider;
+import androidx.test.InstrumentationRegistry;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
+import androidx.versionedparcelable.ParcelUtils;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+
 @RunWith(AndroidJUnit4.class)
 @SmallTest
 public class RemoteActionCompatTest extends BaseInstrumentationTestCase<TestActivity> {
+    private static final IconCompat ICON = IconCompat.createWithContentUri("content://test");
+    private static final String TITLE = "title";
+    private static final String DESCRIPTION = "description";
+    private static final PendingIntent ACTION = PendingIntent.getBroadcast(
+            InstrumentationRegistry.getContext(), 0, new Intent("TESTACTION"), 0);
 
     public RemoteActionCompatTest() {
         super(TestActivity.class);
     }
 
     @Test
-    public void testRemoteAction_bundle() throws Throwable {
-        IconCompat icon = IconCompat.createWithContentUri("content://test");
-        String title = "title";
-        String description = "description";
-        PendingIntent action = PendingIntent.getBroadcast(
-                ApplicationProvider.getApplicationContext(), 0,
-                new Intent("TESTACTION"), 0);
-        RemoteActionCompat reference = new RemoteActionCompat(icon, title, description, action);
-        reference.setEnabled(false);
-        reference.setShouldShowIcon(false);
-
-        RemoteActionCompat result = RemoteActionCompat.createFromBundle(reference.toBundle());
-
-        assertEquals(icon.getUri(), result.getIcon().getUri());
-        assertEquals(title, result.getTitle());
-        assertEquals(description, result.getContentDescription());
-        assertEquals(action.getTargetPackage(), result.getActionIntent().getTargetPackage());
-        assertFalse(result.isEnabled());
-        assertFalse(result.shouldShowIcon());
+    public void testRemoteAction_shallowCopy() throws Throwable {
+        RemoteActionCompat reference = createTestRemoteActionCompat();
+        RemoteActionCompat result = new RemoteActionCompat(reference);
+        assertEqualsToTestRemoteActionCompat(result);
     }
 
     @Test
-    public void testRemoteAction_shallowCopy() throws Throwable {
-        IconCompat icon = IconCompat.createWithContentUri("content://test");
-        String title = "title";
-        String description = "description";
-        PendingIntent action = PendingIntent.getBroadcast(
-                ApplicationProvider.getApplicationContext(), 0,
-                new Intent("TESTACTION"), 0);
-        RemoteActionCompat reference = new RemoteActionCompat(icon, title, description, action);
+    public void testRemoteAction_parcel() {
+        RemoteActionCompat reference = createTestRemoteActionCompat();
+
+        Parcel p = Parcel.obtain();
+        p.writeParcelable(ParcelUtils.toParcelable(reference), 0);
+        p.setDataPosition(0);
+        RemoteActionCompat result = ParcelUtils.fromParcelable(
+                p.readParcelable(getClass().getClassLoader()));
+
+        assertEqualsToTestRemoteActionCompat(result);
+    }
+
+    private RemoteActionCompat createTestRemoteActionCompat() {
+        RemoteActionCompat reference = new RemoteActionCompat(ICON, TITLE, DESCRIPTION, ACTION);
         reference.setEnabled(false);
         reference.setShouldShowIcon(false);
-
-        RemoteActionCompat result = new RemoteActionCompat(reference);
-
-        assertEquals(icon.getUri(), result.getIcon().getUri());
-        assertEquals(title, result.getTitle());
-        assertEquals(description, result.getContentDescription());
-        assertEquals(action.getTargetPackage(), result.getActionIntent().getTargetPackage());
-        assertFalse(result.isEnabled());
-        assertFalse(result.shouldShowIcon());
+        return reference;
     }
-}
+
+    private void assertEqualsToTestRemoteActionCompat(RemoteActionCompat remoteAction) {
+        assertEquals(ICON.getUri(), remoteAction.getIcon().getUri());
+        assertEquals(TITLE, remoteAction.getTitle());
+        assertEquals(DESCRIPTION, remoteAction.getContentDescription());
+        assertEquals(ACTION.getTargetPackage(), remoteAction.getActionIntent().getTargetPackage());
+        assertFalse(remoteAction.isEnabled());
+        assertFalse(remoteAction.shouldShowIcon());
+    }
+}
\ No newline at end of file
diff --git a/core/src/main/java/androidx/core/app/RemoteActionCompat.java b/core/src/main/java/androidx/core/app/RemoteActionCompat.java
index faf676c..28f89c7 100644
--- a/core/src/main/java/androidx/core/app/RemoteActionCompat.java
+++ b/core/src/main/java/androidx/core/app/RemoteActionCompat.java
@@ -19,12 +19,14 @@
 import android.app.PendingIntent;
 import android.app.RemoteAction;
 import android.os.Build;
-import android.os.Bundle;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.RequiresApi;
 import androidx.core.graphics.drawable.IconCompat;
 import androidx.core.util.Preconditions;
+import androidx.versionedparcelable.ParcelField;
+import androidx.versionedparcelable.VersionedParcelable;
+import androidx.versionedparcelable.VersionedParcelize;
 
 /**
  * Represents a remote action that can be called from another process.  The action can have an
@@ -32,21 +34,20 @@
  * <p>
  * This is a backward-compatible version of {@link RemoteAction}.
  */
-public final class RemoteActionCompat {
-
-    private static final String EXTRA_ICON = "icon";
-    private static final String EXTRA_TITLE = "title";
-    private static final String EXTRA_CONTENT_DESCRIPTION = "desc";
-    private static final String EXTRA_ACTION_INTENT = "action";
-    private static final String EXTRA_ENABLED = "enabled";
-    private static final String EXTRA_SHOULD_SHOW_ICON = "showicon";
-
-    private final IconCompat mIcon;
-    private final CharSequence mTitle;
-    private final CharSequence mContentDescription;
-    private final PendingIntent mActionIntent;
-    private boolean mEnabled;
-    private boolean mShouldShowIcon;
+@VersionedParcelize(jetifyAs = "android.support.v4.app.RemoteActionCompat")
+public final class RemoteActionCompat implements VersionedParcelable {
+    @ParcelField(1)
+    IconCompat mIcon;
+    @ParcelField(2)
+    CharSequence mTitle;
+    @ParcelField(3)
+    CharSequence mContentDescription;
+    @ParcelField(4)
+    PendingIntent mActionIntent;
+    @ParcelField(5)
+    boolean mEnabled;
+    @ParcelField(6)
+    boolean mShouldShowIcon;
 
     public RemoteActionCompat(@NonNull IconCompat icon, @NonNull CharSequence title,
             @NonNull CharSequence contentDescription, @NonNull PendingIntent intent) {
@@ -59,6 +60,11 @@
     }
 
     /**
+     * Used for VersionedParcelable.
+     */
+    RemoteActionCompat() {}
+
+    /**
      * Constructs a {@link RemoteActionCompat} using data from {@code other}.
      */
     public RemoteActionCompat(@NonNull RemoteActionCompat other) {
@@ -160,35 +166,4 @@
         }
         return action;
     }
-
-    /**
-     * Converts this into a Bundle that can be converted back to a {@link RemoteActionCompat}
-     * by calling {@link #createFromBundle(Bundle)}.
-     */
-    @NonNull
-    public Bundle toBundle() {
-        Bundle bundle = new Bundle();
-        bundle.putBundle(EXTRA_ICON, mIcon.toBundle());
-        bundle.putCharSequence(EXTRA_TITLE, mTitle);
-        bundle.putCharSequence(EXTRA_CONTENT_DESCRIPTION, mContentDescription);
-        bundle.putParcelable(EXTRA_ACTION_INTENT, mActionIntent);
-        bundle.putBoolean(EXTRA_ENABLED, mEnabled);
-        bundle.putBoolean(EXTRA_SHOULD_SHOW_ICON, mShouldShowIcon);
-        return bundle;
-    }
-
-    /**
-     * Converts the bundle created by {@link #toBundle()} back to {@link RemoteActionCompat}.
-     */
-    @NonNull
-    public static RemoteActionCompat createFromBundle(@NonNull Bundle bundle) {
-        RemoteActionCompat action = new RemoteActionCompat(
-                IconCompat.createFromBundle(bundle.getBundle(EXTRA_ICON)),
-                bundle.getCharSequence(EXTRA_TITLE),
-                bundle.getCharSequence(EXTRA_CONTENT_DESCRIPTION),
-                bundle.<PendingIntent>getParcelable(EXTRA_ACTION_INTENT));
-        action.setEnabled(bundle.getBoolean(EXTRA_ENABLED));
-        action.setShouldShowIcon(bundle.getBoolean(EXTRA_SHOULD_SHOW_ICON));
-        return action;
-    }
 }
diff --git a/core/src/main/java/androidx/core/graphics/TypefaceCompatUtil.java b/core/src/main/java/androidx/core/graphics/TypefaceCompatUtil.java
index 1e6ca0e..8bf3e30 100644
--- a/core/src/main/java/androidx/core/graphics/TypefaceCompatUtil.java
+++ b/core/src/main/java/androidx/core/graphics/TypefaceCompatUtil.java
@@ -60,9 +60,14 @@
      */
     @Nullable
     public static File getTempFile(Context context) {
+        File cacheDir = context.getCacheDir();
+        if (cacheDir == null) {
+            return null;
+        }
+
         final String prefix = CACHE_FILE_PREFIX + Process.myPid() + "-" + Process.myTid() + "-";
         for (int i = 0; i < 100; ++i) {
-            final File file = new File(context.getCacheDir(), prefix + i);
+            final File file = new File(cacheDir, prefix + i);
             try {
                 if (file.createNewFile()) {
                     return file;
diff --git a/fragment/api/1.1.0-alpha06.txt b/fragment/api/1.1.0-alpha06.txt
index e586a2f..1c78cb8 100644
--- a/fragment/api/1.1.0-alpha06.txt
+++ b/fragment/api/1.1.0-alpha06.txt
@@ -331,14 +331,13 @@
     method public boolean isViewFromObject(android.view.View, Object);
   }
 
-  public class FragmentTabHost extends android.widget.TabHost implements android.widget.TabHost.OnTabChangeListener {
-    ctor public FragmentTabHost(android.content.Context);
-    ctor public FragmentTabHost(android.content.Context, android.util.AttributeSet?);
-    method public void addTab(android.widget.TabHost.TabSpec, Class<?>, android.os.Bundle?);
-    method public void onTabChanged(String?);
-    method @Deprecated public void setup();
-    method public void setup(android.content.Context, androidx.fragment.app.FragmentManager);
-    method public void setup(android.content.Context, androidx.fragment.app.FragmentManager, int);
+  @Deprecated public class FragmentTabHost extends android.widget.TabHost implements android.widget.TabHost.OnTabChangeListener {
+    ctor @Deprecated public FragmentTabHost(android.content.Context);
+    ctor @Deprecated public FragmentTabHost(android.content.Context, android.util.AttributeSet?);
+    method @Deprecated public void addTab(android.widget.TabHost.TabSpec, Class<?>, android.os.Bundle?);
+    method @Deprecated public void onTabChanged(String?);
+    method @Deprecated public void setup(android.content.Context, androidx.fragment.app.FragmentManager);
+    method @Deprecated public void setup(android.content.Context, androidx.fragment.app.FragmentManager, int);
   }
 
   public abstract class FragmentTransaction {
diff --git a/fragment/api/current.txt b/fragment/api/current.txt
index e586a2f..1c78cb8 100644
--- a/fragment/api/current.txt
+++ b/fragment/api/current.txt
@@ -331,14 +331,13 @@
     method public boolean isViewFromObject(android.view.View, Object);
   }
 
-  public class FragmentTabHost extends android.widget.TabHost implements android.widget.TabHost.OnTabChangeListener {
-    ctor public FragmentTabHost(android.content.Context);
-    ctor public FragmentTabHost(android.content.Context, android.util.AttributeSet?);
-    method public void addTab(android.widget.TabHost.TabSpec, Class<?>, android.os.Bundle?);
-    method public void onTabChanged(String?);
-    method @Deprecated public void setup();
-    method public void setup(android.content.Context, androidx.fragment.app.FragmentManager);
-    method public void setup(android.content.Context, androidx.fragment.app.FragmentManager, int);
+  @Deprecated public class FragmentTabHost extends android.widget.TabHost implements android.widget.TabHost.OnTabChangeListener {
+    ctor @Deprecated public FragmentTabHost(android.content.Context);
+    ctor @Deprecated public FragmentTabHost(android.content.Context, android.util.AttributeSet?);
+    method @Deprecated public void addTab(android.widget.TabHost.TabSpec, Class<?>, android.os.Bundle?);
+    method @Deprecated public void onTabChanged(String?);
+    method @Deprecated public void setup(android.content.Context, androidx.fragment.app.FragmentManager);
+    method @Deprecated public void setup(android.content.Context, androidx.fragment.app.FragmentManager, int);
   }
 
   public abstract class FragmentTransaction {
diff --git a/fragment/src/androidTest/java/androidx/fragment/app/CountCallsFragment.java b/fragment/src/androidTest/java/androidx/fragment/app/CountCallsFragment.java
deleted file mode 100644
index 900ec9d..0000000
--- a/fragment/src/androidTest/java/androidx/fragment/app/CountCallsFragment.java
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * Copyright 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package androidx.fragment.app;
-
-import android.content.Context;
-import android.os.Bundle;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-
-/**
- * Counts the number of onCreateView, onHiddenChanged (onHide, onShow), onAttach, and onDetach
- * calls.
- */
-public class CountCallsFragment extends StrictViewFragment {
-    public int onCreateViewCount = 0;
-    public int onDestroyViewCount = 0;
-    public int onHideCount = 0;
-    public int onShowCount = 0;
-    public int onAttachCount = 0;
-    public int onDetachCount = 0;
-
-    @Override
-    public View onCreateView(LayoutInflater inflater, ViewGroup container,
-            Bundle savedInstanceState) {
-        onCreateViewCount++;
-        return super.onCreateView(inflater, container, savedInstanceState);
-    }
-
-    @Override
-    public void onHiddenChanged(boolean hidden) {
-        if (hidden) {
-            onHideCount++;
-        } else {
-            onShowCount++;
-        }
-        super.onHiddenChanged(hidden);
-    }
-
-    @Override
-    public void onAttach(Context context) {
-        onAttachCount++;
-        super.onAttach(context);
-    }
-
-    @Override
-    public void onDetach() {
-        onDetachCount++;
-        super.onDetach();
-    }
-
-    @Override
-    public void onDestroyView() {
-        onDestroyViewCount++;
-        super.onDestroyView();
-    }
-}
diff --git a/fragment/src/androidTest/java/androidx/fragment/app/CountCallsFragment.kt b/fragment/src/androidTest/java/androidx/fragment/app/CountCallsFragment.kt
new file mode 100644
index 0000000..e1e3378
--- /dev/null
+++ b/fragment/src/androidTest/java/androidx/fragment/app/CountCallsFragment.kt
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package androidx.fragment.app
+
+import android.content.Context
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.annotation.LayoutRes
+import androidx.fragment.test.R
+
+/**
+ * Counts the number of onCreateView, onHiddenChanged (onHide, onShow), onAttach, and onDetach
+ * calls.
+ */
+class CountCallsFragment(
+    @LayoutRes contentLayoutId: Int = R.layout.strict_view_fragment
+) : StrictViewFragment(contentLayoutId) {
+    var onCreateViewCount = 0
+    var onDestroyViewCount = 0
+    var onHideCount = 0
+    var onShowCount = 0
+    var onAttachCount = 0
+    var onDetachCount = 0
+
+    override fun onCreateView(
+        inflater: LayoutInflater,
+        container: ViewGroup?,
+        savedInstanceState: Bundle?
+    ): View? {
+        onCreateViewCount++
+        return super.onCreateView(inflater, container, savedInstanceState)
+    }
+
+    override fun onHiddenChanged(hidden: Boolean) {
+        if (hidden) {
+            onHideCount++
+        } else {
+            onShowCount++
+        }
+        super.onHiddenChanged(hidden)
+    }
+
+    override fun onAttach(context: Context) {
+        onAttachCount++
+        super.onAttach(context)
+    }
+
+    override fun onDetach() {
+        onDetachCount++
+        super.onDetach()
+    }
+
+    override fun onDestroyView() {
+        onDestroyViewCount++
+        super.onDestroyView()
+    }
+}
diff --git a/fragment/src/androidTest/java/androidx/fragment/app/FragmentAnimationTest.kt b/fragment/src/androidTest/java/androidx/fragment/app/FragmentAnimationTest.kt
index e4a3be9..300fad6 100644
--- a/fragment/src/androidTest/java/androidx/fragment/app/FragmentAnimationTest.kt
+++ b/fragment/src/androidTest/java/androidx/fragment/app/FragmentAnimationTest.kt
@@ -355,8 +355,7 @@
 
         val fm1 = fc1.supportFragmentManager
 
-        val fragment1 = StrictViewFragment()
-        fragment1.setLayoutId(R.layout.scene1)
+        val fragment1 = StrictViewFragment(R.layout.scene1)
         fm1.beginTransaction()
             .add(R.id.fragmentContainer, fragment1, "1")
             .commit()
@@ -596,7 +595,7 @@
 
     @Throws(InterruptedException::class)
     private fun assertPostponed(fragment: AnimatorFragment, expectedAnimators: Int) {
-        assertThat(fragment.mOnCreateViewCalled).isTrue()
+        assertThat(fragment.onCreateViewCalled).isTrue()
         assertThat(fragment.requireView().visibility).isEqualTo(View.VISIBLE)
         assertThat(fragment.requireView().alpha).isWithin(0f).of(0f)
         assertThat(fragment.numAnimators).isEqualTo(expectedAnimators)
diff --git a/fragment/src/androidTest/java/androidx/fragment/app/FragmentAnimatorTest.kt b/fragment/src/androidTest/java/androidx/fragment/app/FragmentAnimatorTest.kt
index b96e10b..b71b846 100644
--- a/fragment/src/androidTest/java/androidx/fragment/app/FragmentAnimatorTest.kt
+++ b/fragment/src/androidTest/java/androidx/fragment/app/FragmentAnimatorTest.kt
@@ -415,8 +415,7 @@
 
         val fm1 = fc1.supportFragmentManager
 
-        val fragment1 = StrictViewFragment()
-        fragment1.setLayoutId(R.layout.scene1)
+        val fragment1 = StrictViewFragment(R.layout.scene1)
         fm1.beginTransaction()
             .add(R.id.fragmentContainer, fragment1, "1")
             .setReorderingAllowed(true)
@@ -509,7 +508,7 @@
     }
 
     private fun assertPostponed(fragment: AnimatorFragment, expectedAnimators: Int) {
-        assertThat(fragment.mOnCreateViewCalled).isTrue()
+        assertThat(fragment.onCreateViewCalled).isTrue()
         assertThat(fragment.requireView().visibility).isEqualTo(View.VISIBLE)
         assertThat(fragment.requireView().alpha).isWithin(0f).of(0f)
         assertThat(fragment.numAnimators).isEqualTo(expectedAnimators)
diff --git a/fragment/src/androidTest/java/androidx/fragment/app/FragmentLifecycleTest.java b/fragment/src/androidTest/java/androidx/fragment/app/FragmentLifecycleTest.java
deleted file mode 100644
index a52ef71..0000000
--- a/fragment/src/androidTest/java/androidx/fragment/app/FragmentLifecycleTest.java
+++ /dev/null
@@ -1,2038 +0,0 @@
-/*
- * Copyright 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-
-package androidx.fragment.app;
-
-import static androidx.fragment.app.FragmentTestUtil.HostCallbacks;
-import static androidx.fragment.app.FragmentTestUtil.restartFragmentController;
-import static androidx.fragment.app.FragmentTestUtil.shutdownFragmentController;
-import static androidx.fragment.app.FragmentTestUtil.startupFragmentController;
-
-import static com.google.common.truth.Truth.assertThat;
-import static com.google.common.truth.Truth.assertWithMessage;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNotSame;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertSame;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-
-import android.content.Context;
-import android.content.Intent;
-import android.os.Build;
-import android.os.Bundle;
-import android.os.Parcelable;
-import android.util.AttributeSet;
-import android.util.Pair;
-import android.view.LayoutInflater;
-import android.view.Menu;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.TextView;
-
-import androidx.annotation.ContentView;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.core.view.ViewCompat;
-import androidx.fragment.app.test.EmptyFragmentTestActivity;
-import androidx.fragment.app.test.FragmentTestActivity;
-import androidx.fragment.test.R;
-import androidx.lifecycle.Lifecycle;
-import androidx.lifecycle.ViewModelStore;
-import androidx.test.annotation.UiThreadTest;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.MediumTest;
-import androidx.test.filters.SdkSuppress;
-import androidx.test.platform.app.InstrumentationRegistry;
-import androidx.test.rule.ActivityTestRule;
-
-import org.junit.Assert;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.concurrent.TimeUnit;
-
-@RunWith(AndroidJUnit4.class)
-@MediumTest
-public class FragmentLifecycleTest {
-
-    @Rule
-    public ActivityTestRule<EmptyFragmentTestActivity> mActivityRule =
-            new ActivityTestRule<EmptyFragmentTestActivity>(EmptyFragmentTestActivity.class);
-
-    @Test
-    public void basicLifecycle() throws Throwable {
-        final FragmentManager fm = mActivityRule.getActivity().getSupportFragmentManager();
-        final StrictFragment strictFragment = new StrictFragment();
-
-        // Add fragment; StrictFragment will throw if it detects any violation
-        // in standard lifecycle method ordering or expected preconditions.
-        fm.beginTransaction().add(strictFragment, "EmptyHeadless").commit();
-        executePendingTransactions(fm);
-
-        assertTrue("fragment is not added", strictFragment.isAdded());
-        assertFalse("fragment is detached", strictFragment.isDetached());
-        assertTrue("fragment is not resumed", strictFragment.isResumed());
-        Lifecycle lifecycle = strictFragment.getLifecycle();
-        assertThat(lifecycle.getCurrentState())
-                .isEqualTo(Lifecycle.State.RESUMED);
-
-        // Test removal as well; StrictFragment will throw here too.
-        fm.beginTransaction().remove(strictFragment).commit();
-        executePendingTransactions(fm);
-
-        assertFalse("fragment is added", strictFragment.isAdded());
-        assertFalse("fragment is resumed", strictFragment.isResumed());
-        assertThat(lifecycle.getCurrentState())
-                .isEqualTo(Lifecycle.State.DESTROYED);
-        // Once removed, a new Lifecycle should be created just in case
-        // the developer reuses the same Fragment
-        assertThat(strictFragment.getLifecycle().getCurrentState())
-                .isEqualTo(Lifecycle.State.INITIALIZED);
-
-        // This one is perhaps counterintuitive; "detached" means specifically detached
-        // but still managed by a FragmentManager. The .remove call above
-        // should not enter this state.
-        assertFalse("fragment is detached", strictFragment.isDetached());
-    }
-
-    @Test
-    public void detachment() throws Throwable {
-        final FragmentManager fm = mActivityRule.getActivity().getSupportFragmentManager();
-        final StrictFragment f1 = new StrictFragment();
-        final StrictFragment f2 = new StrictFragment();
-
-        fm.beginTransaction().add(f1, "1").add(f2, "2").commit();
-        executePendingTransactions(fm);
-
-        assertTrue("fragment 1 is not added", f1.isAdded());
-        assertTrue("fragment 2 is not added", f2.isAdded());
-
-        // Test detaching fragments using StrictFragment to throw on errors.
-        fm.beginTransaction().detach(f1).detach(f2).commit();
-        executePendingTransactions(fm);
-
-        assertTrue("fragment 1 is not detached", f1.isDetached());
-        assertTrue("fragment 2 is not detached", f2.isDetached());
-        assertFalse("fragment 1 is added", f1.isAdded());
-        assertFalse("fragment 2 is added", f2.isAdded());
-
-        // Only reattach f1; leave v2 detached.
-        fm.beginTransaction().attach(f1).commit();
-        executePendingTransactions(fm);
-
-        assertTrue("fragment 1 is not added", f1.isAdded());
-        assertFalse("fragment 1 is detached", f1.isDetached());
-        assertTrue("fragment 2 is not detached", f2.isDetached());
-
-        // Remove both from the FragmentManager.
-        fm.beginTransaction().remove(f1).remove(f2).commit();
-        executePendingTransactions(fm);
-
-        assertFalse("fragment 1 is added", f1.isAdded());
-        assertFalse("fragment 2 is added", f2.isAdded());
-        assertFalse("fragment 1 is detached", f1.isDetached());
-        assertFalse("fragment 2 is detached", f2.isDetached());
-    }
-
-    @Test
-    public void basicBackStack() throws Throwable {
-        final FragmentManager fm = mActivityRule.getActivity().getSupportFragmentManager();
-        final StrictFragment f1 = new StrictFragment();
-        final StrictFragment f2 = new StrictFragment();
-
-        // Add a fragment normally to set up
-        fm.beginTransaction().add(f1, "1").commit();
-        executePendingTransactions(fm);
-
-        assertTrue("fragment 1 is not added", f1.isAdded());
-
-        // Remove the first one and add a second. We're not using replace() here since
-        // these fragments are headless and as of this test writing, replace() only works
-        // for fragments with views and a container view id.
-        // Add it to the back stack so we can pop it afterwards.
-        fm.beginTransaction().remove(f1).add(f2, "2").addToBackStack("stack1").commit();
-        executePendingTransactions(fm);
-
-        assertFalse("fragment 1 is added", f1.isAdded());
-        assertTrue("fragment 2 is not added", f2.isAdded());
-
-        // Test popping the stack
-        fm.popBackStack();
-        executePendingTransactions(fm);
-
-        assertFalse("fragment 2 is added", f2.isAdded());
-        assertTrue("fragment 1 is not added", f1.isAdded());
-    }
-
-    @Test
-    public void attachBackStack() throws Throwable {
-        final FragmentManager fm = mActivityRule.getActivity().getSupportFragmentManager();
-        final StrictFragment f1 = new StrictFragment();
-        final StrictFragment f2 = new StrictFragment();
-
-        // Add a fragment normally to set up
-        fm.beginTransaction().add(f1, "1").commit();
-        executePendingTransactions(fm);
-
-        assertTrue("fragment 1 is not added", f1.isAdded());
-
-        fm.beginTransaction().detach(f1).add(f2, "2").addToBackStack("stack1").commit();
-        executePendingTransactions(fm);
-
-        assertTrue("fragment 1 is not detached", f1.isDetached());
-        assertFalse("fragment 2 is detached", f2.isDetached());
-        assertFalse("fragment 1 is added", f1.isAdded());
-        assertTrue("fragment 2 is not added", f2.isAdded());
-    }
-
-    @Test
-    public void viewLifecycle() throws Throwable {
-        // Test basic lifecycle when the fragment creates a view
-
-        final FragmentManager fm = mActivityRule.getActivity().getSupportFragmentManager();
-        final StrictViewFragment f1 = new StrictViewFragment();
-
-        fm.beginTransaction().add(android.R.id.content, f1).commit();
-        executePendingTransactions(fm);
-
-        assertTrue("fragment 1 is not added", f1.isAdded());
-        final View view = f1.getView();
-        assertNotNull("fragment 1 returned null from getView", view);
-        assertTrue("fragment 1's view is not attached to a window",
-                ViewCompat.isAttachedToWindow(view));
-
-        fm.beginTransaction().remove(f1).commit();
-        executePendingTransactions(fm);
-
-        assertFalse("fragment 1 is added", f1.isAdded());
-        assertNull("fragment 1 returned non-null from getView after removal", f1.getView());
-        assertFalse("fragment 1's previous view is still attached to a window",
-                ViewCompat.isAttachedToWindow(view));
-    }
-
-    @Test
-    public void viewReplace() throws Throwable {
-        // Replace one view with another, then reverse it with the back stack
-
-        final FragmentManager fm = mActivityRule.getActivity().getSupportFragmentManager();
-        final StrictViewFragment f1 = new StrictViewFragment();
-        final StrictViewFragment f2 = new StrictViewFragment();
-
-        fm.beginTransaction().add(android.R.id.content, f1).commit();
-        executePendingTransactions(fm);
-
-        assertTrue("fragment 1 is not added", f1.isAdded());
-
-        View origView1 = f1.getView();
-        assertNotNull("fragment 1 returned null view", origView1);
-        assertTrue("fragment 1's view not attached", ViewCompat.isAttachedToWindow(origView1));
-
-        fm.beginTransaction().replace(android.R.id.content, f2).addToBackStack("stack1").commit();
-        executePendingTransactions(fm);
-
-        assertFalse("fragment 1 is added", f1.isAdded());
-        assertTrue("fragment 2 is added", f2.isAdded());
-        assertNull("fragment 1 returned non-null view", f1.getView());
-        assertFalse("fragment 1's old view still attached",
-                ViewCompat.isAttachedToWindow(origView1));
-        View origView2 = f2.getView();
-        assertNotNull("fragment 2 returned null view", origView2);
-        assertTrue("fragment 2's view not attached", ViewCompat.isAttachedToWindow(origView2));
-
-        fm.popBackStack();
-        executePendingTransactions(fm);
-
-        assertTrue("fragment 1 is not added", f1.isAdded());
-        assertFalse("fragment 2 is added", f2.isAdded());
-        assertNull("fragment 2 returned non-null view", f2.getView());
-        assertFalse("fragment 2's view still attached", ViewCompat.isAttachedToWindow(origView2));
-        View newView1 = f1.getView();
-        assertNotSame("fragment 1 had same view from last attachment", origView1, newView1);
-        assertTrue("fragment 1's view not attached", ViewCompat.isAttachedToWindow(newView1));
-    }
-
-    @Test
-    @UiThreadTest
-    public void setInitialSavedState() throws Throwable {
-        FragmentManager fm = mActivityRule.getActivity().getSupportFragmentManager();
-
-        // Add a StateSaveFragment
-        StateSaveFragment fragment = new StateSaveFragment("Saved", "");
-        fm.beginTransaction().add(fragment, "tag").commit();
-        executePendingTransactions(fm);
-
-        // Change the user visible hint before we save state
-        fragment.setUserVisibleHint(false);
-
-        // Save its state and remove it
-        Fragment.SavedState state = fm.saveFragmentInstanceState(fragment);
-        fm.beginTransaction().remove(fragment).commit();
-        executePendingTransactions(fm);
-
-        // Create a new instance, calling setInitialSavedState
-        fragment = new StateSaveFragment("", "");
-        fragment.setInitialSavedState(state);
-
-        // Add the new instance
-        fm.beginTransaction().add(fragment, "tag").commit();
-        executePendingTransactions(fm);
-
-        assertEquals("setInitialSavedState did not restore saved state",
-                "Saved", fragment.getSavedState());
-        assertEquals("setInitialSavedState did not restore user visible hint",
-                false, fragment.getUserVisibleHint());
-    }
-
-    @Test
-    @UiThreadTest
-    public void setInitialSavedStateWithSetUserVisibleHint() throws Throwable {
-        FragmentManager fm = mActivityRule.getActivity().getSupportFragmentManager();
-
-        // Add a StateSaveFragment
-        StateSaveFragment fragment = new StateSaveFragment("Saved", "");
-        fm.beginTransaction().add(fragment, "tag").commit();
-        executePendingTransactions(fm);
-
-        // Save its state and remove it
-        Fragment.SavedState state = fm.saveFragmentInstanceState(fragment);
-        fm.beginTransaction().remove(fragment).commit();
-        executePendingTransactions(fm);
-
-        // Create a new instance, calling setInitialSavedState
-        fragment = new StateSaveFragment("", "");
-        fragment.setInitialSavedState(state);
-
-        // Change the user visible hint after we call setInitialSavedState
-        fragment.setUserVisibleHint(false);
-
-        // Add the new instance
-        fm.beginTransaction().add(fragment, "tag").commit();
-        executePendingTransactions(fm);
-
-        assertEquals("setInitialSavedState did not restore saved state",
-                "Saved", fragment.getSavedState());
-        assertEquals("setUserVisibleHint should override setInitialSavedState",
-                false, fragment.getUserVisibleHint());
-    }
-
-    @Test
-    @UiThreadTest
-    public void testSavedInstanceStateAfterRestore() {
-
-        final ViewModelStore viewModelStore = new ViewModelStore();
-        final FragmentController fc1 =
-                startupFragmentController(mActivityRule.getActivity(), null, viewModelStore);
-        final FragmentManager fm1 = fc1.getSupportFragmentManager();
-
-        // Add the initial state
-        final StrictFragment parentFragment = new StrictFragment();
-        parentFragment.setRetainInstance(true);
-        final StrictFragment childFragment = new StrictFragment();
-        fm1.beginTransaction().add(parentFragment, "parent").commitNow();
-        final FragmentManager childFragmentManager = parentFragment.getChildFragmentManager();
-        childFragmentManager.beginTransaction().add(childFragment, "child").commitNow();
-
-        // Confirm the initial state
-        assertWithMessage("Initial parent saved instance state should be null")
-                .that(parentFragment.mSavedInstanceState)
-                .isNull();
-        assertWithMessage("Initial child saved instance state should be null")
-                .that(childFragment.mSavedInstanceState)
-                .isNull();
-
-        // Bring the state back down to destroyed, simulating an activity restart
-        fc1.dispatchPause();
-        final Parcelable savedState = fc1.saveAllState();
-        fc1.dispatchStop();
-        fc1.dispatchDestroy();
-
-        // Create the new controller and restore state
-        final FragmentController fc2 =
-                startupFragmentController(mActivityRule.getActivity(), savedState, viewModelStore);
-        final FragmentManager fm2 = fc2.getSupportFragmentManager();
-
-        final StrictFragment restoredParentFragment = (StrictFragment) fm2
-                .findFragmentByTag("parent");
-        assertNotNull("Parent fragment was not restored", restoredParentFragment);
-        final StrictFragment restoredChildFragment = (StrictFragment) restoredParentFragment
-                .getChildFragmentManager().findFragmentByTag("child");
-        assertNotNull("Child fragment was not restored", restoredChildFragment);
-
-        assertWithMessage("Parent fragment saved instance state should still be null "
-                + "since it is a retained Fragment")
-                .that(restoredParentFragment.mSavedInstanceState)
-                .isNull();
-        assertWithMessage("Child fragment saved instance state should be non-null")
-                .that(restoredChildFragment.mSavedInstanceState)
-                .isNotNull();
-
-        // Bring the state back down to destroyed before we finish the test
-        shutdownFragmentController(fc2, viewModelStore);
-    }
-
-    @Test
-    @UiThreadTest
-    public void restoreNestedFragmentsOnBackStack() {
-
-        final ViewModelStore viewModelStore = new ViewModelStore();
-        final FragmentController fc1 = FragmentController.createController(
-                new HostCallbacks(mActivityRule.getActivity(), viewModelStore));
-
-        final FragmentManager fm1 = fc1.getSupportFragmentManager();
-
-        fc1.attachHost(null);
-        fc1.dispatchCreate();
-
-        // Add the initial state
-        final StrictFragment parentFragment = new StrictFragment();
-        final StrictFragment childFragment = new StrictFragment();
-        fm1.beginTransaction().add(parentFragment, "parent").commitNow();
-        final FragmentManager childFragmentManager = parentFragment.getChildFragmentManager();
-        childFragmentManager.beginTransaction().add(childFragment, "child").commitNow();
-
-        // Now add a Fragment to the back stack
-        final StrictFragment replacementChildFragment = new StrictFragment();
-        childFragmentManager.beginTransaction()
-                .remove(childFragment)
-                .add(replacementChildFragment, "child")
-                .addToBackStack("back_stack").commit();
-        childFragmentManager.executePendingTransactions();
-
-        // Move the activity to resumed
-        fc1.dispatchActivityCreated();
-        fc1.noteStateNotSaved();
-        fc1.execPendingActions();
-        fc1.dispatchStart();
-        fc1.dispatchResume();
-        fc1.execPendingActions();
-
-        // Now bring the state back down
-        fc1.dispatchPause();
-        final Parcelable savedState = fc1.saveAllState();
-        fc1.dispatchStop();
-        fc1.dispatchDestroy();
-
-        // Create the new controller and restore state
-        final FragmentController fc2 = FragmentController.createController(
-                new HostCallbacks(mActivityRule.getActivity(), viewModelStore));
-
-        final FragmentManager fm2 = fc2.getSupportFragmentManager();
-
-        fc2.attachHost(null);
-        fc2.restoreSaveState(savedState);
-        fc2.dispatchCreate();
-
-        final StrictFragment restoredParentFragment = (StrictFragment) fm2
-                .findFragmentByTag("parent");
-        assertNotNull("Parent fragment was not restored", restoredParentFragment);
-        final StrictFragment restoredChildFragment = (StrictFragment) restoredParentFragment
-                .getChildFragmentManager().findFragmentByTag("child");
-        assertNotNull("Child fragment was not restored", restoredChildFragment);
-
-        fc2.dispatchActivityCreated();
-        fc2.noteStateNotSaved();
-        fc2.execPendingActions();
-        fc2.dispatchStart();
-        fc2.dispatchResume();
-        fc2.execPendingActions();
-
-        // Bring the state back down to destroyed before we finish the test
-        shutdownFragmentController(fc2, viewModelStore);
-    }
-
-    @Test
-    @UiThreadTest
-    public void restoreRetainedInstanceFragments() throws Throwable {
-        // Create a new FragmentManager in isolation, nest some assorted fragments
-        // and then restore them to a second new FragmentManager.
-
-        final ViewModelStore viewModelStore = new ViewModelStore();
-        final FragmentController fc1 = FragmentController.createController(
-                new HostCallbacks(mActivityRule.getActivity(), viewModelStore));
-
-        final FragmentManager fm1 = fc1.getSupportFragmentManager();
-
-        fc1.attachHost(null);
-        fc1.dispatchCreate();
-
-        // Configure fragments.
-
-        // This retained fragment will be added, then removed. After being removed, it
-        // should no longer be retained by the FragmentManager
-        final StateSaveFragment removedFragment = new StateSaveFragment("Removed",
-                "UnsavedRemoved");
-        removedFragment.setRetainInstance(true);
-        fm1.beginTransaction().add(removedFragment, "tag:removed").commitNow();
-        fm1.beginTransaction().remove(removedFragment).commitNow();
-
-        // This retained fragment will be added, then detached. After being detached, it
-        // should continue to be retained by the FragmentManager
-        final StateSaveFragment detachedFragment = new StateSaveFragment("Detached",
-                "UnsavedDetached");
-        removedFragment.setRetainInstance(true);
-        fm1.beginTransaction().add(detachedFragment, "tag:detached").commitNow();
-        fm1.beginTransaction().detach(detachedFragment).commitNow();
-
-        // Grandparent fragment will not retain instance
-        final StateSaveFragment grandparentFragment = new StateSaveFragment("Grandparent",
-                "UnsavedGrandparent");
-        assertNotNull("grandparent fragment saved state not initialized",
-                grandparentFragment.getSavedState());
-        assertNotNull("grandparent fragment unsaved state not initialized",
-                grandparentFragment.getUnsavedState());
-        fm1.beginTransaction().add(grandparentFragment, "tag:grandparent").commitNow();
-
-        // Parent fragment will retain instance
-        final StateSaveFragment parentFragment = new StateSaveFragment("Parent", "UnsavedParent");
-        assertNotNull("parent fragment saved state not initialized",
-                parentFragment.getSavedState());
-        assertNotNull("parent fragment unsaved state not initialized",
-                parentFragment.getUnsavedState());
-        parentFragment.setRetainInstance(true);
-        grandparentFragment.getChildFragmentManager().beginTransaction()
-                .add(parentFragment, "tag:parent").commitNow();
-        assertSame("parent fragment is not a child of grandparent",
-                grandparentFragment, parentFragment.getParentFragment());
-
-        // Child fragment will not retain instance
-        final StateSaveFragment childFragment = new StateSaveFragment("Child", "UnsavedChild");
-        assertNotNull("child fragment saved state not initialized",
-                childFragment.getSavedState());
-        assertNotNull("child fragment unsaved state not initialized",
-                childFragment.getUnsavedState());
-        parentFragment.getChildFragmentManager().beginTransaction()
-                .add(childFragment, "tag:child").commitNow();
-        assertSame("child fragment is not a child of grandpanret",
-                parentFragment, childFragment.getParentFragment());
-
-        // Saved for comparison later
-        final FragmentManager parentChildFragmentManager = parentFragment.getChildFragmentManager();
-
-        fc1.dispatchActivityCreated();
-        fc1.noteStateNotSaved();
-        fc1.execPendingActions();
-        fc1.dispatchStart();
-        fc1.dispatchResume();
-        fc1.execPendingActions();
-
-        // Bring the state back down to destroyed, simulating an activity restart
-        fc1.dispatchPause();
-        final Parcelable savedState = fc1.saveAllState();
-        fc1.dispatchStop();
-        fc1.dispatchDestroy();
-
-        // Create the new controller and restore state
-        final FragmentController fc2 = FragmentController.createController(
-                new HostCallbacks(mActivityRule.getActivity(), viewModelStore));
-
-        final FragmentManager fm2 = fc2.getSupportFragmentManager();
-
-        fc2.attachHost(null);
-        fc2.restoreSaveState(savedState);
-        fc2.dispatchCreate();
-
-        // Confirm that the restored fragments are available and in the expected states
-        final StateSaveFragment restoredRemovedFragment = (StateSaveFragment)
-                fm2.findFragmentByTag("tag:removed");
-        assertNull(restoredRemovedFragment);
-        assertTrue("Removed Fragment should be destroyed", removedFragment.mCalledOnDestroy);
-
-        final StateSaveFragment restoredDetachedFragment = (StateSaveFragment)
-                fm2.findFragmentByTag("tag:detached");
-        assertNotNull(restoredDetachedFragment);
-
-        final StateSaveFragment restoredGrandparent = (StateSaveFragment) fm2.findFragmentByTag(
-                "tag:grandparent");
-        assertNotNull("grandparent fragment not restored", restoredGrandparent);
-
-        assertNotSame("grandparent fragment instance was saved",
-                grandparentFragment, restoredGrandparent);
-        assertEquals("grandparent fragment saved state was not equal",
-                grandparentFragment.getSavedState(), restoredGrandparent.getSavedState());
-        assertNotEquals("grandparent fragment unsaved state was unexpectedly preserved",
-                grandparentFragment.getUnsavedState(), restoredGrandparent.getUnsavedState());
-
-        final StateSaveFragment restoredParent = (StateSaveFragment) restoredGrandparent
-                .getChildFragmentManager().findFragmentByTag("tag:parent");
-        assertNotNull("parent fragment not restored", restoredParent);
-
-        assertSame("parent fragment instance was not saved", parentFragment, restoredParent);
-        assertEquals("parent fragment saved state was not equal",
-                parentFragment.getSavedState(), restoredParent.getSavedState());
-        assertEquals("parent fragment unsaved state was not equal",
-                parentFragment.getUnsavedState(), restoredParent.getUnsavedState());
-        assertNotSame("parent fragment has the same child FragmentManager",
-                parentChildFragmentManager, restoredParent.getChildFragmentManager());
-
-        final StateSaveFragment restoredChild = (StateSaveFragment) restoredParent
-                .getChildFragmentManager().findFragmentByTag("tag:child");
-        assertNotNull("child fragment not restored", restoredChild);
-
-        assertNotSame("child fragment instance state was saved", childFragment, restoredChild);
-        assertEquals("child fragment saved state was not equal",
-                childFragment.getSavedState(), restoredChild.getSavedState());
-        assertNotEquals("child fragment saved state was unexpectedly equal",
-                childFragment.getUnsavedState(), restoredChild.getUnsavedState());
-
-        fc2.dispatchActivityCreated();
-        fc2.noteStateNotSaved();
-        fc2.execPendingActions();
-        fc2.dispatchStart();
-        fc2.dispatchResume();
-        fc2.execPendingActions();
-
-        // Test that the fragments are in the configuration we expect
-
-        // Bring the state back down to destroyed before we finish the test
-        shutdownFragmentController(fc2, viewModelStore);
-
-        assertTrue("grandparent not destroyed", restoredGrandparent.mCalledOnDestroy);
-        assertTrue("parent not destroyed", restoredParent.mCalledOnDestroy);
-        assertTrue("child not destroyed", restoredChild.mCalledOnDestroy);
-    }
-
-    @Test
-    @UiThreadTest
-    public void restoreRetainedInstanceFragmentWithTransparentActivityConfigChange() {
-        // Create a new FragmentManager in isolation, add a retained instance Fragment,
-        // then mimic the following scenario:
-        // 1. Activity A adds retained Fragment F
-        // 2. Activity A starts translucent Activity B
-        // 3. Activity B start opaque Activity C
-        // 4. Rotate phone
-        // 5. Finish Activity C
-        // 6. Finish Activity B
-
-        final ViewModelStore viewModelStore = new ViewModelStore();
-        final FragmentController fc1 = FragmentController.createController(
-                new HostCallbacks(mActivityRule.getActivity(), viewModelStore));
-
-        final FragmentManager fm1 = fc1.getSupportFragmentManager();
-
-        fc1.attachHost(null);
-        fc1.dispatchCreate();
-
-        // Add the retained Fragment
-        final StateSaveFragment retainedFragment = new StateSaveFragment("Retained",
-                "UnsavedRetained");
-        retainedFragment.setRetainInstance(true);
-        fm1.beginTransaction().add(retainedFragment, "tag:retained").commitNow();
-
-        // Move the activity to resumed
-        fc1.dispatchActivityCreated();
-        fc1.noteStateNotSaved();
-        fc1.execPendingActions();
-        fc1.dispatchStart();
-        fc1.dispatchResume();
-        fc1.execPendingActions();
-
-        // Launch the transparent activity on top
-        fc1.dispatchPause();
-
-        // Launch the opaque activity on top
-        final Parcelable savedState = fc1.saveAllState();
-        fc1.dispatchStop();
-
-        // Finish the opaque activity, making our Activity visible i.e., started
-        fc1.noteStateNotSaved();
-        fc1.execPendingActions();
-        fc1.dispatchStart();
-
-        // Finish the transparent activity, causing a config change
-        fc1.dispatchStop();
-        fc1.dispatchDestroy();
-
-        // Create the new controller and restore state
-        final FragmentController fc2 = FragmentController.createController(
-                new HostCallbacks(mActivityRule.getActivity(), viewModelStore));
-
-        final FragmentManager fm2 = fc2.getSupportFragmentManager();
-
-        fc2.attachHost(null);
-        fc2.restoreSaveState(savedState);
-        fc2.dispatchCreate();
-
-        final StateSaveFragment restoredFragment = (StateSaveFragment) fm2
-                .findFragmentByTag("tag:retained");
-        assertNotNull("retained fragment not restored", restoredFragment);
-        assertEquals("The retained Fragment shouldn't be recreated",
-                retainedFragment, restoredFragment);
-
-        fc2.dispatchActivityCreated();
-        fc2.noteStateNotSaved();
-        fc2.execPendingActions();
-        fc2.dispatchStart();
-        fc2.dispatchResume();
-        fc2.execPendingActions();
-
-        // Bring the state back down to destroyed before we finish the test
-        shutdownFragmentController(fc2, viewModelStore);
-    }
-
-    @Test
-    @UiThreadTest
-    public void saveAnimationState() throws Throwable {
-        ViewModelStore viewModelStore = new ViewModelStore();
-        FragmentController fc = startupFragmentController(mActivityRule.getActivity(), null,
-                viewModelStore);
-        FragmentManager fm = fc.getSupportFragmentManager();
-
-        fm.beginTransaction()
-                .setCustomAnimations(0, R.anim.fade_out, R.anim.fade_in, R.anim.fade_out)
-                .add(android.R.id.content, SimpleFragment.create(R.layout.fragment_a))
-                .addToBackStack(null)
-                .commit();
-        fm.executePendingTransactions();
-
-        assertAnimationsMatch(fm, 0, R.anim.fade_out, R.anim.fade_in, R.anim.fade_out);
-
-        // Causes save and restore of fragments and back stack
-        fc = restartFragmentController(mActivityRule.getActivity(), fc, viewModelStore);
-        fm = fc.getSupportFragmentManager();
-
-        assertAnimationsMatch(fm, 0, R.anim.fade_out, R.anim.fade_in, R.anim.fade_out);
-
-        fm.beginTransaction()
-                .setCustomAnimations(R.anim.fade_in, R.anim.fade_out, 0, 0)
-                .replace(android.R.id.content, SimpleFragment.create(R.layout.fragment_b))
-                .addToBackStack(null)
-                .commit();
-        fm.executePendingTransactions();
-
-        assertAnimationsMatch(fm, R.anim.fade_in, R.anim.fade_out, 0, 0);
-
-        // Causes save and restore of fragments and back stack
-        fc = restartFragmentController(mActivityRule.getActivity(), fc, viewModelStore);
-        fm = fc.getSupportFragmentManager();
-
-        assertAnimationsMatch(fm, R.anim.fade_in, R.anim.fade_out, 0, 0);
-
-        fm.popBackStackImmediate();
-
-        assertAnimationsMatch(fm, 0, R.anim.fade_out, R.anim.fade_in, R.anim.fade_out);
-
-        shutdownFragmentController(fc, viewModelStore);
-    }
-
-    /**
-     * This test confirms that as long as a parent fragment has called super.onCreate,
-     * any child fragments added, committed and with transactions executed will be brought
-     * to at least the CREATED state by the time the parent fragment receives onCreateView.
-     * This means the child fragment will have received onAttach/onCreate.
-     */
-    @Test
-    @UiThreadTest
-    public void childFragmentManagerAttach() throws Throwable {
-        final ViewModelStore viewModelStore = new ViewModelStore();
-        final FragmentController fc = FragmentController.createController(
-                new HostCallbacks(mActivityRule.getActivity(), viewModelStore));
-        fc.attachHost(null);
-        fc.dispatchCreate();
-
-        FragmentManager.FragmentLifecycleCallbacks
-                mockLc = mock(FragmentManager.FragmentLifecycleCallbacks.class);
-        FragmentManager.FragmentLifecycleCallbacks
-                mockRecursiveLc = mock(FragmentManager.FragmentLifecycleCallbacks.class);
-
-        FragmentManager fm = fc.getSupportFragmentManager();
-        fm.registerFragmentLifecycleCallbacks(mockLc, false);
-        fm.registerFragmentLifecycleCallbacks(mockRecursiveLc, true);
-
-        ChildFragmentManagerFragment fragment = new ChildFragmentManagerFragment();
-        fm.beginTransaction()
-                .add(android.R.id.content, fragment)
-                .commitNow();
-
-        verify(mockLc, times(1)).onFragmentCreated(fm, fragment, null);
-
-        fc.dispatchActivityCreated();
-
-        Fragment childFragment = fragment.getChildFragment();
-
-        verify(mockLc, times(1)).onFragmentActivityCreated(fm, fragment, null);
-        verify(mockRecursiveLc, times(1)).onFragmentActivityCreated(fm, fragment, null);
-        verify(mockRecursiveLc, times(1)).onFragmentActivityCreated(fm, childFragment, null);
-
-        fc.dispatchStart();
-
-        verify(mockLc, times(1)).onFragmentStarted(fm, fragment);
-        verify(mockRecursiveLc, times(1)).onFragmentStarted(fm, fragment);
-        verify(mockRecursiveLc, times(1)).onFragmentStarted(fm, childFragment);
-
-        fc.dispatchResume();
-
-        verify(mockLc, times(1)).onFragmentResumed(fm, fragment);
-        verify(mockRecursiveLc, times(1)).onFragmentResumed(fm, fragment);
-        verify(mockRecursiveLc, times(1)).onFragmentResumed(fm, childFragment);
-
-        // Confirm that the parent fragment received onAttachFragment
-        assertTrue("parent fragment did not receive onAttachFragment",
-                fragment.mCalledOnAttachFragment);
-
-        fc.dispatchStop();
-
-        verify(mockLc, times(1)).onFragmentStopped(fm, fragment);
-        verify(mockRecursiveLc, times(1)).onFragmentStopped(fm, fragment);
-        verify(mockRecursiveLc, times(1)).onFragmentStopped(fm, childFragment);
-
-        viewModelStore.clear();
-        fc.dispatchDestroy();
-
-        verify(mockLc, times(1)).onFragmentDestroyed(fm, fragment);
-        verify(mockRecursiveLc, times(1)).onFragmentDestroyed(fm, fragment);
-        verify(mockRecursiveLc, times(1)).onFragmentDestroyed(fm, childFragment);
-    }
-
-    /**
-     * This test checks that FragmentLifecycleCallbacks are invoked when expected.
-     */
-    @Test
-    @UiThreadTest
-    public void fragmentLifecycleCallbacks() throws Throwable {
-        final ViewModelStore viewModelStore = new ViewModelStore();
-        final FragmentController fc = FragmentController.createController(
-                new HostCallbacks(mActivityRule.getActivity(), viewModelStore));
-        fc.attachHost(null);
-        fc.dispatchCreate();
-
-        FragmentManager fm = fc.getSupportFragmentManager();
-
-        ChildFragmentManagerFragment fragment = new ChildFragmentManagerFragment();
-        fm.beginTransaction()
-                .add(android.R.id.content, fragment)
-                .commitNow();
-
-        fc.dispatchActivityCreated();
-
-        fc.dispatchStart();
-        fc.dispatchResume();
-
-        // Confirm that the parent fragment received onAttachFragment
-        assertTrue("parent fragment did not receive onAttachFragment",
-                fragment.mCalledOnAttachFragment);
-
-        shutdownFragmentController(fc, viewModelStore);
-    }
-
-    /**
-     * This tests that fragments call onDestroy when the activity finishes.
-     */
-    @Test
-    @UiThreadTest
-    public void fragmentDestroyedOnFinish() throws Throwable {
-        ViewModelStore viewModelStore = new ViewModelStore();
-        FragmentController fc = startupFragmentController(mActivityRule.getActivity(), null,
-                viewModelStore);
-        FragmentManager fm = fc.getSupportFragmentManager();
-
-        StrictViewFragment fragmentA = StrictViewFragment.create(R.layout.fragment_a);
-        StrictViewFragment fragmentB = StrictViewFragment.create(R.layout.fragment_b);
-        fm.beginTransaction()
-                .add(android.R.id.content, fragmentA)
-                .commit();
-        fm.executePendingTransactions();
-        fm.beginTransaction()
-                .replace(android.R.id.content, fragmentB)
-                .addToBackStack(null)
-                .commit();
-        fm.executePendingTransactions();
-        shutdownFragmentController(fc, viewModelStore);
-        assertTrue(fragmentB.mCalledOnDestroy);
-        assertTrue(fragmentA.mCalledOnDestroy);
-    }
-
-    // Make sure that executing transactions during activity lifecycle events
-    // is properly prevented.
-    @Test
-    public void preventReentrantCalls() throws Throwable {
-        testLifecycleTransitionFailure(StrictFragment.ATTACHED, StrictFragment.CREATED);
-        testLifecycleTransitionFailure(StrictFragment.CREATED, StrictFragment.ACTIVITY_CREATED);
-        testLifecycleTransitionFailure(StrictFragment.ACTIVITY_CREATED, StrictFragment.STARTED);
-        testLifecycleTransitionFailure(StrictFragment.STARTED, StrictFragment.RESUMED);
-
-        testLifecycleTransitionFailure(StrictFragment.RESUMED, StrictFragment.STARTED);
-        testLifecycleTransitionFailure(StrictFragment.STARTED, StrictFragment.CREATED);
-        testLifecycleTransitionFailure(StrictFragment.CREATED, StrictFragment.ATTACHED);
-        testLifecycleTransitionFailure(StrictFragment.ATTACHED, StrictFragment.DETACHED);
-    }
-
-    private void testLifecycleTransitionFailure(final int fromState,
-            final int toState) throws Throwable {
-        mActivityRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                final ViewModelStore viewModelStore = new ViewModelStore();
-                final FragmentController fc1 = startupFragmentController(
-                        mActivityRule.getActivity(), null, viewModelStore);
-
-                final FragmentManager fm1 = fc1.getSupportFragmentManager();
-
-                final Fragment reentrantFragment = ReentrantFragment.create(fromState, toState);
-
-                fm1.beginTransaction()
-                        .add(reentrantFragment, "reentrant")
-                        .commit();
-                try {
-                    fm1.executePendingTransactions();
-                } catch (IllegalStateException e) {
-                    fail("An exception shouldn't happen when initially adding the fragment");
-                }
-
-                // Now shut down the fragment controller. When fromState > toState, this should
-                // result in an exception
-                Parcelable savedState;
-                try {
-                    fc1.dispatchPause();
-                    savedState = fc1.saveAllState();
-                    fc1.dispatchStop();
-                    fc1.dispatchDestroy();
-                    if (fromState > toState) {
-                        fail("Expected IllegalStateException when moving from "
-                                + StrictFragment.stateToString(fromState) + " to "
-                                + StrictFragment.stateToString(toState));
-                    }
-                } catch (IllegalStateException e) {
-                    if (fromState < toState) {
-                        fail("Unexpected IllegalStateException when moving from "
-                                + StrictFragment.stateToString(fromState) + " to "
-                                + StrictFragment.stateToString(toState));
-                    }
-                    return; // test passed!
-                }
-
-                // now restore from saved state. This will be reached when
-                // fromState < toState. We want to catch the fragment while it
-                // is being restored as the fragment controller state is being brought up.
-
-                try {
-                    startupFragmentController(mActivityRule.getActivity(), savedState,
-                            viewModelStore);
-
-                    fail("Expected IllegalStateException when moving from "
-                            + StrictFragment.stateToString(fromState) + " to "
-                            + StrictFragment.stateToString(toState));
-                } catch (IllegalStateException e) {
-                    // expected, so the test passed!
-                }
-            }
-        });
-    }
-
-    /**
-     * Test to ensure that when dispatch* is called that the fragment manager
-     * doesn't cause the contained fragment states to change even if no state changes.
-     */
-    @Test
-    @UiThreadTest
-    public void noPrematureStateChange() throws Throwable {
-        ViewModelStore viewModelStore = new ViewModelStore();
-        FragmentController fc = startupFragmentController(mActivityRule.getActivity(), null,
-                viewModelStore);
-        FragmentManager fm = fc.getSupportFragmentManager();
-
-        fm.beginTransaction()
-                .add(new StrictFragment(), "1")
-                .commitNow();
-
-        fc = restartFragmentController(mActivityRule.getActivity(), fc, viewModelStore);
-
-        fm = fc.getSupportFragmentManager();
-
-        StrictFragment fragment1 = (StrictFragment) fm.findFragmentByTag("1");
-        assertWithMessage("Fragment should be resumed after restart")
-                .that(fragment1.mCalledOnResume)
-                .isTrue();
-        fragment1.mCalledOnResume = false;
-        fc.dispatchResume();
-
-        assertWithMessage("Fragment should not get onResume() after second dispatchResume()")
-                .that(fragment1.mCalledOnResume)
-                .isFalse();
-    }
-
-    @Test
-    @UiThreadTest
-    public void testIsStateSaved() throws Throwable {
-        ViewModelStore viewModelStore = new ViewModelStore();
-        FragmentController fc = startupFragmentController(mActivityRule.getActivity(), null,
-                viewModelStore);
-        FragmentManager fm = fc.getSupportFragmentManager();
-
-        Fragment f = new StrictFragment();
-        fm.beginTransaction()
-                .add(f, "1")
-                .commitNow();
-
-        assertFalse("fragment reported state saved while resumed", f.isStateSaved());
-
-        fc.dispatchPause();
-        fc.saveAllState();
-
-        assertTrue("fragment reported state not saved after saveAllState", f.isStateSaved());
-
-        fc.dispatchStop();
-
-        assertTrue("fragment reported state not saved after stop", f.isStateSaved());
-
-        viewModelStore.clear();
-        fc.dispatchDestroy();
-
-        assertFalse("fragment reported state saved after destroy", f.isStateSaved());
-    }
-
-    @Test
-    @UiThreadTest
-    public void testSetArgumentsLifecycle() throws Throwable {
-        ViewModelStore viewModelStore = new ViewModelStore();
-        FragmentController fc = startupFragmentController(mActivityRule.getActivity(), null,
-                viewModelStore);
-        FragmentManager fm = fc.getSupportFragmentManager();
-
-        Fragment f = new StrictFragment();
-        f.setArguments(new Bundle());
-
-        fm.beginTransaction()
-                .add(f, "1")
-                .commitNow();
-
-        f.setArguments(new Bundle());
-
-        fc.dispatchPause();
-        fc.saveAllState();
-
-        boolean threw = false;
-        try {
-            f.setArguments(new Bundle());
-        } catch (IllegalStateException ise) {
-            threw = true;
-        }
-        assertTrue("fragment allowed setArguments after state save", threw);
-
-        fc.dispatchStop();
-
-        threw = false;
-        try {
-            f.setArguments(new Bundle());
-        } catch (IllegalStateException ise) {
-            threw = true;
-        }
-        assertTrue("fragment allowed setArguments after stop", threw);
-
-        viewModelStore.clear();
-        fc.dispatchDestroy();
-
-        // Fully destroyed, so fragments have been removed.
-        f.setArguments(new Bundle());
-    }
-
-    /*
-     * Test that target fragments are in a useful state when we restore them, even if they're
-     * on the back stack.
-     */
-
-    @Test
-    @UiThreadTest
-    public void targetFragmentRestoreLifecycleStateBackStack() throws Throwable {
-        ViewModelStore viewModelStore = new ViewModelStore();
-        final FragmentController fc1 = FragmentController.createController(
-                new HostCallbacks(mActivityRule.getActivity(), viewModelStore));
-
-        final FragmentManager fm1 = fc1.getSupportFragmentManager();
-
-        fc1.attachHost(null);
-        fc1.dispatchCreate();
-
-        final Fragment target = new TargetFragment();
-        fm1.beginTransaction().add(target, "target").commitNow();
-
-        final Fragment referrer = new ReferrerFragment();
-        referrer.setTargetFragment(target, 0);
-
-        fm1.beginTransaction()
-                .remove(target)
-                .add(referrer, "referrer")
-                .addToBackStack(null)
-                .commit();
-
-        fc1.dispatchActivityCreated();
-        fc1.noteStateNotSaved();
-        fc1.execPendingActions();
-        fc1.dispatchStart();
-        fc1.dispatchResume();
-        fc1.execPendingActions();
-
-        // Simulate an activity restart
-        final FragmentController fc2 =
-                restartFragmentController(mActivityRule.getActivity(), fc1, viewModelStore);
-
-        // Bring the state back down to destroyed before we finish the test
-        shutdownFragmentController(fc2, viewModelStore);
-    }
-
-    @Test
-    @UiThreadTest
-    public void targetFragmentRestoreLifecycleStateManagerOrder() throws Throwable {
-        ViewModelStore viewModelStore = new ViewModelStore();
-        final FragmentController fc1 = FragmentController.createController(
-                new HostCallbacks(mActivityRule.getActivity(), viewModelStore));
-
-        final FragmentManager fm1 = fc1.getSupportFragmentManager();
-
-        fc1.attachHost(null);
-        fc1.dispatchCreate();
-
-        final Fragment target1 = new TargetFragment();
-        final Fragment referrer1 = new ReferrerFragment();
-        referrer1.setTargetFragment(target1, 0);
-
-        fm1.beginTransaction().add(target1, "target1").add(referrer1, "referrer1").commitNow();
-
-        final Fragment target2 = new TargetFragment();
-        final Fragment referrer2 = new ReferrerFragment();
-        referrer2.setTargetFragment(target2, 0);
-
-        // Order shouldn't matter.
-        fm1.beginTransaction().add(referrer2, "referrer2").add(target2, "target2").commitNow();
-
-        fc1.dispatchActivityCreated();
-        fc1.noteStateNotSaved();
-        fc1.execPendingActions();
-        fc1.dispatchStart();
-        fc1.dispatchResume();
-        fc1.execPendingActions();
-
-        // Simulate an activity restart
-        final FragmentController fc2 =
-                restartFragmentController(mActivityRule.getActivity(), fc1, viewModelStore);
-
-        // Bring the state back down to destroyed before we finish the test
-        shutdownFragmentController(fc2, viewModelStore);
-    }
-
-    @Test
-    @UiThreadTest
-    public void targetFragmentClearedWhenSetToNull() {
-        ViewModelStore viewModelStore = new ViewModelStore();
-        final FragmentController fc =
-                startupFragmentController(mActivityRule.getActivity(), null, viewModelStore);
-
-        final FragmentManager fm = fc.getSupportFragmentManager();
-
-        final Fragment target = new TargetFragment();
-        final Fragment referrer = new ReferrerFragment();
-        referrer.setTargetFragment(target, 0);
-
-        assertWithMessage("Target Fragment should be accessible before being added")
-                .that(referrer.getTargetFragment())
-                .isSameAs(target);
-
-        fm.beginTransaction().add(target, "target").add(referrer, "referrer").commitNow();
-
-        assertWithMessage("Target Fragment should be accessible after being added")
-                .that(referrer.getTargetFragment())
-                .isSameAs(target);
-
-        referrer.setTargetFragment(null, 0);
-
-        assertWithMessage("Target Fragment should cleared after setTargetFragment with null")
-                .that(referrer.getTargetFragment())
-                .isNull();
-
-        fm.beginTransaction()
-                .remove(referrer)
-                .commitNow();
-
-        assertWithMessage("Target Fragment should still be cleared after being removed")
-                .that(referrer.getTargetFragment())
-                .isNull();
-
-        shutdownFragmentController(fc, viewModelStore);
-    }
-
-    /**
-     * Test the availability of getTargetFragment() when the target Fragment is already
-     * attached to a FragmentManager, but the referrer Fragment is not attached.
-     */
-    @Test
-    @UiThreadTest
-    public void targetFragmentOnlyTargetAdded() {
-        ViewModelStore viewModelStore = new ViewModelStore();
-        final FragmentController fc =
-                startupFragmentController(mActivityRule.getActivity(), null, viewModelStore);
-
-        final FragmentManager fm = fc.getSupportFragmentManager();
-
-        final Fragment target = new TargetFragment();
-        // Add just the target Fragment to the FragmentManager
-        fm.beginTransaction().add(target, "target").commitNow();
-
-        final Fragment referrer = new ReferrerFragment();
-        referrer.setTargetFragment(target, 0);
-
-        assertWithMessage("Target Fragment should be accessible before being added")
-                .that(referrer.getTargetFragment())
-                .isSameAs(target);
-
-        fm.beginTransaction().add(referrer, "referrer").commitNow();
-
-        assertWithMessage("Target Fragment should be accessible after being added")
-                .that(referrer.getTargetFragment())
-                .isSameAs(target);
-
-        fm.beginTransaction()
-                .remove(referrer)
-                .commitNow();
-
-        assertWithMessage("Target Fragment should be accessible after being removed")
-                .that(referrer.getTargetFragment())
-                .isSameAs(target);
-
-        shutdownFragmentController(fc, viewModelStore);
-    }
-
-    /**
-     * Test the availability of getTargetFragment() when the target fragment is
-     * not retained and the referrer fragment is not retained.
-     */
-    @Test
-    @UiThreadTest
-    public void targetFragmentNonRetainedNonRetained() {
-        ViewModelStore viewModelStore = new ViewModelStore();
-        final FragmentController fc =
-                startupFragmentController(mActivityRule.getActivity(), null, viewModelStore);
-
-        final FragmentManager fm = fc.getSupportFragmentManager();
-
-        final Fragment target = new TargetFragment();
-        final Fragment referrer = new ReferrerFragment();
-        referrer.setTargetFragment(target, 0);
-
-        assertWithMessage("Target Fragment should be accessible before being added")
-                .that(referrer.getTargetFragment())
-                .isSameAs(target);
-
-        fm.beginTransaction().add(target, "target").add(referrer, "referrer").commitNow();
-
-        assertWithMessage("Target Fragment should be accessible after being added")
-                .that(referrer.getTargetFragment())
-                .isSameAs(target);
-
-        fm.beginTransaction()
-                .remove(referrer)
-                .commitNow();
-
-        assertWithMessage("Target Fragment should be accessible after being removed")
-                .that(referrer.getTargetFragment())
-                .isSameAs(target);
-
-        shutdownFragmentController(fc, viewModelStore);
-
-        assertWithMessage("Target Fragment should be accessible after destruction")
-                .that(referrer.getTargetFragment())
-                .isSameAs(target);
-    }
-
-    /**
-     * Test the availability of getTargetFragment() when the target fragment is
-     * retained and the referrer fragment is not retained.
-     */
-    @Test
-    @UiThreadTest
-    public void targetFragmentRetainedNonRetained() {
-        ViewModelStore viewModelStore = new ViewModelStore();
-        final FragmentController fc =
-                startupFragmentController(mActivityRule.getActivity(), null, viewModelStore);
-
-        final FragmentManager fm = fc.getSupportFragmentManager();
-
-        final Fragment target = new TargetFragment();
-        target.setRetainInstance(true);
-        final Fragment referrer = new ReferrerFragment();
-        referrer.setTargetFragment(target, 0);
-
-        assertWithMessage("Target Fragment should be accessible before being added")
-                .that(referrer.getTargetFragment())
-                .isSameAs(target);
-
-        fm.beginTransaction().add(target, "target").add(referrer, "referrer").commitNow();
-
-        assertWithMessage("Target Fragment should be accessible after being added")
-                .that(referrer.getTargetFragment())
-                .isSameAs(target);
-
-        fm.beginTransaction()
-                .remove(referrer)
-                .commitNow();
-
-        assertWithMessage("Target Fragment should be accessible after being removed")
-                .that(referrer.getTargetFragment())
-                .isSameAs(target);
-
-        shutdownFragmentController(fc, viewModelStore);
-
-        assertWithMessage("Target Fragment should be accessible after destruction")
-                .that(referrer.getTargetFragment())
-                .isSameAs(target);
-    }
-
-    /**
-     * Test the availability of getTargetFragment() when the target fragment is
-     * not retained and the referrer fragment is retained.
-     */
-    @Test
-    @UiThreadTest
-    public void targetFragmentNonRetainedRetained() {
-        ViewModelStore viewModelStore = new ViewModelStore();
-        final FragmentController fc =
-                startupFragmentController(mActivityRule.getActivity(), null, viewModelStore);
-
-        final FragmentManager fm = fc.getSupportFragmentManager();
-
-        final Fragment target = new TargetFragment();
-        final Fragment referrer = new ReferrerFragment();
-        referrer.setTargetFragment(target, 0);
-        referrer.setRetainInstance(true);
-
-        assertWithMessage("Target Fragment should be accessible before being added")
-                .that(referrer.getTargetFragment())
-                .isSameAs(target);
-
-        fm.beginTransaction().add(target, "target").add(referrer, "referrer").commitNow();
-
-        assertWithMessage("Target Fragment should be accessible after being added")
-                .that(referrer.getTargetFragment())
-                .isSameAs(target);
-
-        // Save the state
-        fc.dispatchPause();
-        fc.saveAllState();
-        fc.dispatchStop();
-        fc.dispatchDestroy();
-
-        assertWithMessage("Target Fragment should be accessible after target Fragment destruction")
-                .that(referrer.getTargetFragment())
-                .isSameAs(target);
-    }
-
-    /**
-     * Test the availability of getTargetFragment() when the target fragment is
-     * retained and the referrer fragment is also retained.
-     */
-    @Test
-    @UiThreadTest
-    public void targetFragmentRetainedRetained() {
-        ViewModelStore viewModelStore = new ViewModelStore();
-        final FragmentController fc =
-                startupFragmentController(mActivityRule.getActivity(), null, viewModelStore);
-
-        final FragmentManager fm = fc.getSupportFragmentManager();
-
-        final Fragment target = new TargetFragment();
-        target.setRetainInstance(true);
-        final Fragment referrer = new ReferrerFragment();
-        referrer.setRetainInstance(true);
-        referrer.setTargetFragment(target, 0);
-
-        assertWithMessage("Target Fragment should be accessible before being added")
-                .that(referrer.getTargetFragment())
-                .isSameAs(target);
-
-        fm.beginTransaction().add(target, "target").add(referrer, "referrer").commitNow();
-
-        assertWithMessage("Target Fragment should be accessible after being added")
-                .that(referrer.getTargetFragment())
-                .isSameAs(target);
-
-        // Save the state
-        fc.dispatchPause();
-        fc.saveAllState();
-        fc.dispatchStop();
-        fc.dispatchDestroy();
-
-        assertWithMessage("Target Fragment should be accessible after FragmentManager destruction")
-                .that(referrer.getTargetFragment())
-                .isSameAs(target);
-    }
-
-    @Test
-    public void targetFragmentNoCycles() throws Throwable {
-        final Fragment one = new Fragment();
-        final Fragment two = new Fragment();
-        final Fragment three = new Fragment();
-
-        try {
-            one.setTargetFragment(two, 0);
-            two.setTargetFragment(three, 0);
-            three.setTargetFragment(one, 0);
-            assertTrue("creating a fragment target cycle did not throw IllegalArgumentException",
-                    false);
-        } catch (IllegalArgumentException e) {
-            // Success!
-        }
-    }
-
-    @Test
-    public void targetFragmentSetClear() throws Throwable {
-        final Fragment one = new Fragment();
-        final Fragment two = new Fragment();
-
-        one.setTargetFragment(two, 0);
-        one.setTargetFragment(null, 0);
-    }
-
-    /**
-     * FragmentActivity should not raise the state of a Fragment while it is being destroyed.
-     */
-    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.JELLY_BEAN_MR1)
-    @Test
-    public void fragmentActivityFinishEarly() throws Throwable {
-        Intent intent = new Intent(mActivityRule.getActivity(), FragmentTestActivity.class);
-        intent.putExtra("finishEarly", true);
-        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-
-        FragmentTestActivity activity = (FragmentTestActivity)
-                InstrumentationRegistry.getInstrumentation().startActivitySync(intent);
-
-        assertTrue(activity.onDestroyLatch.await(1000, TimeUnit.MILLISECONDS));
-    }
-
-    /**
-     * When a fragment is saved in non-config, it should be restored to the same index.
-     */
-    @Test
-    @UiThreadTest
-    public void restoreNonConfig() throws Throwable {
-        FragmentController fc = FragmentTestUtil.createController(mActivityRule);
-        FragmentTestUtil.resume(mActivityRule, fc, null);
-        FragmentManager fm = fc.getSupportFragmentManager();
-
-        Fragment backStackRetainedFragment = new StrictFragment();
-        backStackRetainedFragment.setRetainInstance(true);
-        Fragment fragment1 = new StrictFragment();
-        fm.beginTransaction()
-                .add(backStackRetainedFragment, "backStack")
-                .add(fragment1, "1")
-                .setPrimaryNavigationFragment(fragment1)
-                .addToBackStack(null)
-                .commit();
-        fm.executePendingTransactions();
-        Fragment fragment2 = new StrictFragment();
-        fragment2.setRetainInstance(true);
-        fragment2.setTargetFragment(fragment1, 0);
-        Fragment fragment3 = new StrictFragment();
-        fm.beginTransaction()
-                .remove(backStackRetainedFragment)
-                .remove(fragment1)
-                .add(fragment2, "2")
-                .add(fragment3, "3")
-                .addToBackStack(null)
-                .commit();
-        fm.executePendingTransactions();
-
-        Pair<Parcelable, FragmentManagerNonConfig> savedState =
-                FragmentTestUtil.destroy(mActivityRule, fc);
-
-        fc = FragmentTestUtil.createController(mActivityRule);
-        FragmentTestUtil.resume(mActivityRule, fc, savedState);
-        boolean foundFragment2 = false;
-        for (Fragment fragment : fc.getSupportFragmentManager().getFragments()) {
-            if (fragment == fragment2) {
-                foundFragment2 = true;
-                assertNotNull(fragment.getTargetFragment());
-                assertEquals("1", fragment.getTargetFragment().getTag());
-            } else {
-                assertNotEquals("2", fragment.getTag());
-            }
-        }
-        assertTrue(foundFragment2);
-        fc.getSupportFragmentManager().popBackStackImmediate();
-        Fragment foundBackStackRetainedFragment = fc.getSupportFragmentManager()
-                .findFragmentByTag("backStack");
-        assertEquals("Retained Fragment on the back stack was not retained",
-                backStackRetainedFragment, foundBackStackRetainedFragment);
-    }
-
-    /**
-     * Check that retained fragments in the backstack correctly restored after two "configChanges"
-     */
-    @Test
-    @UiThreadTest
-    public void retainedFragmentInBackstack() throws Throwable {
-        FragmentController fc = FragmentTestUtil.createController(mActivityRule);
-        FragmentTestUtil.resume(mActivityRule, fc, null);
-        FragmentManager fm = fc.getSupportFragmentManager();
-
-        Fragment fragment1 = new StrictFragment();
-        fm.beginTransaction()
-                .add(fragment1, "1")
-                .addToBackStack(null)
-                .commit();
-        fm.executePendingTransactions();
-
-        Fragment child = new StrictFragment();
-        child.setRetainInstance(true);
-        fragment1.getChildFragmentManager().beginTransaction()
-                .add(child, "child").commit();
-        fragment1.getChildFragmentManager().executePendingTransactions();
-
-        Fragment fragment2 = new StrictFragment();
-        fm.beginTransaction()
-                .remove(fragment1)
-                .add(fragment2, "2")
-                .addToBackStack(null)
-                .commit();
-        fm.executePendingTransactions();
-
-        Pair<Parcelable, FragmentManagerNonConfig> savedState =
-                FragmentTestUtil.destroy(mActivityRule, fc);
-
-        fc = FragmentTestUtil.createController(mActivityRule);
-        FragmentTestUtil.resume(mActivityRule, fc, savedState);
-        savedState = FragmentTestUtil.destroy(mActivityRule, fc);
-        fc = FragmentTestUtil.createController(mActivityRule);
-        FragmentTestUtil.resume(mActivityRule, fc, savedState);
-        fm = fc.getSupportFragmentManager();
-        fm.popBackStackImmediate();
-        Fragment retainedChild = fm.findFragmentByTag("1")
-                .getChildFragmentManager().findFragmentByTag("child");
-        assertEquals(child, retainedChild);
-    }
-
-    /**
-     * When a fragment has been optimized out, it state should still be saved during
-     * save and restore instance state.
-     */
-    @Test
-    @UiThreadTest
-    public void saveRemovedFragment() throws Throwable {
-        FragmentController fc = FragmentTestUtil.createController(mActivityRule);
-        FragmentTestUtil.resume(mActivityRule, fc, null);
-        FragmentManager fm = fc.getSupportFragmentManager();
-
-        SaveStateFragment fragment1 = SaveStateFragment.create(1);
-        fm.beginTransaction()
-                .add(android.R.id.content, fragment1, "1")
-                .addToBackStack(null)
-                .commit();
-        SaveStateFragment fragment2 = SaveStateFragment.create(2);
-        fm.beginTransaction()
-                .replace(android.R.id.content, fragment2, "2")
-                .addToBackStack(null)
-                .commit();
-        fm.executePendingTransactions();
-
-        Pair<Parcelable, FragmentManagerNonConfig> savedState =
-                FragmentTestUtil.destroy(mActivityRule, fc);
-
-        fc = FragmentTestUtil.createController(mActivityRule);
-        FragmentTestUtil.resume(mActivityRule, fc, savedState);
-        fm = fc.getSupportFragmentManager();
-        fragment2 = (SaveStateFragment) fm.findFragmentByTag("2");
-        assertNotNull(fragment2);
-        assertEquals(2, fragment2.getValue());
-        fm.popBackStackImmediate();
-        fragment1 = (SaveStateFragment) fm.findFragmentByTag("1");
-        assertNotNull(fragment1);
-        assertEquals(1, fragment1.getValue());
-    }
-
-    /**
-     * When there are no retained instance fragments, the FragmentManagerNonConfig's fragments
-     * should be null
-     */
-    @Test
-    @UiThreadTest
-    public void nullNonConfig() throws Throwable {
-        FragmentController fc = FragmentTestUtil.createController(mActivityRule);
-        FragmentTestUtil.resume(mActivityRule, fc, null);
-        FragmentManager fm = fc.getSupportFragmentManager();
-
-        Fragment fragment1 = new StrictFragment();
-        fm.beginTransaction()
-                .add(fragment1, "1")
-                .addToBackStack(null)
-                .commit();
-        fm.executePendingTransactions();
-        Pair<Parcelable, FragmentManagerNonConfig> savedState =
-                FragmentTestUtil.destroy(mActivityRule, fc);
-        assertNull(savedState.second);
-    }
-
-    /**
-     * When the FragmentManager state changes, the pending transactions should execute.
-     */
-    @Test
-    @UiThreadTest
-    public void runTransactionsOnChange() throws Throwable {
-        FragmentController fc = FragmentTestUtil.createController(mActivityRule);
-        FragmentTestUtil.resume(mActivityRule, fc, null);
-        FragmentManager fm = fc.getSupportFragmentManager();
-
-        RemoveHelloInOnResume fragment1 = new RemoveHelloInOnResume();
-        StrictFragment fragment2 = new StrictFragment();
-        fm.beginTransaction()
-                .add(fragment1, "1")
-                .setReorderingAllowed(false)
-                .commit();
-        fm.beginTransaction()
-                .add(fragment2, "Hello")
-                .setReorderingAllowed(false)
-                .commit();
-        fm.executePendingTransactions();
-
-        assertEquals(2, fm.getFragments().size());
-        assertTrue(fm.getFragments().contains(fragment1));
-        assertTrue(fm.getFragments().contains(fragment2));
-
-        Pair<Parcelable, FragmentManagerNonConfig> savedState =
-                FragmentTestUtil.destroy(mActivityRule, fc);
-        fc = FragmentTestUtil.createController(mActivityRule);
-        FragmentTestUtil.resume(mActivityRule, fc, savedState);
-        fm = fc.getSupportFragmentManager();
-
-        assertEquals(1, fm.getFragments().size());
-        for (Fragment fragment : fm.getFragments()) {
-            assertTrue(fragment instanceof RemoveHelloInOnResume);
-        }
-    }
-
-    @Test
-    @UiThreadTest
-    public void optionsMenu() throws Throwable {
-        FragmentController fc = FragmentTestUtil.createController(mActivityRule);
-        FragmentTestUtil.resume(mActivityRule, fc, null);
-        FragmentManager fm = fc.getSupportFragmentManager();
-
-        InvalidateOptionFragment fragment = new InvalidateOptionFragment();
-        fm.beginTransaction()
-                .add(android.R.id.content, fragment)
-                .commit();
-        fm.executePendingTransactions();
-
-        Menu menu = mock(Menu.class);
-        fc.dispatchPrepareOptionsMenu(menu);
-        assertTrue(fragment.onPrepareOptionsMenuCalled);
-        fragment.onPrepareOptionsMenuCalled = false;
-        FragmentTestUtil.destroy(mActivityRule, fc);
-        fc.dispatchPrepareOptionsMenu(menu);
-        assertFalse(fragment.onPrepareOptionsMenuCalled);
-    }
-
-    /**
-     * When a retained instance fragment is saved while in the back stack, it should go
-     * through onCreate() when it is popped back.
-     */
-    @Test
-    @UiThreadTest
-    public void retainInstanceWithOnCreate() throws Throwable {
-        FragmentController fc = FragmentTestUtil.createController(mActivityRule);
-        FragmentTestUtil.resume(mActivityRule, fc, null);
-        FragmentManager fm = fc.getSupportFragmentManager();
-
-        OnCreateFragment fragment1 = new OnCreateFragment();
-
-        fm.beginTransaction()
-                .add(fragment1, "1")
-                .commit();
-        fm.beginTransaction()
-                .remove(fragment1)
-                .addToBackStack(null)
-                .commit();
-
-        Pair<Parcelable, FragmentManagerNonConfig> savedState =
-                FragmentTestUtil.destroy(mActivityRule, fc);
-        Pair<Parcelable, FragmentManagerNonConfig> restartState =
-                Pair.create(savedState.first, null);
-
-        fc = FragmentTestUtil.createController(mActivityRule);
-        FragmentTestUtil.resume(mActivityRule, fc, restartState);
-
-        // Save again, but keep the state
-        savedState = FragmentTestUtil.destroy(mActivityRule, fc);
-
-        fc = FragmentTestUtil.createController(mActivityRule);
-        FragmentTestUtil.resume(mActivityRule, fc, savedState);
-
-        fm = fc.getSupportFragmentManager();
-
-        fm.popBackStackImmediate();
-        OnCreateFragment fragment2 = (OnCreateFragment) fm.findFragmentByTag("1");
-        assertTrue(fragment2.onCreateCalled);
-        fm.popBackStackImmediate();
-    }
-
-    /**
-     * A retained instance fragment should go through onCreate() once, even through save and
-     * restore.
-     */
-    @Test
-    @UiThreadTest
-    public void retainInstanceOneOnCreate() throws Throwable {
-        FragmentController fc = FragmentTestUtil.createController(mActivityRule);
-        FragmentTestUtil.resume(mActivityRule, fc, null);
-        FragmentManager fm = fc.getSupportFragmentManager();
-
-        OnCreateFragment fragment = new OnCreateFragment();
-
-        fm.beginTransaction()
-                .add(fragment, "fragment")
-                .commit();
-        fm.executePendingTransactions();
-
-        fm.beginTransaction()
-                .remove(fragment)
-                .addToBackStack(null)
-                .commit();
-
-        assertTrue(fragment.onCreateCalled);
-        fragment.onCreateCalled = false;
-
-        Pair<Parcelable, FragmentManagerNonConfig> savedState =
-                FragmentTestUtil.destroy(mActivityRule, fc);
-
-        fc = FragmentTestUtil.createController(mActivityRule);
-        FragmentTestUtil.resume(mActivityRule, fc, savedState);
-        fm = fc.getSupportFragmentManager();
-
-        fm.popBackStackImmediate();
-        assertFalse(fragment.onCreateCalled);
-    }
-
-    /**
-     * A retained instance fragment added via XML should go through onCreate() once, but should get
-     * onInflate calls for each inflation.
-     */
-    @Test
-    @UiThreadTest
-    public void retainInstanceLayoutOnInflate() throws Throwable {
-        FragmentController fc = FragmentTestUtil.createController(mActivityRule);
-        FragmentTestUtil.resume(mActivityRule, fc, null);
-        FragmentManager fm = fc.getSupportFragmentManager();
-
-        RetainedInflatedParentFragment parentFragment = new RetainedInflatedParentFragment();
-
-        fm.beginTransaction()
-                .add(android.R.id.content, parentFragment)
-                .commit();
-        fm.executePendingTransactions();
-
-        RetainedInflatedChildFragment childFragment = (RetainedInflatedChildFragment)
-                parentFragment.getChildFragmentManager().findFragmentById(R.id.child_fragment);
-
-        fm.beginTransaction()
-                .remove(parentFragment)
-                .addToBackStack(null)
-                .commit();
-
-        Pair<Parcelable, FragmentManagerNonConfig> savedState =
-                FragmentTestUtil.destroy(mActivityRule, fc);
-
-        fc = FragmentTestUtil.createController(mActivityRule);
-        FragmentTestUtil.resume(mActivityRule, fc, savedState);
-        fm = fc.getSupportFragmentManager();
-
-        fm.popBackStackImmediate();
-
-        parentFragment = (RetainedInflatedParentFragment) fm.findFragmentById(android.R.id.content);
-        RetainedInflatedChildFragment childFragment2 = (RetainedInflatedChildFragment)
-                parentFragment.getChildFragmentManager().findFragmentById(R.id.child_fragment);
-
-        assertEquals("Child Fragment should be retained", childFragment, childFragment2);
-        assertEquals("Child Fragment should have onInflate called twice",
-                2, childFragment2.mOnInflateCount);
-    }
-
-    private void assertAnimationsMatch(FragmentManager fm, int enter, int exit, int popEnter,
-            int popExit) {
-        FragmentManagerImpl fmImpl = (FragmentManagerImpl) fm;
-        BackStackRecord record = fmImpl.mBackStack.get(fmImpl.mBackStack.size() - 1);
-
-        Assert.assertEquals(enter, record.mEnterAnim);
-        Assert.assertEquals(exit, record.mExitAnim);
-        Assert.assertEquals(popEnter, record.mPopEnterAnim);
-        Assert.assertEquals(popExit, record.mPopExitAnim);
-    }
-
-    private void executePendingTransactions(final FragmentManager fm) throws Throwable {
-        mActivityRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                fm.executePendingTransactions();
-            }
-        });
-    }
-
-    public static class StateSaveFragment extends StrictFragment {
-        private static final String STATE_KEY = "state";
-
-        private String mSavedState;
-        private String mUnsavedState;
-
-        public StateSaveFragment() {
-        }
-
-        public StateSaveFragment(String savedState, String unsavedState) {
-            mSavedState = savedState;
-            mUnsavedState = unsavedState;
-        }
-
-        public String getSavedState() {
-            return mSavedState;
-        }
-
-        public String getUnsavedState() {
-            return mUnsavedState;
-        }
-
-        @Override
-        public void onCreate(Bundle savedInstanceState) {
-            super.onCreate(savedInstanceState);
-            if (savedInstanceState != null) {
-                mSavedState = savedInstanceState.getString(STATE_KEY);
-            }
-        }
-
-        @Override
-        public void onSaveInstanceState(Bundle outState) {
-            super.onSaveInstanceState(outState);
-            outState.putString(STATE_KEY, mSavedState);
-        }
-    }
-
-    /**
-     * This tests a deliberately odd use of a child fragment, added in onCreateView instead
-     * of elsewhere. It simulates creating a UI child fragment added to the view hierarchy
-     * created by this fragment.
-     */
-    public static class ChildFragmentManagerFragment extends StrictFragment {
-        private FragmentManager mSavedChildFragmentManager;
-        private ChildFragmentManagerChildFragment mChildFragment;
-
-        @Override
-        public void onAttach(Context context) {
-            super.onAttach(context);
-            mSavedChildFragmentManager = getChildFragmentManager();
-        }
-
-        @Nullable
-        @Override
-        public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
-                @Nullable Bundle savedInstanceState) {
-            assertSame("child FragmentManagers not the same instance", mSavedChildFragmentManager,
-                    getChildFragmentManager());
-            ChildFragmentManagerChildFragment child =
-                    (ChildFragmentManagerChildFragment) mSavedChildFragmentManager
-                            .findFragmentByTag("tag");
-            if (child == null) {
-                child = new ChildFragmentManagerChildFragment("foo");
-                mSavedChildFragmentManager.beginTransaction()
-                        .add(child, "tag")
-                        .commitNow();
-                assertEquals("argument strings don't match", "foo", child.getString());
-            }
-            mChildFragment = child;
-            return new TextView(container.getContext());
-        }
-
-        @Nullable
-        public Fragment getChildFragment() {
-            return mChildFragment;
-        }
-    }
-
-    public static class ChildFragmentManagerChildFragment extends StrictFragment {
-        private String mString;
-
-        public ChildFragmentManagerChildFragment() {
-        }
-
-        public ChildFragmentManagerChildFragment(String arg) {
-            final Bundle b = new Bundle();
-            b.putString("string", arg);
-            setArguments(b);
-        }
-
-        @Override
-        public void onAttach(Context context) {
-            super.onAttach(context);
-            mString = requireArguments().getString("string", "NO VALUE");
-        }
-
-        public String getString() {
-            return mString;
-        }
-    }
-
-    public static class SimpleFragment extends Fragment {
-        private int mLayoutId;
-        private static final String LAYOUT_ID = "layoutId";
-
-        @Override
-        public void onCreate(Bundle savedInstanceState) {
-            super.onCreate(savedInstanceState);
-            if (savedInstanceState != null) {
-                mLayoutId = savedInstanceState.getInt(LAYOUT_ID, mLayoutId);
-            }
-        }
-
-        @Override
-        public void onSaveInstanceState(Bundle outState) {
-            super.onSaveInstanceState(outState);
-            outState.putInt(LAYOUT_ID, mLayoutId);
-        }
-
-        @Override
-        public View onCreateView(LayoutInflater inflater, ViewGroup container,
-                Bundle savedInstanceState) {
-            return inflater.inflate(mLayoutId, container, false);
-        }
-
-        public static SimpleFragment create(int layoutId) {
-            SimpleFragment fragment = new SimpleFragment();
-            fragment.mLayoutId = layoutId;
-            return fragment;
-        }
-    }
-
-    public static class TargetFragment extends Fragment {
-        public boolean calledCreate;
-
-        @Override
-        public void onCreate(@Nullable Bundle savedInstanceState) {
-            super.onCreate(savedInstanceState);
-            calledCreate = true;
-        }
-    }
-
-    public static class ReferrerFragment extends Fragment {
-        @Override
-        public void onCreate(@Nullable Bundle savedInstanceState) {
-            super.onCreate(savedInstanceState);
-
-            Fragment target = getTargetFragment();
-            assertNotNull("target fragment was null during referrer onCreate", target);
-
-            if (!(target instanceof TargetFragment)) {
-                throw new IllegalStateException("target fragment was not a TargetFragment");
-            }
-
-            assertTrue("target fragment has not yet been created",
-                    ((TargetFragment) target).calledCreate);
-        }
-    }
-
-    public static class SaveStateFragment extends Fragment {
-        private static final String VALUE_KEY = "SaveStateFragment.mValue";
-        private int mValue;
-
-        public static SaveStateFragment create(int value) {
-            SaveStateFragment saveStateFragment = new SaveStateFragment();
-            saveStateFragment.mValue = value;
-            return saveStateFragment;
-        }
-
-        @Override
-        public void onSaveInstanceState(Bundle outState) {
-            super.onSaveInstanceState(outState);
-            outState.putInt(VALUE_KEY, mValue);
-        }
-
-        @Override
-        public void onCreate(Bundle savedInstanceState) {
-            super.onCreate(savedInstanceState);
-            if (savedInstanceState != null) {
-                mValue = savedInstanceState.getInt(VALUE_KEY, mValue);
-            }
-        }
-
-        public int getValue() {
-            return mValue;
-        }
-    }
-
-    public static class RemoveHelloInOnResume extends Fragment {
-        @Override
-        public void onResume() {
-            super.onResume();
-            Fragment fragment = getFragmentManager().findFragmentByTag("Hello");
-            if (fragment != null) {
-                getFragmentManager().beginTransaction().remove(fragment).commit();
-            }
-        }
-    }
-
-    public static class InvalidateOptionFragment extends Fragment {
-        public boolean onPrepareOptionsMenuCalled;
-
-        public InvalidateOptionFragment() {
-            setHasOptionsMenu(true);
-        }
-
-        @Override
-        public void onPrepareOptionsMenu(Menu menu) {
-            onPrepareOptionsMenuCalled = true;
-            assertNotNull(getContext());
-            super.onPrepareOptionsMenu(menu);
-        }
-    }
-
-    public static class OnCreateFragment extends Fragment {
-        public boolean onCreateCalled;
-
-        public OnCreateFragment() {
-            setRetainInstance(true);
-        }
-
-        @Override
-        public void onCreate(Bundle savedInstanceState) {
-            super.onCreate(savedInstanceState);
-            onCreateCalled = true;
-        }
-    }
-
-    @ContentView(R.layout.nested_retained_inflated_fragment_parent)
-    public static class RetainedInflatedParentFragment extends Fragment {
-    }
-
-    @ContentView(R.layout.nested_inflated_fragment_child)
-    public static class RetainedInflatedChildFragment extends Fragment {
-
-        int mOnInflateCount = 0;
-
-        @Override
-        public void onCreate(@Nullable Bundle savedInstanceState) {
-            super.onCreate(savedInstanceState);
-            setRetainInstance(true);
-        }
-
-        @Override
-        public void onInflate(@NonNull Context context, @NonNull AttributeSet attrs,
-                @Nullable Bundle savedInstanceState) {
-            super.onInflate(context, attrs, savedInstanceState);
-            mOnInflateCount++;
-        }
-    }
-}
diff --git a/fragment/src/androidTest/java/androidx/fragment/app/FragmentLifecycleTest.kt b/fragment/src/androidTest/java/androidx/fragment/app/FragmentLifecycleTest.kt
new file mode 100644
index 0000000..42870f1
--- /dev/null
+++ b/fragment/src/androidTest/java/androidx/fragment/app/FragmentLifecycleTest.kt
@@ -0,0 +1,910 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.fragment.app
+
+import android.content.Context
+import android.content.Intent
+import android.os.Build
+import android.os.Bundle
+import android.os.Parcelable
+import android.util.AttributeSet
+import android.util.Pair
+import android.view.LayoutInflater
+import android.view.Menu
+import android.view.ViewGroup
+import android.widget.TextView
+import androidx.annotation.ContentView
+import androidx.core.view.ViewCompat
+import androidx.fragment.app.FragmentManager.FragmentLifecycleCallbacks
+import androidx.fragment.app.FragmentTestUtil.HostCallbacks
+import androidx.fragment.app.FragmentTestUtil.shutdownFragmentController
+import androidx.fragment.app.FragmentTestUtil.startupFragmentController
+import androidx.fragment.app.test.EmptyFragmentTestActivity
+import androidx.fragment.app.test.FragmentTestActivity
+import androidx.fragment.test.R
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.ViewModelStore
+import androidx.test.annotation.UiThreadTest
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.MediumTest
+import androidx.test.filters.SdkSuppress
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.rule.ActivityTestRule
+import com.google.common.truth.Truth.assertThat
+import com.google.common.truth.Truth.assertWithMessage
+import org.junit.Assert.fail
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
+import java.util.concurrent.TimeUnit
+
+@RunWith(AndroidJUnit4::class)
+@MediumTest
+class FragmentLifecycleTest {
+
+    @get:Rule
+    val activityRule = ActivityTestRule(EmptyFragmentTestActivity::class.java)
+
+    @Test
+    fun basicLifecycle() {
+        val fm = activityRule.activity.supportFragmentManager
+        val strictFragment = StrictFragment()
+
+        // Add fragment; StrictFragment will throw if it detects any violation
+        // in standard lifecycle method ordering or expected preconditions.
+        fm.beginTransaction().add(strictFragment, "EmptyHeadless").commit()
+        executePendingTransactions(fm)
+
+        assertWithMessage("fragment is not added").that(strictFragment.isAdded).isTrue()
+        assertWithMessage("fragment is detached").that(strictFragment.isDetached).isFalse()
+        assertWithMessage("fragment is not resumed").that(strictFragment.isResumed).isTrue()
+        val lifecycle = strictFragment.lifecycle
+        assertThat(lifecycle.currentState).isEqualTo(Lifecycle.State.RESUMED)
+
+        // Test removal as well; StrictFragment will throw here too.
+        fm.beginTransaction().remove(strictFragment).commit()
+        executePendingTransactions(fm)
+
+        assertWithMessage("fragment is added").that(strictFragment.isAdded).isFalse()
+        assertWithMessage("fragment is resumed").that(strictFragment.isResumed).isFalse()
+        assertThat(lifecycle.currentState).isEqualTo(Lifecycle.State.DESTROYED)
+        // Once removed, a new Lifecycle should be created just in case
+        // the developer reuses the same Fragment
+        assertThat(strictFragment.lifecycle.currentState).isEqualTo(Lifecycle.State.INITIALIZED)
+
+        // This one is perhaps counterintuitive; "detached" means specifically detached
+        // but still managed by a FragmentManager. The .remove call above
+        // should not enter this state.
+        assertWithMessage("fragment is detached").that(strictFragment.isDetached).isFalse()
+    }
+
+    @Test
+    fun detachment() {
+        val fm = activityRule.activity.supportFragmentManager
+        val f1 = StrictFragment()
+        val f2 = StrictFragment()
+
+        fm.beginTransaction().add(f1, "1").add(f2, "2").commit()
+        executePendingTransactions(fm)
+
+        assertWithMessage("fragment 1 is not added").that(f1.isAdded).isTrue()
+        assertWithMessage("fragment 2 is not added").that(f2.isAdded).isTrue()
+
+        // Test detaching fragments using StrictFragment to throw on errors.
+        fm.beginTransaction().detach(f1).detach(f2).commit()
+        executePendingTransactions(fm)
+
+        assertWithMessage("fragment 1 is not detached").that(f1.isDetached).isTrue()
+        assertWithMessage("fragment 2 is not detached").that(f2.isDetached).isTrue()
+        assertWithMessage("fragment 1 is added").that(f1.isAdded).isFalse()
+        assertWithMessage("fragment 2 is added").that(f2.isAdded).isFalse()
+
+        // Only reattach f1; leave v2 detached.
+        fm.beginTransaction().attach(f1).commit()
+        executePendingTransactions(fm)
+
+        assertWithMessage("fragment 1 is not added").that(f1.isAdded).isTrue()
+        assertWithMessage("fragment 1 is detached").that(f1.isDetached).isFalse()
+        assertWithMessage("fragment 2 is not detached").that(f2.isDetached).isTrue()
+
+        // Remove both from the FragmentManager.
+        fm.beginTransaction().remove(f1).remove(f2).commit()
+        executePendingTransactions(fm)
+
+        assertWithMessage("fragment 1 is added").that(f1.isAdded).isFalse()
+        assertWithMessage("fragment 2 is added").that(f2.isAdded).isFalse()
+        assertWithMessage("fragment 1 is detached").that(f1.isDetached).isFalse()
+        assertWithMessage("fragment 2 is detached").that(f2.isDetached).isFalse()
+    }
+
+    @Test
+    fun basicBackStack() {
+        val fm = activityRule.activity.supportFragmentManager
+        val f1 = StrictFragment()
+        val f2 = StrictFragment()
+
+        // Add a fragment normally to set up
+        fm.beginTransaction().add(f1, "1").commit()
+        executePendingTransactions(fm)
+
+        assertWithMessage("fragment 1 is not added").that(f1.isAdded).isTrue()
+
+        // Remove the first one and add a second. We're not using replace() here since
+        // these fragments are headless and as of this test writing, replace() only works
+        // for fragments with views and a container view id.
+        // Add it to the back stack so we can pop it afterwards.
+        fm.beginTransaction().remove(f1).add(f2, "2").addToBackStack("stack1").commit()
+        executePendingTransactions(fm)
+
+        assertWithMessage("fragment 1 is added").that(f1.isAdded).isFalse()
+        assertWithMessage("fragment 2 is not added").that(f2.isAdded).isTrue()
+
+        // Test popping the stack
+        fm.popBackStack()
+        executePendingTransactions(fm)
+
+        assertWithMessage("fragment 2 is added").that(f2.isAdded).isFalse()
+        assertWithMessage("fragment 1 is not added").that(f1.isAdded).isTrue()
+    }
+
+    @Test
+    fun attachBackStack() {
+        val fm = activityRule.activity.supportFragmentManager
+        val f1 = StrictFragment()
+        val f2 = StrictFragment()
+
+        // Add a fragment normally to set up
+        fm.beginTransaction().add(f1, "1").commit()
+        executePendingTransactions(fm)
+
+        assertWithMessage("fragment 1 is not added").that(f1.isAdded).isTrue()
+
+        fm.beginTransaction().detach(f1).add(f2, "2").addToBackStack("stack1").commit()
+        executePendingTransactions(fm)
+
+        assertWithMessage("fragment 1 is not detached").that(f1.isDetached).isTrue()
+        assertWithMessage("fragment 2 is detached").that(f2.isDetached).isFalse()
+        assertWithMessage("fragment 1 is added").that(f1.isAdded).isFalse()
+        assertWithMessage("fragment 2 is not added").that(f2.isAdded).isTrue()
+    }
+
+    @Test
+    fun viewLifecycle() {
+        // Test basic lifecycle when the fragment creates a view
+
+        val fm = activityRule.activity.supportFragmentManager
+        val f1 = StrictViewFragment()
+
+        fm.beginTransaction().add(android.R.id.content, f1).commit()
+        executePendingTransactions(fm)
+
+        assertWithMessage("fragment 1 is not added").that(f1.isAdded).isTrue()
+        val view = f1.requireView()
+        assertWithMessage("fragment 1 returned null from getView").that(view).isNotNull()
+        assertWithMessage("fragment 1's view is not attached to a window")
+            .that(ViewCompat.isAttachedToWindow(view)).isTrue()
+
+        fm.beginTransaction().remove(f1).commit()
+        executePendingTransactions(fm)
+
+        assertWithMessage("fragment 1 is added").that(f1.isAdded).isFalse()
+        assertWithMessage("fragment 1 returned non-null from getView after removal")
+            .that(f1.view).isNull()
+        assertWithMessage("fragment 1's previous view is still attached to a window")
+            .that(ViewCompat.isAttachedToWindow(view)).isFalse()
+    }
+
+    @Test
+    fun viewReplace() {
+        // Replace one view with another, then reverse it with the back stack
+
+        val fm = activityRule.activity.supportFragmentManager
+        val f1 = StrictViewFragment()
+        val f2 = StrictViewFragment()
+
+        fm.beginTransaction().add(android.R.id.content, f1).commit()
+        executePendingTransactions(fm)
+
+        assertWithMessage("fragment 1 is not added").that(f1.isAdded).isTrue()
+
+        val origView1 = f1.requireView()
+        assertWithMessage("fragment 1 returned null view").that(origView1).isNotNull()
+        assertWithMessage("fragment 1's view not attached")
+            .that(ViewCompat.isAttachedToWindow(origView1)).isTrue()
+
+        fm.beginTransaction().replace(android.R.id.content, f2).addToBackStack("stack1").commit()
+        executePendingTransactions(fm)
+
+        assertWithMessage("fragment 1 is added").that(f1.isAdded).isFalse()
+        assertWithMessage("fragment 2 is added").that(f2.isAdded).isTrue()
+        assertWithMessage("fragment 1 returned non-null view").that(f1.view).isNull()
+        assertWithMessage("fragment 1's old view still attached")
+            .that(ViewCompat.isAttachedToWindow(origView1)).isFalse()
+        val origView2 = f2.requireView()
+        assertWithMessage("fragment 2 returned null view").that(origView2).isNotNull()
+        assertWithMessage("fragment 2's view not attached")
+            .that(ViewCompat.isAttachedToWindow(origView2)).isTrue()
+
+        fm.popBackStack()
+        executePendingTransactions(fm)
+
+        assertWithMessage("fragment 1 is not added").that(f1.isAdded).isTrue()
+        assertWithMessage("fragment 2 is added").that(f2.isAdded).isFalse()
+        assertWithMessage("fragment 2 returned non-null view").that(f2.view).isNull()
+        assertWithMessage("fragment 2's view still attached")
+            .that(ViewCompat.isAttachedToWindow(origView2)).isFalse()
+        val newView1 = f1.requireView()
+        assertWithMessage("fragment 1 had same view from last attachment")
+            .that(newView1).isNotSameAs(origView1)
+        assertWithMessage("fragment 1's view not attached")
+            .that(ViewCompat.isAttachedToWindow(newView1)).isTrue()
+    }
+
+    /**
+     * This test confirms that as long as a parent fragment has called super.onCreate,
+     * any child fragments added, committed and with transactions executed will be brought
+     * to at least the CREATED state by the time the parent fragment receives onCreateView.
+     * This means the child fragment will have received onAttach/onCreate.
+     */
+    @Test
+    @UiThreadTest
+    fun childFragmentManagerAttach() {
+        val viewModelStore = ViewModelStore()
+        val fc = FragmentController.createController(
+            HostCallbacks(activityRule.activity, viewModelStore)
+        )
+        fc.attachHost(null)
+        fc.dispatchCreate()
+
+        val mockLc = mock(FragmentManager.FragmentLifecycleCallbacks::class.java)
+        val mockRecursiveLc = mock(FragmentManager.FragmentLifecycleCallbacks::class.java)
+
+        val fm = fc.supportFragmentManager
+        fm.registerFragmentLifecycleCallbacks(mockLc, false)
+        fm.registerFragmentLifecycleCallbacks(mockRecursiveLc, true)
+
+        val fragment = ChildFragmentManagerFragment()
+        fm.beginTransaction()
+            .add(android.R.id.content, fragment)
+            .commitNow()
+
+        verify<FragmentManager.FragmentLifecycleCallbacks>(mockLc, times(1))
+            .onFragmentCreated(fm, fragment, null)
+
+        fc.dispatchActivityCreated()
+
+        val childFragment = fragment.childFragment!!
+
+        verify<FragmentLifecycleCallbacks>(mockLc, times(1))
+            .onFragmentActivityCreated(fm, fragment, null)
+        verify<FragmentLifecycleCallbacks>(mockRecursiveLc, times(1))
+            .onFragmentActivityCreated(fm, fragment, null)
+        verify<FragmentLifecycleCallbacks>(mockRecursiveLc, times(1))
+            .onFragmentActivityCreated(fm, childFragment, null)
+
+        fc.dispatchStart()
+
+        verify<FragmentLifecycleCallbacks>(mockLc, times(1)).onFragmentStarted(fm, fragment)
+        verify<FragmentLifecycleCallbacks>(mockRecursiveLc, times(1))
+            .onFragmentStarted(fm, fragment)
+        verify<FragmentLifecycleCallbacks>(mockRecursiveLc, times(1))
+            .onFragmentStarted(fm, childFragment)
+
+        fc.dispatchResume()
+
+        verify<FragmentLifecycleCallbacks>(mockLc, times(1)).onFragmentResumed(fm, fragment)
+        verify<FragmentLifecycleCallbacks>(mockRecursiveLc, times(1))
+            .onFragmentResumed(fm, fragment)
+        verify<FragmentLifecycleCallbacks>(mockRecursiveLc, times(1))
+            .onFragmentResumed(fm, childFragment)
+
+        // Confirm that the parent fragment received onAttachFragment
+        assertWithMessage("parent fragment did not receive onAttachFragment")
+            .that(fragment.calledOnAttachFragment).isTrue()
+
+        fc.dispatchStop()
+
+        verify<FragmentLifecycleCallbacks>(mockLc, times(1)).onFragmentStopped(fm, fragment)
+        verify<FragmentLifecycleCallbacks>(mockRecursiveLc, times(1))
+            .onFragmentStopped(fm, fragment)
+        verify<FragmentLifecycleCallbacks>(mockRecursiveLc, times(1))
+            .onFragmentStopped(fm, childFragment)
+
+        viewModelStore.clear()
+        fc.dispatchDestroy()
+
+        verify<FragmentLifecycleCallbacks>(mockLc, times(1)).onFragmentDestroyed(fm, fragment)
+        verify<FragmentLifecycleCallbacks>(mockRecursiveLc, times(1))
+            .onFragmentDestroyed(fm, fragment)
+        verify<FragmentLifecycleCallbacks>(mockRecursiveLc, times(1))
+            .onFragmentDestroyed(fm, childFragment)
+    }
+
+    /**
+     * This test checks that FragmentLifecycleCallbacks are invoked when expected.
+     */
+    @Test
+    @UiThreadTest
+    fun fragmentLifecycleCallbacks() {
+        val viewModelStore = ViewModelStore()
+        val fc = FragmentController.createController(
+            HostCallbacks(activityRule.activity, viewModelStore)
+        )
+        fc.attachHost(null)
+        fc.dispatchCreate()
+
+        val fm = fc.supportFragmentManager
+
+        val fragment = ChildFragmentManagerFragment()
+        fm.beginTransaction()
+            .add(android.R.id.content, fragment)
+            .commitNow()
+
+        fc.dispatchActivityCreated()
+
+        fc.dispatchStart()
+        fc.dispatchResume()
+
+        // Confirm that the parent fragment received onAttachFragment
+        assertWithMessage("parent fragment did not receive onAttachFragment")
+            .that(fragment.calledOnAttachFragment).isTrue()
+
+        shutdownFragmentController(fc, viewModelStore)
+    }
+
+    /**
+     * This tests that fragments call onDestroy when the activity finishes.
+     */
+    @Test
+    @UiThreadTest
+    fun fragmentDestroyedOnFinish() {
+        val viewModelStore = ViewModelStore()
+        val fc = startupFragmentController(activityRule.activity, null, viewModelStore)
+        val fm = fc.supportFragmentManager
+
+        val fragmentA = StrictViewFragment(R.layout.fragment_a)
+        val fragmentB = StrictViewFragment(R.layout.fragment_b)
+        fm.beginTransaction()
+            .add(android.R.id.content, fragmentA)
+            .commit()
+        fm.executePendingTransactions()
+        fm.beginTransaction()
+            .replace(android.R.id.content, fragmentB)
+            .addToBackStack(null)
+            .commit()
+        fm.executePendingTransactions()
+        shutdownFragmentController(fc, viewModelStore)
+        assertThat(fragmentB.calledOnDestroy).isTrue()
+        assertThat(fragmentA.calledOnDestroy).isTrue()
+    }
+
+    // Make sure that executing transactions during activity lifecycle events
+    // is properly prevented.
+    @Test
+    fun preventReentrantCalls() {
+        testLifecycleTransitionFailure(StrictFragment.ATTACHED, StrictFragment.CREATED)
+        testLifecycleTransitionFailure(StrictFragment.CREATED, StrictFragment.ACTIVITY_CREATED)
+        testLifecycleTransitionFailure(StrictFragment.ACTIVITY_CREATED, StrictFragment.STARTED)
+        testLifecycleTransitionFailure(StrictFragment.STARTED, StrictFragment.RESUMED)
+
+        testLifecycleTransitionFailure(StrictFragment.RESUMED, StrictFragment.STARTED)
+        testLifecycleTransitionFailure(StrictFragment.STARTED, StrictFragment.CREATED)
+        testLifecycleTransitionFailure(StrictFragment.CREATED, StrictFragment.ATTACHED)
+        testLifecycleTransitionFailure(StrictFragment.ATTACHED, StrictFragment.DETACHED)
+    }
+
+    private fun testLifecycleTransitionFailure(fromState: Int, toState: Int) {
+        activityRule.runOnUiThread(Runnable {
+            val viewModelStore = ViewModelStore()
+            val fc1 = startupFragmentController(activityRule.activity, null, viewModelStore)
+
+            val fm1 = fc1.supportFragmentManager
+
+            val reentrantFragment = ReentrantFragment.create(fromState, toState)
+
+            fm1.beginTransaction().add(reentrantFragment, "reentrant").commit()
+            try {
+                fm1.executePendingTransactions()
+            } catch (e: IllegalStateException) {
+                fail("An exception shouldn't happen when initially adding the fragment")
+            }
+
+            // Now shut down the fragment controller. When fromState > toState, this should
+            // result in an exception
+            val savedState: Parcelable?
+            try {
+                fc1.dispatchPause()
+                savedState = fc1.saveAllState()
+                fc1.dispatchStop()
+                fc1.dispatchDestroy()
+                if (fromState > toState) {
+                    fail(
+                        "Expected IllegalStateException when moving from " +
+                                StrictFragment.stateToString(fromState) + " to " +
+                                StrictFragment.stateToString(toState)
+                    )
+                }
+            } catch (e: IllegalStateException) {
+                if (fromState < toState) {
+                    fail(
+                        "Unexpected IllegalStateException when moving from " +
+                                StrictFragment.stateToString(fromState) + " to " +
+                                StrictFragment.stateToString(toState)
+                    )
+                }
+                assertThat(e)
+                    .hasMessageThat().contains("FragmentManager is already executing transactions")
+                return@Runnable // test passed!
+            }
+
+            // now restore from saved state. This will be reached when
+            // fromState < toState. We want to catch the fragment while it
+            // is being restored as the fragment controller state is being brought up.
+
+            try {
+                startupFragmentController(activityRule.activity, savedState, viewModelStore)
+                fail(
+                    "Expected IllegalStateException when moving from " +
+                            StrictFragment.stateToString(fromState) + " to " +
+                            StrictFragment.stateToString(toState)
+                )
+            } catch (e: IllegalStateException) {
+                assertThat(e)
+                    .hasMessageThat().contains("FragmentManager is already executing transactions")
+            }
+        })
+    }
+
+    @Test
+    @UiThreadTest
+    fun testSetArgumentsLifecycle() {
+        val viewModelStore = ViewModelStore()
+        val fc = startupFragmentController(activityRule.activity, null, viewModelStore)
+        val fm = fc.supportFragmentManager
+
+        val f = StrictFragment()
+        f.arguments = Bundle()
+
+        fm.beginTransaction().add(f, "1").commitNow()
+
+        f.arguments = Bundle()
+
+        fc.dispatchPause()
+        fc.saveAllState()
+
+        try {
+            f.arguments = Bundle()
+        } catch (e: IllegalStateException) {
+            assertThat(e)
+                .hasMessageThat().contains("Fragment already added and state has been saved")
+        }
+
+        fc.dispatchStop()
+
+        try {
+            f.arguments = Bundle()
+        } catch (e: IllegalStateException) {
+            assertThat(e)
+                .hasMessageThat().contains("Fragment already added and state has been saved")
+        }
+
+        viewModelStore.clear()
+        fc.dispatchDestroy()
+
+        // Fully destroyed, so fragments have been removed.
+        f.arguments = Bundle()
+    }
+
+    /**
+     * FragmentActivity should not raise the state of a Fragment while it is being destroyed.
+     */
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.JELLY_BEAN_MR1)
+    @Test
+    fun fragmentActivityFinishEarly() {
+        val intent = Intent(activityRule.activity, FragmentTestActivity::class.java)
+        intent.putExtra("finishEarly", true)
+        intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
+
+        val activity = InstrumentationRegistry.getInstrumentation()
+            .startActivitySync(intent) as FragmentTestActivity
+
+        assertThat(activity.onDestroyLatch.await(1000, TimeUnit.MILLISECONDS)).isTrue()
+    }
+
+    /**
+     * When a fragment is saved in non-config, it should be restored to the same index.
+     */
+    @Test
+    @UiThreadTest
+    fun restoreNonConfig() {
+        var fc = FragmentTestUtil.createController(activityRule)
+        FragmentTestUtil.resume(activityRule, fc, null)
+        val fm = fc.supportFragmentManager
+
+        val backStackRetainedFragment = StrictFragment()
+        backStackRetainedFragment.retainInstance = true
+        val fragment1 = StrictFragment()
+        fm.beginTransaction()
+            .add(backStackRetainedFragment, "backStack")
+            .add(fragment1, "1")
+            .setPrimaryNavigationFragment(fragment1)
+            .addToBackStack(null)
+            .commit()
+        fm.executePendingTransactions()
+        val fragment2 = StrictFragment()
+        fragment2.retainInstance = true
+        fragment2.setTargetFragment(fragment1, 0)
+        val fragment3 = StrictFragment()
+        fm.beginTransaction()
+            .remove(backStackRetainedFragment)
+            .remove(fragment1)
+            .add(fragment2, "2")
+            .add(fragment3, "3")
+            .addToBackStack(null)
+            .commit()
+        fm.executePendingTransactions()
+
+        val savedState = FragmentTestUtil.destroy(activityRule, fc)
+
+        fc = FragmentTestUtil.createController(activityRule)
+        FragmentTestUtil.resume(activityRule, fc, savedState)
+        var foundFragment2 = false
+        for (fragment in fc.supportFragmentManager.fragments) {
+            if (fragment === fragment2) {
+                foundFragment2 = true
+                assertThat(fragment.getTargetFragment()).isNotNull()
+                assertThat(fragment.getTargetFragment()!!.tag).isEqualTo("1")
+            } else {
+                assertThat(fragment.tag).isNotEqualTo("2")
+            }
+        }
+        assertThat(foundFragment2).isTrue()
+        fc.supportFragmentManager.popBackStackImmediate()
+        val foundBackStackRetainedFragment = fc.supportFragmentManager
+            .findFragmentByTag("backStack")
+        assertWithMessage("Retained Fragment on the back stack was not retained")
+            .that(foundBackStackRetainedFragment).isEqualTo(backStackRetainedFragment)
+    }
+
+    /**
+     * Check that retained fragments in the backstack correctly restored after two "configChanges"
+     */
+    @Test
+    @UiThreadTest
+    fun retainedFragmentInBackstack() {
+        var fc = FragmentTestUtil.createController(activityRule)
+        FragmentTestUtil.resume(activityRule, fc, null)
+        var fm = fc.supportFragmentManager
+
+        val fragment1 = StrictFragment()
+        fm.beginTransaction().add(fragment1, "1").addToBackStack(null).commit()
+        fm.executePendingTransactions()
+
+        val child = StrictFragment()
+        child.retainInstance = true
+        fragment1.childFragmentManager.beginTransaction().add(child, "child").commit()
+        fragment1.childFragmentManager.executePendingTransactions()
+
+        val fragment2 = StrictFragment()
+        fm.beginTransaction().remove(fragment1).add(fragment2, "2").addToBackStack(null).commit()
+        fm.executePendingTransactions()
+
+        var savedState = FragmentTestUtil.destroy(activityRule, fc)
+
+        fc = FragmentTestUtil.createController(activityRule)
+        FragmentTestUtil.resume(activityRule, fc, savedState)
+        savedState = FragmentTestUtil.destroy(activityRule, fc)
+        fc = FragmentTestUtil.createController(activityRule)
+        FragmentTestUtil.resume(activityRule, fc, savedState)
+        fm = fc.supportFragmentManager
+        fm.popBackStackImmediate()
+        val retainedChild = fm.findFragmentByTag("1")!!
+            .childFragmentManager.findFragmentByTag("child")
+        assertThat(retainedChild).isEqualTo(child)
+    }
+
+    /**
+     * When there are no retained instance fragments, the FragmentManagerNonConfig's fragments
+     * should be null
+     */
+    @Test
+    @UiThreadTest
+    fun nullNonConfig() {
+        val fc = FragmentTestUtil.createController(activityRule)
+        FragmentTestUtil.resume(activityRule, fc, null)
+        val fm = fc.supportFragmentManager
+
+        val fragment1 = StrictFragment()
+        fm.beginTransaction().add(fragment1, "1").addToBackStack(null).commit()
+        fm.executePendingTransactions()
+        val savedState = FragmentTestUtil.destroy(activityRule, fc)
+        assertThat(savedState.second).isNull()
+    }
+
+    /**
+     * When the FragmentManager state changes, the pending transactions should execute.
+     */
+    @Test
+    @UiThreadTest
+    fun runTransactionsOnChange() {
+        var fc = FragmentTestUtil.createController(activityRule)
+        FragmentTestUtil.resume(activityRule, fc, null)
+        var fm = fc.supportFragmentManager
+
+        val fragment1 = RemoveHelloInOnResume()
+        val fragment2 = StrictFragment()
+        fm.beginTransaction().add(fragment1, "1").setReorderingAllowed(false).commit()
+        fm.beginTransaction().add(fragment2, "Hello").setReorderingAllowed(false).commit()
+        fm.executePendingTransactions()
+
+        assertThat(fm.fragments.size).isEqualTo(2)
+        assertThat(fm.fragments.contains(fragment1)).isTrue()
+        assertThat(fm.fragments.contains(fragment2)).isTrue()
+
+        val savedState = FragmentTestUtil.destroy(activityRule, fc)
+        fc = FragmentTestUtil.createController(activityRule)
+        FragmentTestUtil.resume(activityRule, fc, savedState)
+        fm = fc.supportFragmentManager
+
+        assertThat(fm.fragments.size).isEqualTo(1)
+        for (fragment in fm.fragments) {
+            assertThat(fragment is RemoveHelloInOnResume).isTrue()
+        }
+    }
+
+    @Test
+    @UiThreadTest
+    fun optionsMenu() {
+        val fc = FragmentTestUtil.createController(activityRule)
+        FragmentTestUtil.resume(activityRule, fc, null)
+        val fm = fc.supportFragmentManager
+
+        val fragment = InvalidateOptionFragment()
+        fm.beginTransaction().add(android.R.id.content, fragment).commit()
+        fm.executePendingTransactions()
+
+        val menu = mock(Menu::class.java)
+        fc.dispatchPrepareOptionsMenu(menu)
+        assertThat(fragment.onPrepareOptionsMenuCalled).isTrue()
+        fragment.onPrepareOptionsMenuCalled = false
+        FragmentTestUtil.destroy(activityRule, fc)
+        fc.dispatchPrepareOptionsMenu(menu)
+        assertThat(fragment.onPrepareOptionsMenuCalled).isFalse()
+    }
+
+    /**
+     * When a retained instance fragment is saved while in the back stack, it should go
+     * through onCreate() when it is popped back.
+     */
+    @Test
+    @UiThreadTest
+    fun retainInstanceWithOnCreate() {
+        var fc = FragmentTestUtil.createController(activityRule)
+        FragmentTestUtil.resume(activityRule, fc, null)
+        var fm = fc.supportFragmentManager
+
+        val fragment1 = OnCreateFragment()
+
+        fm.beginTransaction().add(fragment1, "1").commit()
+        fm.beginTransaction().remove(fragment1).addToBackStack(null).commit()
+
+        var savedState = FragmentTestUtil.destroy(activityRule, fc)
+        val restartState = Pair.create<Parcelable, FragmentManagerNonConfig>(savedState.first, null)
+
+        fc = FragmentTestUtil.createController(activityRule)
+        FragmentTestUtil.resume(activityRule, fc, restartState)
+
+        // Save again, but keep the state
+        savedState = FragmentTestUtil.destroy(activityRule, fc)
+
+        fc = FragmentTestUtil.createController(activityRule)
+        FragmentTestUtil.resume(activityRule, fc, savedState)
+
+        fm = fc.supportFragmentManager
+
+        fm.popBackStackImmediate()
+        val fragment2 = fm.findFragmentByTag("1") as OnCreateFragment
+        assertThat(fragment2.onCreateCalled).isTrue()
+        fm.popBackStackImmediate()
+    }
+
+    /**
+     * A retained instance fragment should go through onCreate() once, even through save and
+     * restore.
+     */
+    @Test
+    @UiThreadTest
+    fun retainInstanceOneOnCreate() {
+        var fc = FragmentTestUtil.createController(activityRule)
+        FragmentTestUtil.resume(activityRule, fc, null)
+        var fm = fc.supportFragmentManager
+
+        val fragment = OnCreateFragment()
+
+        fm.beginTransaction().add(fragment, "fragment").commit()
+        fm.executePendingTransactions()
+
+        fm.beginTransaction().remove(fragment).addToBackStack(null).commit()
+
+        assertThat(fragment.onCreateCalled).isTrue()
+        fragment.onCreateCalled = false
+
+        val savedState = FragmentTestUtil.destroy(activityRule, fc)
+
+        fc = FragmentTestUtil.createController(activityRule)
+        FragmentTestUtil.resume(activityRule, fc, savedState)
+        fm = fc.supportFragmentManager
+
+        fm.popBackStackImmediate()
+        assertThat(fragment.onCreateCalled).isFalse()
+    }
+
+    /**
+     * A retained instance fragment added via XML should go through onCreate() once, but should get
+     * onInflate calls for each inflation.
+     */
+    @Test
+    @UiThreadTest
+    fun retainInstanceLayoutOnInflate() {
+        var fc = FragmentTestUtil.createController(activityRule)
+        FragmentTestUtil.resume(activityRule, fc, null)
+        var fm = fc.supportFragmentManager
+
+        var parentFragment = RetainedInflatedParentFragment()
+
+        fm.beginTransaction().add(android.R.id.content, parentFragment).commit()
+        fm.executePendingTransactions()
+
+        val childFragment = parentFragment.childFragmentManager
+            .findFragmentById(R.id.child_fragment) as RetainedInflatedChildFragment
+
+        fm.beginTransaction().remove(parentFragment).addToBackStack(null).commit()
+
+        val savedState = FragmentTestUtil.destroy(activityRule, fc)
+
+        fc = FragmentTestUtil.createController(activityRule)
+        FragmentTestUtil.resume(activityRule, fc, savedState)
+        fm = fc.supportFragmentManager
+
+        fm.popBackStackImmediate()
+
+        parentFragment = fm.findFragmentById(android.R.id.content) as RetainedInflatedParentFragment
+        val childFragment2 = parentFragment.childFragmentManager
+            .findFragmentById(R.id.child_fragment) as RetainedInflatedChildFragment
+
+        assertWithMessage("Child Fragment should be retained")
+            .that(childFragment2).isEqualTo(childFragment)
+        assertWithMessage("Child Fragment should have onInflate called twice")
+            .that(childFragment2.mOnInflateCount).isEqualTo(2)
+    }
+
+    private fun executePendingTransactions(fm: FragmentManager) {
+        activityRule.runOnUiThread { fm.executePendingTransactions() }
+    }
+
+    /**
+     * This tests a deliberately odd use of a child fragment, added in onCreateView instead
+     * of elsewhere. It simulates creating a UI child fragment added to the view hierarchy
+     * created by this fragment.
+     */
+    class ChildFragmentManagerFragment : StrictFragment() {
+        private lateinit var savedChildFragmentManager: FragmentManager
+        private lateinit var childFragmentManagerChildFragment: ChildFragmentManagerChildFragment
+
+        val childFragment: Fragment?
+            get() = childFragmentManagerChildFragment
+
+        override fun onAttach(context: Context) {
+            super.onAttach(context)
+            savedChildFragmentManager = childFragmentManager
+        }
+
+        override fun onCreateView(
+            inflater: LayoutInflater,
+            container: ViewGroup?,
+            savedInstanceState: Bundle?
+        ) = TextView(inflater.context).also {
+            assertWithMessage("child FragmentManagers not the same instance")
+                .that(childFragmentManager).isSameAs(savedChildFragmentManager)
+            var child = savedChildFragmentManager
+                .findFragmentByTag("tag") as ChildFragmentManagerChildFragment?
+            if (child == null) {
+                child = ChildFragmentManagerChildFragment("foo")
+                savedChildFragmentManager.beginTransaction().add(child, "tag").commitNow()
+                assertWithMessage("argument strings don't match")
+                    .that(child.string).isEqualTo("foo")
+            }
+            childFragmentManagerChildFragment = child
+        }
+    }
+
+    class ChildFragmentManagerChildFragment : StrictFragment {
+        lateinit var string: String
+            private set
+
+        constructor()
+
+        constructor(arg: String) {
+            val b = Bundle()
+            b.putString("string", arg)
+            arguments = b
+        }
+
+        override fun onAttach(context: Context) {
+            super.onAttach(context)
+            string = requireArguments().getString("string", "NO VALUE")
+        }
+    }
+
+    class RemoveHelloInOnResume : Fragment() {
+        override fun onResume() {
+            super.onResume()
+            val fragment = fragmentManager!!.findFragmentByTag("Hello")
+            if (fragment != null) {
+                fragmentManager!!.beginTransaction().remove(fragment).commit()
+            }
+        }
+    }
+
+    class InvalidateOptionFragment : Fragment() {
+        var onPrepareOptionsMenuCalled: Boolean = false
+
+        init {
+            setHasOptionsMenu(true)
+        }
+
+        override fun onPrepareOptionsMenu(menu: Menu) {
+            onPrepareOptionsMenuCalled = true
+            assertThat(context).isNotNull()
+            super.onPrepareOptionsMenu(menu)
+        }
+    }
+
+    class OnCreateFragment : Fragment() {
+        var onCreateCalled: Boolean = false
+
+        init {
+            retainInstance = true
+        }
+
+        override fun onCreate(savedInstanceState: Bundle?) {
+            super.onCreate(savedInstanceState)
+            onCreateCalled = true
+        }
+    }
+
+    @ContentView(R.layout.nested_retained_inflated_fragment_parent)
+    class RetainedInflatedParentFragment : Fragment()
+
+    @ContentView(R.layout.nested_inflated_fragment_child)
+    class RetainedInflatedChildFragment : Fragment() {
+        internal var mOnInflateCount = 0
+
+        override fun onCreate(savedInstanceState: Bundle?) {
+            super.onCreate(savedInstanceState)
+            retainInstance = true
+        }
+
+        override fun onInflate(context: Context, attrs: AttributeSet, savedInstanceState: Bundle?) {
+            super.onInflate(context, attrs, savedInstanceState)
+            mOnInflateCount++
+        }
+    }
+}
diff --git a/fragment/src/androidTest/java/androidx/fragment/app/FragmentReorderingTest.kt b/fragment/src/androidTest/java/androidx/fragment/app/FragmentReorderingTest.kt
index 57cd568..6877430 100644
--- a/fragment/src/androidTest/java/androidx/fragment/app/FragmentReorderingTest.kt
+++ b/fragment/src/androidTest/java/androidx/fragment/app/FragmentReorderingTest.kt
@@ -614,8 +614,7 @@
 
         assertThat(firstEditText.isFocused).isTrue()
         val fragment1 = CountCallsFragment()
-        val fragment2 = CountCallsFragment()
-        fragment2.setLayoutId(R.layout.with_edit_text)
+        val fragment2 = CountCallsFragment(R.layout.with_edit_text)
         fm.beginTransaction()
             .add(R.id.fragmentContainer2, fragment1)
             .addToBackStack(null)
diff --git a/fragment/src/androidTest/java/androidx/fragment/app/FragmentReplaceTest.kt b/fragment/src/androidTest/java/androidx/fragment/app/FragmentReplaceTest.kt
index 1430927..2284c44 100644
--- a/fragment/src/androidTest/java/androidx/fragment/app/FragmentReplaceTest.kt
+++ b/fragment/src/androidTest/java/androidx/fragment/app/FragmentReplaceTest.kt
@@ -19,7 +19,6 @@
 import android.view.KeyEvent
 import android.view.View
 import androidx.fragment.app.test.FragmentTestActivity
-import androidx.fragment.app.test.FragmentTestActivity.TestFragment
 import androidx.fragment.test.R
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.MediumTest
@@ -53,7 +52,7 @@
         val fm = activity.supportFragmentManager
 
         fm.beginTransaction()
-            .add(R.id.content, TestFragment.create(R.layout.fragment_a))
+            .add(R.id.content, StrictViewFragment(R.layout.fragment_a))
             .addToBackStack(null)
             .commit()
         executePendingTransactions(fm)
@@ -62,7 +61,7 @@
         assertThat(activity.findViewById<View>(R.id.textC)).isNull()
 
         fm.beginTransaction()
-            .add(R.id.content, TestFragment.create(R.layout.fragment_b))
+            .add(R.id.content, StrictViewFragment(R.layout.fragment_b))
             .addToBackStack(null)
             .commit()
         executePendingTransactions(fm)
@@ -71,7 +70,7 @@
         assertThat(activity.findViewById<View>(R.id.textC)).isNull()
 
         activity.supportFragmentManager.beginTransaction()
-            .replace(R.id.content, TestFragment.create(R.layout.fragment_c))
+            .replace(R.id.content, StrictViewFragment(R.layout.fragment_c))
             .addToBackStack(null)
             .commit()
         executePendingTransactions(fm)
diff --git a/fragment/src/androidTest/java/androidx/fragment/app/FragmentTransitionTest.kt b/fragment/src/androidTest/java/androidx/fragment/app/FragmentTransitionTest.kt
index bc68d97..83d8465 100644
--- a/fragment/src/androidTest/java/androidx/fragment/app/FragmentTransitionTest.kt
+++ b/fragment/src/androidTest/java/androidx/fragment/app/FragmentTransitionTest.kt
@@ -113,8 +113,7 @@
         val fragment1 = setupInitialFragment()
 
         // Now do a transition to scene2
-        val fragment2 = TransitionFragment()
-        fragment2.setLayoutId(R.layout.scene2)
+        val fragment2 = TransitionFragment(R.layout.scene2)
 
         verifyTransition(fragment1, fragment2, "blueSquare")
 
@@ -127,13 +126,11 @@
     fun intermediateFragment() {
         val fragment1 = setupInitialFragment()
 
-        val fragment2 = TransitionFragment()
-        fragment2.setLayoutId(R.layout.scene3)
+        val fragment2 = TransitionFragment(R.layout.scene3)
 
         verifyTransition(fragment1, fragment2, "shared")
 
-        val fragment3 = TransitionFragment()
-        fragment3.setLayoutId(R.layout.scene2)
+        val fragment3 = TransitionFragment(R.layout.scene2)
 
         verifyTransition(fragment2, fragment3, "blueSquare")
 
@@ -149,8 +146,7 @@
         val startBlue = findBlue()
         val startGreen = findGreen()
 
-        val fragment2 = TransitionFragment()
-        fragment2.setLayoutId(R.layout.scene2)
+        val fragment2 = TransitionFragment(R.layout.scene2)
 
         instrumentation.runOnMainSync {
             fragmentManager.beginTransaction()
@@ -190,10 +186,8 @@
     @Test
     fun crossContainer() {
         FragmentTestUtil.setContentView(activityRule, R.layout.double_container)
-        val fragment1 = TransitionFragment()
-        fragment1.setLayoutId(R.layout.scene1)
-        val fragment2 = TransitionFragment()
-        fragment2.setLayoutId(R.layout.scene1)
+        val fragment1 = TransitionFragment(R.layout.scene1)
+        val fragment2 = TransitionFragment(R.layout.scene1)
         fragmentManager.beginTransaction()
             .setReorderingAllowed(reorderingAllowed)
             .add(R.id.fragmentContainer1, fragment1)
@@ -229,8 +223,7 @@
         val fragment1 = setupInitialFragment()
 
         // Now do a transition to scene2
-        val fragment2 = TransitionFragment()
-        fragment2.setLayoutId(R.layout.scene2)
+        val fragment2 = TransitionFragment(R.layout.scene2)
 
         val enterCallback = mock(SharedElementCallback::class.java)
         fragment2.setEnterSharedElementCallback(enterCallback)
@@ -297,8 +290,7 @@
         val fragment1 = setupInitialFragment()
 
         // Now do a transition to scene2
-        val fragment2 = TransitionFragment()
-        fragment2.setLayoutId(R.layout.scene2)
+        val fragment2 = TransitionFragment(R.layout.scene2)
 
         val startBlue = findBlue()
         val startGreen = findGreen()
@@ -372,8 +364,7 @@
         val fragment1 = setupInitialFragment()
 
         // Now do a transition to scene2
-        val fragment2 = TransitionFragment()
-        fragment2.setLayoutId(R.layout.scene2)
+        val fragment2 = TransitionFragment(R.layout.scene2)
 
         val startBlue = findBlue()
         val startBlueBounds = getBoundsOnScreen(startBlue)
@@ -447,7 +438,6 @@
 
         // Now do a transition to scene2
         val fragment2 = ComplexTransitionFragment()
-        fragment2.setLayoutId(R.layout.scene2)
 
         val startBlue = findBlue()
         val startGreen = findGreen()
@@ -506,8 +496,7 @@
         val fragment1 = setupInitialFragment()
 
         // Now do a transition to scene2
-        val fragment2 = TransitionFragment()
-        fragment2.setLayoutId(R.layout.scene2)
+        val fragment2 = TransitionFragment(R.layout.scene2)
 
         verifyTransition(fragment1, fragment2, "blueSquare")
         assertThat(fragment1.exitTransition.getTargets().size).isEqualTo(0)
@@ -532,8 +521,7 @@
     @Test
     fun showHideTransition() {
         val fragment1 = setupInitialFragment()
-        val fragment2 = TransitionFragment()
-        fragment2.setLayoutId(R.layout.scene2)
+        val fragment2 = TransitionFragment(R.layout.scene2)
 
         val startBlue = findBlue()
         val startGreen = findGreen()
@@ -583,8 +571,7 @@
     @Test
     fun attachDetachTransition() {
         val fragment1 = setupInitialFragment()
-        val fragment2 = TransitionFragment()
-        fragment2.setLayoutId(R.layout.scene2)
+        val fragment2 = TransitionFragment(R.layout.scene2)
 
         val startBlue = findBlue()
         val startGreen = findGreen()
@@ -627,8 +614,7 @@
         val fragment1 = setupInitialFragment()
 
         // Now do a transition to scene2
-        val fragment2 = TransitionFragment()
-        fragment2.setLayoutId(R.layout.scene2)
+        val fragment2 = TransitionFragment(R.layout.scene2)
 
         val startBlue = findBlue()
         val startGreen = findGreen()
@@ -697,7 +683,6 @@
         }
         // enter transition
         val fragment = InvisibleFragment()
-        fragment.setLayoutId(R.layout.scene1)
         fragmentManager.beginTransaction()
             .setReorderingAllowed(reorderingAllowed)
             .add(R.id.fragmentContainer, fragment)
@@ -737,8 +722,7 @@
         val startGreen = findGreen()
         val startBlueBounds = getBoundsOnScreen(startBlue)
 
-        val fragment2 = TransitionFragment()
-        fragment2.setLayoutId(R.layout.scene2)
+        val fragment2 = TransitionFragment(R.layout.scene2)
 
         fragmentManager.beginTransaction()
             .setReorderingAllowed(reorderingAllowed)
@@ -758,8 +742,7 @@
         verifyNoOtherTransitions(fragment1)
         verifyNoOtherTransitions(fragment2)
 
-        val fragment3 = TransitionFragment()
-        fragment3.setLayoutId(R.layout.scene3)
+        val fragment3 = TransitionFragment(R.layout.scene3)
 
         activityRule.runOnUiThread {
             val fm = activityRule.activity.supportFragmentManager
@@ -801,8 +784,7 @@
         val startGreen = findGreen()
         val startGreenBounds = getBoundsOnScreen(startGreen)
 
-        val fragment2 = TransitionFragment()
-        fragment2.setLayoutId(R.layout.scene3)
+        val fragment2 = TransitionFragment(R.layout.scene3)
 
         fragmentManager.beginTransaction()
             .setReorderingAllowed(reorderingAllowed)
@@ -843,8 +825,7 @@
     }
 
     private fun setupInitialFragment(): TransitionFragment {
-        val fragment1 = TransitionFragment()
-        fragment1.setLayoutId(R.layout.scene1)
+        val fragment1 = TransitionFragment(R.layout.scene1)
         fragmentManager.beginTransaction()
             .setReorderingAllowed(reorderingAllowed)
             .add(R.id.fragmentContainer, fragment1)
@@ -983,10 +964,8 @@
         val startNumOnBackStackChanged = onBackStackChangedTimes
         val changesPerOperation = if (reorderingAllowed) 1 else 2
 
-        val to1 = TransitionFragment()
-        to1.setLayoutId(R.layout.scene2)
-        val to2 = TransitionFragment()
-        to2.setLayoutId(R.layout.scene2)
+        val to1 = TransitionFragment(R.layout.scene2)
+        val to2 = TransitionFragment(R.layout.scene2)
 
         val fromExit1 = findViewById(from1, R.id.greenSquare)
         val fromShared1 = findViewById(from1, R.id.blueSquare)
@@ -1122,7 +1101,7 @@
         }
     }
 
-    class ComplexTransitionFragment : TransitionFragment() {
+    class ComplexTransitionFragment : TransitionFragment(R.layout.scene2) {
         val sharedElementEnterTransition1 = TrackingTransition()
         val sharedElementEnterTransition2 = TrackingTransition()
         val sharedElementReturnTransition1 = TrackingTransition()
@@ -1145,7 +1124,7 @@
         }
     }
 
-    class InvisibleFragment : TransitionFragment() {
+    class InvisibleFragment : TransitionFragment(R.layout.scene1) {
         override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
             view.visibility = View.INVISIBLE
             super.onViewCreated(view, savedInstanceState)
diff --git a/fragment/src/androidTest/java/androidx/fragment/app/FragmentViewLifecycleTest.kt b/fragment/src/androidTest/java/androidx/fragment/app/FragmentViewLifecycleTest.kt
index a8379f2..be3612c 100644
--- a/fragment/src/androidTest/java/androidx/fragment/app/FragmentViewLifecycleTest.kt
+++ b/fragment/src/androidTest/java/androidx/fragment/app/FragmentViewLifecycleTest.kt
@@ -56,8 +56,7 @@
         val activity = activityRule.activity
         val fm = activity.supportFragmentManager
 
-        val fragment = StrictViewFragment()
-        fragment.setLayoutId(R.layout.fragment_a)
+        val fragment = StrictViewFragment(R.layout.fragment_a)
         fm.beginTransaction().add(R.id.content, fragment).commitNow()
         assertThat(fragment.viewLifecycleOwner.lifecycle.currentState)
             .isEqualTo(Lifecycle.State.RESUMED)
@@ -108,8 +107,7 @@
         val fm = activity.supportFragmentManager
 
         val countDownLatch = CountDownLatch(1)
-        val fragment = StrictViewFragment()
-        fragment.setLayoutId(R.layout.fragment_a)
+        val fragment = StrictViewFragment(R.layout.fragment_a)
         fm.beginTransaction().add(R.id.content, fragment).runOnCommit {
             assertThat(fragment.viewLifecycleOwner.lifecycle.currentState)
                 .isEqualTo(Lifecycle.State.RESUMED)
@@ -124,21 +122,20 @@
         val fm = activity.supportFragmentManager
 
         val countDownLatch = CountDownLatch(2)
-        val fragment = StrictViewFragment()
-        fragment.setLayoutId(R.layout.fragment_a)
+        val fragment = StrictViewFragment(R.layout.fragment_a)
         activityRule.runOnUiThread {
             fragment.viewLifecycleOwnerLiveData.observe(activity,
                 Observer { lifecycleOwner ->
                     if (lifecycleOwner != null) {
                         assertWithMessage("Fragment View LifecycleOwner should be only be set" +
                                 "after onCreateView()")
-                            .that(fragment.mOnCreateViewCalled)
+                            .that(fragment.onCreateViewCalled)
                             .isTrue()
                         countDownLatch.countDown()
                     } else {
                         assertWithMessage("Fragment View LifecycleOwner should be set to null" +
                                 " after onDestroyView()")
-                            .that(fragment.mOnDestroyViewCalled)
+                            .that(fragment.onDestroyViewCalled)
                             .isTrue()
                         countDownLatch.countDown()
                     }
@@ -155,8 +152,7 @@
         val activity = activityRule.activity
         val fm = activity.supportFragmentManager
 
-        val fragment = StrictViewFragment()
-        fragment.setLayoutId(R.layout.fragment_a)
+        val fragment = StrictViewFragment(R.layout.fragment_a)
         val lifecycleObserver = mock(LifecycleEventObserver::class.java)
         lateinit var viewLifecycleOwner: LifecycleOwner
         activityRule.runOnUiThread {
@@ -197,7 +193,6 @@
         val fm = activity.supportFragmentManager
 
         val fragment = ObservingFragment()
-        fragment.setLayoutId(R.layout.fragment_a)
         fm.beginTransaction().add(R.id.content, fragment).commitNow()
         val viewLifecycleOwner = fragment.viewLifecycleOwner
         assertThat(viewLifecycleOwner.lifecycle.currentState).isEqualTo(Lifecycle.State.RESUMED)
@@ -225,7 +220,6 @@
         val fm = activity.supportFragmentManager
 
         val fragment = ObservingFragment()
-        fragment.setLayoutId(R.layout.fragment_a)
         fm.beginTransaction().add(R.id.content, fragment).commitNow()
         val viewLifecycleOwner = fragment.viewLifecycleOwner
         assertThat(viewLifecycleOwner.lifecycle.currentState).isEqualTo(Lifecycle.State.RESUMED)
@@ -264,7 +258,7 @@
         }
     }
 
-    class ObservingFragment : StrictViewFragment() {
+    class ObservingFragment : StrictViewFragment(R.layout.fragment_a) {
         val liveData = MutableLiveData<Boolean>()
         private val onCreateViewObserver = Observer<Boolean> { }
         private val onViewCreatedObserver = Observer<Boolean> { }
diff --git a/fragment/src/androidTest/java/androidx/fragment/app/FragmentViewTest.kt b/fragment/src/androidTest/java/androidx/fragment/app/FragmentViewTest.kt
index 33e0ed38..c1d4d40 100644
--- a/fragment/src/androidTest/java/androidx/fragment/app/FragmentViewTest.kt
+++ b/fragment/src/androidTest/java/androidx/fragment/app/FragmentViewTest.kt
@@ -783,8 +783,7 @@
     fun testReplaceFragment() {
         FragmentTestUtil.setContentView(activityRule, R.layout.simple_container)
         val fm = activityRule.activity.supportFragmentManager
-        val fragmentA = StrictViewFragment()
-        fragmentA.setLayoutId(R.layout.text_a)
+        val fragmentA = StrictViewFragment(R.layout.text_a)
 
         fm.beginTransaction()
             .add(R.id.fragmentContainer, fragmentA)
@@ -796,8 +795,7 @@
         assertThat(findViewById(R.id.textB)).isNull()
         assertThat(findViewById(R.id.textC)).isNull()
 
-        val fragmentB = StrictViewFragment()
-        fragmentB.setLayoutId(R.layout.text_b)
+        val fragmentB = StrictViewFragment(R.layout.text_b)
         fm.beginTransaction()
             .add(R.id.fragmentContainer, fragmentB)
             .addToBackStack(null)
@@ -807,8 +805,7 @@
         assertThat(findViewById(R.id.textB)).isNotNull()
         assertThat(findViewById(R.id.textC)).isNull()
 
-        val fragmentC = StrictViewFragment()
-        fragmentC.setLayoutId(R.layout.text_c)
+        val fragmentC = StrictViewFragment(R.layout.text_c)
         fm.beginTransaction()
             .replace(R.id.fragmentContainer, fragmentC)
             .addToBackStack(null)
@@ -891,10 +888,8 @@
             activityRule.activity.findViewById<View>(R.id.fragmentContainer) as ViewGroup
         val fm = activityRule.activity.supportFragmentManager
 
-        val fragment1 = StrictViewFragment()
-        fragment1.setLayoutId(R.layout.scene1)
-        val fragment2 = StrictViewFragment()
-        fragment2.setLayoutId(R.layout.fragment_a)
+        val fragment1 = StrictViewFragment(R.layout.scene1)
+        val fragment2 = StrictViewFragment(R.layout.fragment_a)
 
         activityRule.runOnUiThread {
             fm.beginTransaction()
@@ -923,7 +918,6 @@
         val fm = activityRule.activity.supportFragmentManager
 
         val fragment1 = ParentFragment()
-        fragment1.setLayoutId(R.layout.double_container)
 
         fm.beginTransaction()
             .add(R.id.fragmentContainer, fragment1)
@@ -1017,18 +1011,14 @@
         }
     }
 
-    class ParentFragment : StrictViewFragment() {
-        init {
-            setLayoutId(R.layout.double_container)
-        }
+    class ParentFragment : StrictViewFragment(R.layout.double_container) {
 
         override fun onCreateView(
             inflater: LayoutInflater,
             container: ViewGroup?,
             savedInstanceState: Bundle?
         ): View? {
-            val fragment2 = StrictViewFragment()
-            fragment2.setLayoutId(R.layout.fragment_a)
+            val fragment2 = StrictViewFragment(R.layout.fragment_a)
 
             childFragmentManager.beginTransaction()
                 .add(R.id.fragmentContainer1, fragment2, "inner")
diff --git a/fragment/src/androidTest/java/androidx/fragment/app/LoaderTest.kt b/fragment/src/androidTest/java/androidx/fragment/app/LoaderTest.kt
index 7c17551..4242a3b 100644
--- a/fragment/src/androidTest/java/androidx/fragment/app/LoaderTest.kt
+++ b/fragment/src/androidTest/java/androidx/fragment/app/LoaderTest.kt
@@ -64,7 +64,7 @@
 
         FragmentTestUtil.executePendingTransactions(activityRule, fm)
 
-        val weakActivity = WeakReference(LoaderActivity.sActivity)
+        val weakActivity = WeakReference(LoaderActivity.activity)
 
         // Wait for everything to settle. We have to make sure that the old Activity
         // is ready to be collected.
diff --git a/fragment/src/androidTest/java/androidx/fragment/app/NestedFragmentRestoreTest.kt b/fragment/src/androidTest/java/androidx/fragment/app/NestedFragmentRestoreTest.kt
index 7000783..ec335ed 100644
--- a/fragment/src/androidTest/java/androidx/fragment/app/NestedFragmentRestoreTest.kt
+++ b/fragment/src/androidTest/java/androidx/fragment/app/NestedFragmentRestoreTest.kt
@@ -41,7 +41,7 @@
         val activity = activityRule.activity
         activityRule.runOnUiThread {
             val parent = ParentFragment()
-            parent.setRetainChildInstance(true)
+            parent.retainChildInstance = true
 
             activity.supportFragmentManager.beginTransaction()
                 .add(parent, "parent")
@@ -54,7 +54,7 @@
 
         var attachedTo: Context? = null
         val latch = CountDownLatch(1)
-        child.setOnAttachListener { context, _ ->
+        child.onAttachListener = { context ->
             attachedTo = context
             latch.countDown()
         }
diff --git a/fragment/src/androidTest/java/androidx/fragment/app/PostponedTransitionTest.kt b/fragment/src/androidTest/java/androidx/fragment/app/PostponedTransitionTest.kt
index 782fa5d..5432969 100644
--- a/fragment/src/androidTest/java/androidx/fragment/app/PostponedTransitionTest.kt
+++ b/fragment/src/androidTest/java/androidx/fragment/app/PostponedTransitionTest.kt
@@ -20,7 +20,6 @@
 import android.view.LayoutInflater
 import android.view.View
 import android.view.ViewGroup
-import androidx.annotation.ContentView
 import androidx.fragment.app.test.FragmentTestActivity
 import androidx.fragment.test.R
 import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -116,7 +115,7 @@
         val fm = activityRule.activity.supportFragmentManager
         var startBlue = activityRule.activity.findViewById<View>(R.id.blueSquare)
 
-        val fragment1 = TransitionFragment2()
+        val fragment1 = TransitionFragment(R.layout.scene2)
         fm.beginTransaction()
             .addSharedElement(startBlue, "blueSquare")
             .replace(R.id.fragmentContainer, fragment1)
@@ -140,7 +139,7 @@
 
         startBlue = activityRule.activity.findViewById(R.id.blueSquare)
 
-        val fragment2 = TransitionFragment2()
+        val fragment2 = TransitionFragment(R.layout.scene2)
         fm.beginTransaction()
             .addSharedElement(startBlue, "blueSquare")
             .replace(R.id.fragmentContainer, fragment2)
@@ -906,8 +905,7 @@
         assertThat(fragment.sharedElementReturn.getTargets().isEmpty()).isTrue()
     }
 
-    @ContentView(R.layout.scene1)
-    open class PostponedFragment1 : TransitionFragment() {
+    open class PostponedFragment1 : TransitionFragment(R.layout.scene1) {
         override fun onCreateView(
             inflater: LayoutInflater,
             container: ViewGroup?,
@@ -917,8 +915,7 @@
         }
     }
 
-    @ContentView(R.layout.scene2)
-    class PostponedFragment2 : TransitionFragment() {
+    class PostponedFragment2 : TransitionFragment(R.layout.scene2) {
         override fun onCreateView(
             inflater: LayoutInflater,
             container: ViewGroup?,
@@ -937,7 +934,4 @@
                 .commitNow()
         }
     }
-
-    @ContentView(R.layout.scene2)
-    class TransitionFragment2 : TransitionFragment()
 }
diff --git a/fragment/src/androidTest/java/androidx/fragment/app/ReentrantFragment.java b/fragment/src/androidTest/java/androidx/fragment/app/ReentrantFragment.java
index ae70d7f..8404459 100644
--- a/fragment/src/androidTest/java/androidx/fragment/app/ReentrantFragment.java
+++ b/fragment/src/androidTest/java/androidx/fragment/app/ReentrantFragment.java
@@ -37,7 +37,7 @@
     public void onStateChanged(int fromState) {
         super.onStateChanged(fromState);
         // We execute the transaction when shutting down or after restoring
-        if (fromState == mFromState && mState == mToState
+        if (fromState == mFromState && getCurrentState() == mToState
                 && (mToState < mFromState || mIsRestored)) {
             executeTransaction();
         }
diff --git a/fragment/src/androidTest/java/androidx/fragment/app/SaveStateFragmentTest.kt b/fragment/src/androidTest/java/androidx/fragment/app/SaveStateFragmentTest.kt
new file mode 100644
index 0000000..304d0bf
--- /dev/null
+++ b/fragment/src/androidTest/java/androidx/fragment/app/SaveStateFragmentTest.kt
@@ -0,0 +1,718 @@
+/*
+ * 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.fragment.app
+
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.fragment.app.FragmentTestUtil.HostCallbacks
+import androidx.fragment.app.FragmentTestUtil.createController
+import androidx.fragment.app.FragmentTestUtil.destroy
+import androidx.fragment.app.FragmentTestUtil.restartFragmentController
+import androidx.fragment.app.FragmentTestUtil.resume
+import androidx.fragment.app.FragmentTestUtil.shutdownFragmentController
+import androidx.fragment.app.FragmentTestUtil.startupFragmentController
+import androidx.fragment.app.test.EmptyFragmentTestActivity
+import androidx.fragment.test.R
+import androidx.lifecycle.ViewModelStore
+import androidx.test.annotation.UiThreadTest
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.MediumTest
+import androidx.test.rule.ActivityTestRule
+import com.google.common.truth.Truth.assertThat
+import com.google.common.truth.Truth.assertWithMessage
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+@MediumTest
+class SaveStateFragmentTest {
+
+    @get:Rule
+    val activityRule = ActivityTestRule(EmptyFragmentTestActivity::class.java)
+
+    @Test
+    @UiThreadTest
+    fun setInitialSavedState() {
+        val fm = activityRule.activity.supportFragmentManager
+
+        // Add a StateSaveFragment
+        var fragment = StateSaveFragment("Saved", "")
+        fm.beginTransaction().add(fragment, "tag").commit()
+        executePendingTransactions(fm)
+
+        // Change the user visible hint before we save state
+        fragment.userVisibleHint = false
+
+        // Save its state and remove it
+        val state = fm.saveFragmentInstanceState(fragment)
+        fm.beginTransaction().remove(fragment).commit()
+        executePendingTransactions(fm)
+
+        // Create a new instance, calling setInitialSavedState
+        fragment = StateSaveFragment("", "")
+        fragment.setInitialSavedState(state)
+
+        // Add the new instance
+        fm.beginTransaction().add(fragment, "tag").commit()
+        executePendingTransactions(fm)
+
+        assertWithMessage("setInitialSavedState did not restore saved state")
+            .that(fragment.savedState).isEqualTo("Saved")
+        assertWithMessage("setInitialSavedState did not restore user visible hint")
+            .that(fragment.userVisibleHint).isEqualTo(false)
+    }
+
+    @Test
+    @UiThreadTest
+    fun setInitialSavedStateWithSetUserVisibleHint() {
+        val fm = activityRule.activity.supportFragmentManager
+
+        // Add a StateSaveFragment
+        var fragment = StateSaveFragment("Saved", "")
+        fm.beginTransaction().add(fragment, "tag").commit()
+        executePendingTransactions(fm)
+
+        // Save its state and remove it
+        val state = fm.saveFragmentInstanceState(fragment)
+        fm.beginTransaction().remove(fragment).commit()
+        executePendingTransactions(fm)
+
+        // Create a new instance, calling setInitialSavedState
+        fragment = StateSaveFragment("", "")
+        fragment.setInitialSavedState(state)
+
+        // Change the user visible hint after we call setInitialSavedState
+        fragment.userVisibleHint = false
+
+        // Add the new instance
+        fm.beginTransaction().add(fragment, "tag").commit()
+        executePendingTransactions(fm)
+
+        assertWithMessage("setInitialSavedState did not restore saved state")
+            .that(fragment.savedState).isEqualTo("Saved")
+        assertWithMessage("setUserVisibleHint should override setInitialSavedState")
+            .that(fragment.userVisibleHint).isEqualTo(false)
+    }
+
+    @Test
+    @UiThreadTest
+    fun restoreRetainedInstanceFragments() {
+        // Create a new FragmentManager in isolation, nest some assorted fragments
+        // and then restore them to a second new FragmentManager.
+        val viewModelStore = ViewModelStore()
+        val fc1 = FragmentController.createController(
+            HostCallbacks(activityRule.activity, viewModelStore)
+        )
+
+        val fm1 = fc1.supportFragmentManager
+
+        fc1.attachHost(null)
+        fc1.dispatchCreate()
+
+        // Configure fragments.
+
+        // This retained fragment will be added, then removed. After being removed, it
+        // should no longer be retained by the FragmentManager
+        val removedFragment = StateSaveFragment("Removed", "UnsavedRemoved")
+        removedFragment.retainInstance = true
+        fm1.beginTransaction().add(removedFragment, "tag:removed").commitNow()
+        fm1.beginTransaction().remove(removedFragment).commitNow()
+
+        // This retained fragment will be added, then detached. After being detached, it
+        // should continue to be retained by the FragmentManager
+        val detachedFragment = StateSaveFragment("Detached", "UnsavedDetached")
+        removedFragment.retainInstance = true
+        fm1.beginTransaction().add(detachedFragment, "tag:detached").commitNow()
+        fm1.beginTransaction().detach(detachedFragment).commitNow()
+
+        // Grandparent fragment will not retain instance
+        val grandparentFragment = StateSaveFragment("Grandparent", "UnsavedGrandparent")
+        assertWithMessage("grandparent fragment saved state not initialized")
+            .that(grandparentFragment.savedState).isNotNull()
+        assertWithMessage("grandparent fragment unsaved state not initialized")
+            .that(grandparentFragment.unsavedState).isNotNull()
+        fm1.beginTransaction().add(grandparentFragment, "tag:grandparent").commitNow()
+
+        // Parent fragment will retain instance
+        val parentFragment = StateSaveFragment("Parent", "UnsavedParent")
+        assertWithMessage("parent fragment saved state not initialized")
+            .that(parentFragment.savedState).isNotNull()
+        assertWithMessage("parent fragment unsaved state not initialized")
+            .that(parentFragment.unsavedState).isNotNull()
+        parentFragment.retainInstance = true
+        grandparentFragment.childFragmentManager.beginTransaction()
+            .add(parentFragment, "tag:parent").commitNow()
+        assertWithMessage("parent fragment is not a child of grandparent")
+            .that(parentFragment.parentFragment).isSameAs(grandparentFragment)
+
+        // Child fragment will not retain instance
+        val childFragment = StateSaveFragment("Child", "UnsavedChild")
+        assertWithMessage("child fragment saved state not initialized")
+            .that(childFragment.savedState).isNotNull()
+        assertWithMessage("child fragment unsaved state not initialized")
+            .that(childFragment.unsavedState).isNotNull()
+        parentFragment.childFragmentManager.beginTransaction()
+            .add(childFragment, "tag:child").commitNow()
+        assertWithMessage("child fragment is not a child of grandparent")
+            .that(childFragment.parentFragment).isSameAs(parentFragment)
+
+        // Saved for comparison later
+        val parentChildFragmentManager = parentFragment.childFragmentManager
+
+        fc1.dispatchActivityCreated()
+        fc1.noteStateNotSaved()
+        fc1.execPendingActions()
+        fc1.dispatchStart()
+        fc1.dispatchResume()
+        fc1.execPendingActions()
+
+        // Bring the state back down to destroyed, simulating an activity restart
+        fc1.dispatchPause()
+        val savedState = fc1.saveAllState()
+        fc1.dispatchStop()
+        fc1.dispatchDestroy()
+
+        // Create the new controller and restore state
+        val fc2 = FragmentController.createController(
+            HostCallbacks(activityRule.activity, viewModelStore)
+        )
+
+        val fm2 = fc2.supportFragmentManager
+
+        fc2.attachHost(null)
+        fc2.restoreSaveState(savedState)
+        fc2.dispatchCreate()
+
+        // Confirm that the restored fragments are available and in the expected states
+        val restoredRemovedFragment = fm2.findFragmentByTag("tag:removed") as StateSaveFragment?
+        assertThat(restoredRemovedFragment).isNull()
+        assertWithMessage("Removed Fragment should be destroyed")
+            .that(removedFragment.calledOnDestroy).isTrue()
+
+        val restoredDetachedFragment = fm2.findFragmentByTag("tag:detached") as StateSaveFragment
+        assertThat(restoredDetachedFragment).isNotNull()
+
+        val restoredGrandparent = fm2.findFragmentByTag("tag:grandparent") as StateSaveFragment
+        assertWithMessage("grandparent fragment not restored").that(restoredGrandparent).isNotNull()
+
+        assertWithMessage("grandparent fragment instance was saved")
+            .that(restoredGrandparent).isNotSameAs(grandparentFragment)
+        assertWithMessage("grandparent fragment saved state was not equal")
+            .that(restoredGrandparent.savedState).isEqualTo(grandparentFragment.savedState)
+        assertWithMessage("grandparent fragment unsaved state was unexpectedly preserved")
+            .that(restoredGrandparent.unsavedState).isNotEqualTo(grandparentFragment.unsavedState)
+
+        val restoredParent = restoredGrandparent
+            .childFragmentManager.findFragmentByTag("tag:parent") as StateSaveFragment
+        assertWithMessage("parent fragment not restored").that(restoredParent).isNotNull()
+
+        assertWithMessage("parent fragment instance was not saved")
+            .that(restoredParent).isSameAs(parentFragment)
+        assertWithMessage("parent fragment saved state was not equal")
+            .that(restoredParent.savedState).isEqualTo(parentFragment.savedState)
+        assertWithMessage("parent fragment unsaved state was not equal")
+            .that(restoredParent.unsavedState).isEqualTo(parentFragment.unsavedState)
+        assertWithMessage("parent fragment has the same child FragmentManager")
+            .that(restoredParent.childFragmentManager).isNotSameAs(parentChildFragmentManager)
+
+        val restoredChild = restoredParent
+            .childFragmentManager.findFragmentByTag("tag:child") as StateSaveFragment
+        assertWithMessage("child fragment not restored").that(restoredChild).isNotNull()
+
+        assertWithMessage("child fragment instance state was saved")
+            .that(restoredChild).isNotSameAs(childFragment)
+        assertWithMessage("child fragment saved state was not equal")
+            .that(restoredChild.savedState).isEqualTo(childFragment.savedState)
+        assertWithMessage("child fragment saved state was unexpectedly equal")
+            .that(restoredChild.unsavedState).isNotEqualTo(childFragment.unsavedState)
+
+        fc2.dispatchActivityCreated()
+        fc2.noteStateNotSaved()
+        fc2.execPendingActions()
+        fc2.dispatchStart()
+        fc2.dispatchResume()
+        fc2.execPendingActions()
+
+        // Test that the fragments are in the configuration we expect
+
+        // Bring the state back down to destroyed before we finish the test
+        shutdownFragmentController(fc2, viewModelStore)
+
+        assertWithMessage("grandparent not destroyed")
+            .that(restoredGrandparent.calledOnDestroy).isTrue()
+        assertWithMessage("parent not destroyed").that(restoredParent.calledOnDestroy).isTrue()
+        assertWithMessage("child not destroyed").that(restoredChild.calledOnDestroy).isTrue()
+    }
+
+    @Test
+    @UiThreadTest
+    fun restoreRetainedInstanceFragmentWithTransparentActivityConfigChange() {
+        // Create a new FragmentManager in isolation, add a retained instance Fragment,
+        // then mimic the following scenario:
+        // 1. Activity A adds retained Fragment F
+        // 2. Activity A starts translucent Activity B
+        // 3. Activity B start opaque Activity C
+        // 4. Rotate phone
+        // 5. Finish Activity C
+        // 6. Finish Activity B
+
+        val viewModelStore = ViewModelStore()
+        val fc1 = FragmentController.createController(
+            HostCallbacks(activityRule.activity, viewModelStore)
+        )
+
+        val fm1 = fc1.supportFragmentManager
+
+        fc1.attachHost(null)
+        fc1.dispatchCreate()
+
+        // Add the retained Fragment
+        val retainedFragment = StateSaveFragment("Retained", "UnsavedRetained")
+        retainedFragment.retainInstance = true
+        fm1.beginTransaction().add(retainedFragment, "tag:retained").commitNow()
+
+        // Move the activity to resumed
+        fc1.dispatchActivityCreated()
+        fc1.noteStateNotSaved()
+        fc1.execPendingActions()
+        fc1.dispatchStart()
+        fc1.dispatchResume()
+        fc1.execPendingActions()
+
+        // Launch the transparent activity on top
+        fc1.dispatchPause()
+
+        // Launch the opaque activity on top
+        val savedState = fc1.saveAllState()
+        fc1.dispatchStop()
+
+        // Finish the opaque activity, making our Activity visible i.e., started
+        fc1.noteStateNotSaved()
+        fc1.execPendingActions()
+        fc1.dispatchStart()
+
+        // Finish the transparent activity, causing a config change
+        fc1.dispatchStop()
+        fc1.dispatchDestroy()
+
+        // Create the new controller and restore state
+        val fc2 = FragmentController.createController(
+            HostCallbacks(activityRule.activity, viewModelStore)
+        )
+
+        val fm2 = fc2.supportFragmentManager
+
+        fc2.attachHost(null)
+        fc2.restoreSaveState(savedState)
+        fc2.dispatchCreate()
+
+        val restoredFragment = fm2.findFragmentByTag("tag:retained") as StateSaveFragment
+        assertWithMessage("retained fragment not restored").that(restoredFragment).isNotNull()
+        assertWithMessage("The retained Fragment shouldn't be recreated")
+            .that(restoredFragment).isEqualTo(retainedFragment)
+
+        fc2.dispatchActivityCreated()
+        fc2.noteStateNotSaved()
+        fc2.execPendingActions()
+        fc2.dispatchStart()
+        fc2.dispatchResume()
+        fc2.execPendingActions()
+
+        // Bring the state back down to destroyed before we finish the test
+        shutdownFragmentController(fc2, viewModelStore)
+    }
+
+    @Test
+    @UiThreadTest
+    fun testSavedInstanceStateAfterRestore() {
+
+        val viewModelStore = ViewModelStore()
+        val fc1 =
+            startupFragmentController(activityRule.activity, null, viewModelStore)
+        val fm1 = fc1.supportFragmentManager
+
+        // Add the initial state
+        val parentFragment = StrictFragment()
+        parentFragment.retainInstance = true
+        val childFragment = StrictFragment()
+        fm1.beginTransaction().add(parentFragment, "parent").commitNow()
+        val childFragmentManager = parentFragment.childFragmentManager
+        childFragmentManager.beginTransaction().add(childFragment, "child").commitNow()
+
+        // Confirm the initial state
+        assertWithMessage("Initial parent saved instance state should be null")
+            .that(parentFragment.lastSavedInstanceState).isNull()
+        assertWithMessage("Initial child saved instance state should be null")
+            .that(childFragment.lastSavedInstanceState).isNull()
+
+        // Bring the state back down to destroyed, simulating an activity restart
+        fc1.dispatchPause()
+        val savedState = fc1.saveAllState()
+        fc1.dispatchStop()
+        fc1.dispatchDestroy()
+
+        // Create the new controller and restore state
+        val fc2 = startupFragmentController(
+            activityRule.activity,
+            savedState,
+            viewModelStore
+        )
+        val fm2 = fc2.supportFragmentManager
+
+        val restoredParentFragment = fm2.findFragmentByTag("parent") as StrictFragment
+        assertWithMessage("Parent fragment was not restored")
+            .that(restoredParentFragment).isNotNull()
+        val restoredChildFragment = restoredParentFragment
+            .childFragmentManager.findFragmentByTag("child") as StrictFragment
+        assertWithMessage("Child fragment was not restored").that(restoredChildFragment).isNotNull()
+
+        assertWithMessage("Parent fragment saved instance state should still be null since it is " +
+                "a retained Fragment").that(restoredParentFragment.lastSavedInstanceState).isNull()
+        assertWithMessage("Child fragment saved instance state should be non-null")
+            .that(restoredChildFragment.lastSavedInstanceState).isNotNull()
+
+        // Bring the state back down to destroyed before we finish the test
+        shutdownFragmentController(fc2, viewModelStore)
+    }
+
+    @Test
+    @UiThreadTest
+    fun restoreNestedFragmentsOnBackStack() {
+        val viewModelStore = ViewModelStore()
+        val fc1 = FragmentController.createController(
+            HostCallbacks(activityRule.activity, viewModelStore)
+        )
+
+        val fm1 = fc1.supportFragmentManager
+
+        fc1.attachHost(null)
+        fc1.dispatchCreate()
+
+        // Add the initial state
+        val parentFragment = StrictFragment()
+        val childFragment = StrictFragment()
+        fm1.beginTransaction().add(parentFragment, "parent").commitNow()
+        val childFragmentManager = parentFragment.childFragmentManager
+        childFragmentManager.beginTransaction().add(childFragment, "child").commitNow()
+
+        // Now add a Fragment to the back stack
+        val replacementChildFragment = StrictFragment()
+        childFragmentManager.beginTransaction()
+            .remove(childFragment)
+            .add(replacementChildFragment, "child")
+            .addToBackStack("back_stack").commit()
+        childFragmentManager.executePendingTransactions()
+
+        // Move the activity to resumed
+        fc1.dispatchActivityCreated()
+        fc1.noteStateNotSaved()
+        fc1.execPendingActions()
+        fc1.dispatchStart()
+        fc1.dispatchResume()
+        fc1.execPendingActions()
+
+        // Now bring the state back down
+        fc1.dispatchPause()
+        val savedState = fc1.saveAllState()
+        fc1.dispatchStop()
+        fc1.dispatchDestroy()
+
+        // Create the new controller and restore state
+        val fc2 = FragmentController.createController(
+            HostCallbacks(activityRule.activity, viewModelStore)
+        )
+
+        val fm2 = fc2.supportFragmentManager
+
+        fc2.attachHost(null)
+        fc2.restoreSaveState(savedState)
+        fc2.dispatchCreate()
+
+        val restoredParentFragment = fm2.findFragmentByTag("parent") as StrictFragment
+        assertWithMessage("Parent fragment was not restored")
+            .that(restoredParentFragment).isNotNull()
+        val restoredChildFragment = restoredParentFragment
+            .childFragmentManager.findFragmentByTag("child") as StrictFragment
+        assertWithMessage("Child fragment was not restored").that(restoredChildFragment).isNotNull()
+
+        fc2.dispatchActivityCreated()
+        fc2.noteStateNotSaved()
+        fc2.execPendingActions()
+        fc2.dispatchStart()
+        fc2.dispatchResume()
+        fc2.execPendingActions()
+
+        // Bring the state back down to destroyed before we finish the test
+        shutdownFragmentController(fc2, viewModelStore)
+    }
+
+    /**
+     * When a fragment has been optimized out, it state should still be saved during
+     * save and restore instance state.
+     */
+    @Test
+    @UiThreadTest
+    fun saveRemovedFragment() {
+        var fc = createController(activityRule)
+        resume(activityRule, fc, null)
+        var fm = fc.supportFragmentManager
+
+        var fragment1 = SaveStateFragment.create(1)
+        fm.beginTransaction()
+            .add(android.R.id.content, fragment1, "1")
+            .addToBackStack(null)
+            .commit()
+        var fragment2 = SaveStateFragment.create(2)
+        fm.beginTransaction()
+            .replace(android.R.id.content, fragment2, "2")
+            .addToBackStack(null)
+            .commit()
+        fm.executePendingTransactions()
+
+        val savedState = destroy(activityRule, fc)
+
+        fc = createController(activityRule)
+        resume(activityRule, fc, savedState)
+        fm = fc.supportFragmentManager
+        fragment2 = fm.findFragmentByTag("2") as SaveStateFragment
+        assertThat(fragment2).isNotNull()
+        assertThat(fragment2.value).isEqualTo(2)
+        fm.popBackStackImmediate()
+        fragment1 = fm.findFragmentByTag("1") as SaveStateFragment
+        assertThat(fragment1).isNotNull()
+        assertThat(fragment1.value).isEqualTo(1)
+    }
+
+    /**
+     * Test to ensure that when dispatch* is called that the fragment manager
+     * doesn't cause the contained fragment states to change even if no state changes.
+     */
+    @Test
+    @UiThreadTest
+    fun noPrematureStateChange() {
+        val viewModelStore = ViewModelStore()
+        var fc =
+            startupFragmentController(activityRule.activity, null, viewModelStore)
+        var fm = fc.supportFragmentManager
+
+        fm.beginTransaction().add(StrictFragment(), "1").commitNow()
+
+        fc = restartFragmentController(activityRule.activity, fc, viewModelStore)
+
+        fm = fc.supportFragmentManager
+
+        val fragment1 = fm.findFragmentByTag("1") as StrictFragment
+        assertWithMessage("Fragment should be resumed after restart")
+            .that(fragment1.calledOnResume).isTrue()
+        fragment1.calledOnResume = false
+        fc.dispatchResume()
+
+        assertWithMessage("Fragment should not get onResume() after second dispatchResume()")
+            .that(fragment1.calledOnResume).isFalse()
+    }
+
+    @Test
+    @UiThreadTest
+    fun testIsStateSaved() {
+        val viewModelStore = ViewModelStore()
+        val fc =
+            startupFragmentController(activityRule.activity, null, viewModelStore)
+        val fm = fc.supportFragmentManager
+
+        val f = StrictFragment()
+        fm.beginTransaction().add(f, "1").commitNow()
+
+        assertWithMessage("fragment reported state saved while resumed")
+            .that(f.isStateSaved).isFalse()
+
+        fc.dispatchPause()
+        fc.saveAllState()
+
+        assertWithMessage("fragment reported state not saved after saveAllState")
+            .that(f.isStateSaved).isTrue()
+
+        fc.dispatchStop()
+
+        assertWithMessage("fragment reported state not saved after stop")
+            .that(f.isStateSaved).isTrue()
+
+        viewModelStore.clear()
+        fc.dispatchDestroy()
+
+        assertWithMessage("fragment reported state saved after destroy")
+            .that(f.isStateSaved).isFalse()
+    }
+
+    @Test
+    @UiThreadTest
+    fun saveAnimationState() {
+        val viewModelStore = ViewModelStore()
+        var fc = startupFragmentController(
+            activityRule.activity, null,
+            viewModelStore
+        )
+        var fm = fc.supportFragmentManager
+
+        fm.beginTransaction()
+            .setCustomAnimations(0, R.anim.fade_out, R.anim.fade_in, R.anim.fade_out)
+            .add(android.R.id.content, SimpleFragment.create(R.layout.fragment_a))
+            .addToBackStack(null)
+            .commit()
+        fm.executePendingTransactions()
+
+        assertAnimationsMatch(fm, 0, R.anim.fade_out, R.anim.fade_in, R.anim.fade_out)
+
+        // Causes save and restore of fragments and back stack
+        fc = restartFragmentController(activityRule.activity, fc, viewModelStore)
+        fm = fc.supportFragmentManager
+
+        assertAnimationsMatch(fm, 0, R.anim.fade_out, R.anim.fade_in, R.anim.fade_out)
+
+        fm.beginTransaction()
+            .setCustomAnimations(R.anim.fade_in, R.anim.fade_out, 0, 0)
+            .replace(android.R.id.content, SimpleFragment.create(R.layout.fragment_b))
+            .addToBackStack(null)
+            .commit()
+        fm.executePendingTransactions()
+
+        assertAnimationsMatch(fm, R.anim.fade_in, R.anim.fade_out, 0, 0)
+
+        // Causes save and restore of fragments and back stack
+        fc = restartFragmentController(activityRule.activity, fc, viewModelStore)
+        fm = fc.supportFragmentManager
+
+        assertAnimationsMatch(fm, R.anim.fade_in, R.anim.fade_out, 0, 0)
+
+        fm.popBackStackImmediate()
+
+        assertAnimationsMatch(fm, 0, R.anim.fade_out, R.anim.fade_in, R.anim.fade_out)
+
+        shutdownFragmentController(fc, viewModelStore)
+    }
+
+    private fun executePendingTransactions(fm: FragmentManager) {
+        activityRule.runOnUiThread { fm.executePendingTransactions() }
+    }
+
+    private fun assertAnimationsMatch(
+        fm: FragmentManager,
+        enter: Int,
+        exit: Int,
+        popEnter: Int,
+        popExit: Int
+    ) {
+        val fmImpl = fm as FragmentManagerImpl
+        val record = fmImpl.mBackStack[fmImpl.mBackStack.size - 1]
+
+        assertThat(record.mEnterAnim).isEqualTo(enter)
+        assertThat(record.mExitAnim).isEqualTo(exit)
+        assertThat(record.mPopEnterAnim).isEqualTo(popEnter)
+        assertThat(record.mPopExitAnim).isEqualTo(popExit)
+    }
+
+    class SimpleFragment : Fragment() {
+        private var layoutId: Int = 0
+
+        override fun onCreate(savedInstanceState: Bundle?) {
+            super.onCreate(savedInstanceState)
+            if (savedInstanceState != null) {
+                layoutId = savedInstanceState.getInt(LAYOUT_ID, layoutId)
+            }
+        }
+
+        override fun onSaveInstanceState(outState: Bundle) {
+            super.onSaveInstanceState(outState)
+            outState.putInt(LAYOUT_ID, layoutId)
+        }
+
+        override fun onCreateView(
+            inflater: LayoutInflater,
+            container: ViewGroup?,
+            savedInstanceState: Bundle?
+        ): View = inflater.inflate(layoutId, container, false)
+
+        companion object {
+            private const val LAYOUT_ID = "layoutId"
+
+            fun create(layoutId: Int): SimpleFragment {
+                val fragment = SimpleFragment()
+                fragment.layoutId = layoutId
+                return fragment
+            }
+        }
+    }
+
+    class SaveStateFragment : Fragment() {
+        var value: Int = 0
+            private set
+
+        override fun onSaveInstanceState(outState: Bundle) {
+            super.onSaveInstanceState(outState)
+            outState.putInt(VALUE_KEY, value)
+        }
+
+        override fun onCreate(savedInstanceState: Bundle?) {
+            super.onCreate(savedInstanceState)
+            if (savedInstanceState != null) {
+                value = savedInstanceState.getInt(VALUE_KEY, value)
+            }
+        }
+
+        companion object {
+            private const val VALUE_KEY = "SaveStateFragment.mValue"
+
+            fun create(value: Int): SaveStateFragment {
+                val saveStateFragment = SaveStateFragment()
+                saveStateFragment.value = value
+                return saveStateFragment
+            }
+        }
+    }
+
+    class StateSaveFragment : StrictFragment {
+
+        var savedState: String? = null
+            private set
+        var unsavedState: String? = null
+
+        constructor()
+
+        constructor(savedState: String, unsavedState: String) {
+            this.savedState = savedState
+            this.unsavedState = unsavedState
+        }
+
+        override fun onCreate(savedInstanceState: Bundle?) {
+            super.onCreate(savedInstanceState)
+            if (savedInstanceState != null) {
+                savedState = savedInstanceState.getString(STATE_KEY)
+            }
+        }
+
+        override fun onSaveInstanceState(outState: Bundle) {
+            super.onSaveInstanceState(outState)
+            outState.putString(STATE_KEY, savedState)
+        }
+
+        companion object {
+            private const val STATE_KEY = "state"
+        }
+    }
+}
\ No newline at end of file
diff --git a/fragment/src/androidTest/java/androidx/fragment/app/StrictFragment.java b/fragment/src/androidTest/java/androidx/fragment/app/StrictFragment.java
deleted file mode 100644
index 2acbeda..0000000
--- a/fragment/src/androidTest/java/androidx/fragment/app/StrictFragment.java
+++ /dev/null
@@ -1,191 +0,0 @@
-/*
- * Copyright 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-
-package androidx.fragment.app;
-
-import android.content.Context;
-import android.os.Bundle;
-
-/**
- * This fragment watches its primary lifecycle events and throws IllegalStateException
- * if any of them are called out of order or from a bad/unexpected state.
- */
-public class StrictFragment extends Fragment {
-    public static final int DETACHED = 0;
-    public static final int ATTACHED = 1;
-    public static final int CREATED = 2;
-    public static final int ACTIVITY_CREATED = 3;
-    public static final int STARTED = 4;
-    public static final int RESUMED = 5;
-
-    int mState;
-
-    boolean mCalledOnAttach, mCalledOnCreate, mCalledOnActivityCreated,
-            mCalledOnStart, mCalledOnResume, mCalledOnSaveInstanceState,
-            mCalledOnPause, mCalledOnStop, mCalledOnDestroy, mCalledOnDetach,
-            mCalledOnAttachFragment;
-    Bundle mSavedInstanceState;
-
-    static String stateToString(int state) {
-        switch (state) {
-            case DETACHED: return "DETACHED";
-            case ATTACHED: return "ATTACHED";
-            case CREATED: return "CREATED";
-            case ACTIVITY_CREATED: return "ACTIVITY_CREATED";
-            case STARTED: return "STARTED";
-            case RESUMED: return "RESUMED";
-        }
-        return "(unknown " + state + ")";
-    }
-
-    public void onStateChanged(int fromState) {
-        checkGetActivity();
-    }
-
-    public void checkGetActivity() {
-        if (getActivity() == null) {
-            throw new IllegalStateException("getActivity() returned null at unexpected time");
-        }
-    }
-
-    public void checkState(String caller, int... expected) {
-        if (expected == null || expected.length == 0) {
-            throw new IllegalArgumentException("must supply at least one expected state");
-        }
-        for (int expect : expected) {
-            if (mState == expect) {
-                return;
-            }
-        }
-        final StringBuilder expectString = new StringBuilder(stateToString(expected[0]));
-        for (int i = 1; i < expected.length; i++) {
-            expectString.append(" or ").append(stateToString(expected[i]));
-        }
-        throw new IllegalStateException(caller + " called while fragment was "
-                + stateToString(mState) + "; expected " + expectString.toString());
-    }
-
-    public void checkStateAtLeast(String caller, int minState) {
-        if (mState < minState) {
-            throw new IllegalStateException(caller + " called while fragment was "
-                    + stateToString(mState) + "; expected at least " + stateToString(minState));
-        }
-    }
-
-    @Override
-    public void onAttachFragment(Fragment childFragment) {
-        mCalledOnAttachFragment = true;
-    }
-
-    @Override
-    public void onAttach(Context context) {
-        super.onAttach(context);
-        mCalledOnAttach = true;
-        checkState("onAttach", DETACHED);
-        mState = ATTACHED;
-        onStateChanged(DETACHED);
-    }
-
-    @Override
-    public void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        if (mCalledOnCreate && !mCalledOnDestroy) {
-            throw new IllegalStateException("onCreate called more than once with no onDestroy");
-        }
-        mCalledOnCreate = true;
-        mSavedInstanceState = savedInstanceState;
-        checkState("onCreate", ATTACHED);
-        mState = CREATED;
-        onStateChanged(ATTACHED);
-    }
-
-    @Override
-    public void onActivityCreated(Bundle savedInstanceState) {
-        super.onActivityCreated(savedInstanceState);
-        mCalledOnActivityCreated = true;
-        checkState("onActivityCreated", ATTACHED, CREATED);
-        int fromState = mState;
-        mState = ACTIVITY_CREATED;
-        onStateChanged(fromState);
-    }
-
-    @Override
-    public void onStart() {
-        super.onStart();
-        mCalledOnStart = true;
-        checkState("onStart", CREATED, ACTIVITY_CREATED);
-        mState = STARTED;
-        onStateChanged(ACTIVITY_CREATED);
-    }
-
-    @Override
-    public void onResume() {
-        super.onResume();
-        mCalledOnResume = true;
-        checkState("onResume", STARTED);
-        mState = RESUMED;
-        onStateChanged(STARTED);
-    }
-
-    @Override
-    public void onSaveInstanceState(Bundle outState) {
-        super.onSaveInstanceState(outState);
-        mCalledOnSaveInstanceState = true;
-        checkGetActivity();
-        // FIXME: We should not allow onSaveInstanceState except when STARTED or greater.
-        // But FragmentManager currently does it in saveAllState for fragments on the
-        // back stack, so fragments may be in the CREATED state.
-        checkStateAtLeast("onSaveInstanceState", CREATED);
-    }
-
-    @Override
-    public void onPause() {
-        super.onPause();
-        mCalledOnPause = true;
-        checkState("onPause", RESUMED);
-        mState = STARTED;
-        onStateChanged(RESUMED);
-    }
-
-    @Override
-    public void onStop() {
-        super.onStop();
-        mCalledOnStop = true;
-        checkState("onStop", STARTED);
-        mState = CREATED;
-        onStateChanged(STARTED);
-    }
-
-    @Override
-    public void onDestroy() {
-        super.onDestroy();
-        mCalledOnDestroy = true;
-        checkState("onDestroy", CREATED);
-        mState = ATTACHED;
-        onStateChanged(CREATED);
-    }
-
-    @Override
-    public void onDetach() {
-        super.onDetach();
-        mCalledOnDetach = true;
-        checkState("onDestroy", CREATED, ATTACHED);
-        int fromState = mState;
-        mState = DETACHED;
-        onStateChanged(fromState);
-    }
-}
diff --git a/fragment/src/androidTest/java/androidx/fragment/app/StrictFragment.kt b/fragment/src/androidTest/java/androidx/fragment/app/StrictFragment.kt
new file mode 100644
index 0000000..b8faca2
--- /dev/null
+++ b/fragment/src/androidTest/java/androidx/fragment/app/StrictFragment.kt
@@ -0,0 +1,193 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.fragment.app
+
+import android.content.Context
+import android.os.Bundle
+import com.google.common.truth.Truth.assertWithMessage
+
+/**
+ * This fragment watches its primary lifecycle events and throws IllegalStateException
+ * if any of them are called out of order or from a bad/unexpected state.
+ */
+open class StrictFragment : Fragment() {
+    var currentState: Int = 0
+
+    var calledOnAttach: Boolean = false
+    var calledOnCreate: Boolean = false
+    var calledOnActivityCreated: Boolean = false
+    var calledOnStart: Boolean = false
+    var calledOnResume: Boolean = false
+    var calledOnSaveInstanceState: Boolean = false
+    var calledOnPause: Boolean = false
+    var calledOnStop: Boolean = false
+    var calledOnDestroy: Boolean = false
+    var calledOnDetach: Boolean = false
+    var calledOnAttachFragment: Boolean = false
+    var lastSavedInstanceState: Bundle? = null
+
+    open fun onStateChanged(fromState: Int) {
+        checkGetActivity()
+    }
+
+    fun checkGetActivity() {
+        assertWithMessage("getActivity() returned null at unexpected time")
+            .that(activity)
+            .isNotNull()
+    }
+
+    fun checkState(caller: String, vararg expected: Int) {
+        if (expected.isEmpty()) {
+            throw IllegalArgumentException("must supply at least one expected state")
+        }
+        for (expect in expected) {
+            if (currentState == expect) {
+                return
+            }
+        }
+        val expectString = StringBuilder(stateToString(expected[0]))
+        for (i in 1 until expected.size) {
+            expectString.append(" or ").append(stateToString(expected[i]))
+        }
+        throw IllegalStateException(
+            "$caller called while fragment was ${stateToString(currentState)}; " +
+                    "expected $expectString"
+        )
+    }
+
+    fun checkStateAtLeast(caller: String, minState: Int) {
+        if (currentState < minState) {
+            throw IllegalStateException(
+                "$caller called while fragment was ${stateToString(currentState)}; " +
+                        "expected at least ${stateToString(minState)}"
+            )
+        }
+    }
+
+    override fun onAttachFragment(childFragment: Fragment) {
+        calledOnAttachFragment = true
+    }
+
+    override fun onAttach(context: Context) {
+        super.onAttach(context)
+        calledOnAttach = true
+        checkState("onAttach", DETACHED)
+        currentState = ATTACHED
+        onStateChanged(DETACHED)
+    }
+
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+        if (calledOnCreate && !calledOnDestroy) {
+            throw IllegalStateException("onCreate called more than once with no onDestroy")
+        }
+        calledOnCreate = true
+        lastSavedInstanceState = savedInstanceState
+        checkState("onCreate", ATTACHED)
+        currentState = CREATED
+        onStateChanged(ATTACHED)
+    }
+
+    override fun onActivityCreated(savedInstanceState: Bundle?) {
+        super.onActivityCreated(savedInstanceState)
+        calledOnActivityCreated = true
+        checkState("onActivityCreated", ATTACHED, CREATED)
+        val fromState = currentState
+        currentState = ACTIVITY_CREATED
+        onStateChanged(fromState)
+    }
+
+    override fun onStart() {
+        super.onStart()
+        calledOnStart = true
+        checkState("onStart", CREATED, ACTIVITY_CREATED)
+        currentState = STARTED
+        onStateChanged(ACTIVITY_CREATED)
+    }
+
+    override fun onResume() {
+        super.onResume()
+        calledOnResume = true
+        checkState("onResume", STARTED)
+        currentState = RESUMED
+        onStateChanged(STARTED)
+    }
+
+    override fun onSaveInstanceState(outState: Bundle) {
+        super.onSaveInstanceState(outState)
+        calledOnSaveInstanceState = true
+        checkGetActivity()
+        // FIXME: We should not allow onSaveInstanceState except when STARTED or greater.
+        // But FragmentManager currently does it in saveAllState for fragments on the
+        // back stack, so fragments may be in the CREATED state.
+        checkStateAtLeast("onSaveInstanceState", CREATED)
+    }
+
+    override fun onPause() {
+        super.onPause()
+        calledOnPause = true
+        checkState("onPause", RESUMED)
+        currentState = STARTED
+        onStateChanged(RESUMED)
+    }
+
+    override fun onStop() {
+        super.onStop()
+        calledOnStop = true
+        checkState("onStop", STARTED)
+        currentState = CREATED
+        onStateChanged(STARTED)
+    }
+
+    override fun onDestroy() {
+        super.onDestroy()
+        calledOnDestroy = true
+        checkState("onDestroy", CREATED)
+        currentState = ATTACHED
+        onStateChanged(CREATED)
+    }
+
+    override fun onDetach() {
+        super.onDetach()
+        calledOnDetach = true
+        checkState("onDestroy", CREATED, ATTACHED)
+        val fromState = currentState
+        currentState = DETACHED
+        onStateChanged(fromState)
+    }
+
+    companion object {
+        const val DETACHED = 0
+        const val ATTACHED = 1
+        const val CREATED = 2
+        const val ACTIVITY_CREATED = 3
+        const val STARTED = 4
+        const val RESUMED = 5
+
+        internal fun stateToString(state: Int): String {
+            when (state) {
+                DETACHED -> return "DETACHED"
+                ATTACHED -> return "ATTACHED"
+                CREATED -> return "CREATED"
+                ACTIVITY_CREATED -> return "ACTIVITY_CREATED"
+                STARTED -> return "STARTED"
+                RESUMED -> return "RESUMED"
+            }
+            return "(unknown $state)"
+        }
+    }
+}
diff --git a/fragment/src/androidTest/java/androidx/fragment/app/StrictViewFragment.java b/fragment/src/androidTest/java/androidx/fragment/app/StrictViewFragment.java
deleted file mode 100644
index ec1a553..0000000
--- a/fragment/src/androidTest/java/androidx/fragment/app/StrictViewFragment.java
+++ /dev/null
@@ -1,76 +0,0 @@
-/*
- * Copyright 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-
-package androidx.fragment.app;
-
-import android.os.Bundle;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.fragment.test.R;
-
-public class StrictViewFragment extends StrictFragment {
-    boolean mOnCreateViewCalled, mOnViewCreatedCalled, mOnDestroyViewCalled;
-    int mLayoutId = R.layout.strict_view_fragment;
-
-    public void setLayoutId(int layoutId) {
-        mLayoutId = layoutId;
-    }
-
-    public static StrictViewFragment create(int layoutId) {
-        StrictViewFragment fragment = new StrictViewFragment();
-        fragment.mLayoutId = layoutId;
-        return fragment;
-    }
-
-    @Override
-    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
-            @Nullable Bundle savedInstanceState) {
-        checkGetActivity();
-        checkState("onCreateView", CREATED);
-        View result = super.onCreateView(inflater, container, savedInstanceState);
-        if (result == null) {
-            result = inflater.inflate(mLayoutId, container, false);
-        }
-        mOnCreateViewCalled = true;
-        return result;
-    }
-
-    @Override
-    public void onViewCreated(View view, Bundle savedInstanceState) {
-        if (view == null) {
-            throw new IllegalArgumentException("onViewCreated view argument should not be null");
-        }
-        checkGetActivity();
-        checkState("onViewCreated", CREATED);
-        mOnViewCreatedCalled = true;
-    }
-
-    @Override
-    public void onDestroyView() {
-        super.onDestroyView();
-        if (getView() == null) {
-            throw new IllegalStateException("getView returned null in onDestroyView");
-        }
-        checkGetActivity();
-        checkState("onDestroyView", CREATED);
-        mOnDestroyViewCalled = true;
-    }
-}
diff --git a/fragment/src/androidTest/java/androidx/fragment/app/StrictViewFragment.kt b/fragment/src/androidTest/java/androidx/fragment/app/StrictViewFragment.kt
new file mode 100644
index 0000000..5b367e4
--- /dev/null
+++ b/fragment/src/androidTest/java/androidx/fragment/app/StrictViewFragment.kt
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.fragment.app
+
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.annotation.LayoutRes
+import androidx.fragment.test.R
+import com.google.common.truth.Truth.assertWithMessage
+
+open class StrictViewFragment(
+    @LayoutRes val contentLayoutId: Int = R.layout.strict_view_fragment
+) : StrictFragment() {
+
+    internal var onCreateViewCalled: Boolean = false
+    internal var onViewCreatedCalled: Boolean = false
+    internal var onDestroyViewCalled: Boolean = false
+
+    override fun onCreateView(
+        inflater: LayoutInflater,
+        container: ViewGroup?,
+        savedInstanceState: Bundle?
+    ): View? {
+        checkGetActivity()
+        checkState("onCreateView", StrictFragment.CREATED)
+        var result = super.onCreateView(inflater, container, savedInstanceState)
+        if (result == null) {
+            result = inflater.inflate(contentLayoutId, container, false)
+        }
+        onCreateViewCalled = true
+        return result
+    }
+
+    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+        checkGetActivity()
+        checkState("onViewCreated", StrictFragment.CREATED)
+        onViewCreatedCalled = true
+    }
+
+    override fun onDestroyView() {
+        super.onDestroyView()
+        assertWithMessage("getView returned null in onDestroyView")
+            .that(view)
+            .isNotNull()
+        checkGetActivity()
+        checkState("onDestroyView", StrictFragment.CREATED)
+        onDestroyViewCalled = true
+    }
+}
diff --git a/fragment/src/androidTest/java/androidx/fragment/app/TargetFragmentLifeCycleTest.kt b/fragment/src/androidTest/java/androidx/fragment/app/TargetFragmentLifeCycleTest.kt
new file mode 100644
index 0000000..0a62f5e
--- /dev/null
+++ b/fragment/src/androidTest/java/androidx/fragment/app/TargetFragmentLifeCycleTest.kt
@@ -0,0 +1,427 @@
+/*
+ * 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.fragment.app
+
+import android.os.Bundle
+import androidx.fragment.app.test.EmptyFragmentTestActivity
+import androidx.lifecycle.ViewModelStore
+import androidx.test.annotation.UiThreadTest
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.MediumTest
+import androidx.test.rule.ActivityTestRule
+import com.google.common.truth.Truth.assertThat
+import com.google.common.truth.Truth.assertWithMessage
+import org.junit.Assert
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+@MediumTest
+class TargetFragmentLifeCycleTest {
+
+    @get:Rule
+    val activityRule = ActivityTestRule(EmptyFragmentTestActivity::class.java)
+
+    @Test
+    fun targetFragmentNoCycles() {
+        val one = Fragment()
+        val two = Fragment()
+        val three = Fragment()
+
+        try {
+            one.setTargetFragment(two, 0)
+            two.setTargetFragment(three, 0)
+            three.setTargetFragment(one, 0)
+            Assert.fail("creating a fragment target cycle did not throw IllegalArgumentException")
+        } catch (e: IllegalArgumentException) {
+            assertThat(e).hasMessageThat().contains("Setting $one as the target of $three would" +
+                    " create a target cycle")
+        }
+    }
+
+    @Test
+    fun targetFragmentSetClear() {
+        val one = Fragment()
+        val two = Fragment()
+
+        one.setTargetFragment(two, 0)
+        one.setTargetFragment(null, 0)
+    }
+
+    /**
+     * Test that target fragments are in a useful state when we restore them, even if they're
+     * on the back stack.
+     */
+    @Test
+    @UiThreadTest
+    fun targetFragmentRestoreLifecycleStateBackStack() {
+        val viewModelStore = ViewModelStore()
+        val fc1 = FragmentController.createController(
+            FragmentTestUtil.HostCallbacks(activityRule.activity, viewModelStore)
+        )
+
+        val fm1 = fc1.supportFragmentManager
+
+        fc1.attachHost(null)
+        fc1.dispatchCreate()
+
+        val target = TargetFragment()
+        fm1.beginTransaction().add(target, "target").commitNow()
+
+        val referrer = ReferrerFragment()
+        referrer.setTargetFragment(target, 0)
+
+        fm1.beginTransaction()
+            .remove(target)
+            .add(referrer, "referrer")
+            .addToBackStack(null)
+            .commit()
+
+        fc1.dispatchActivityCreated()
+        fc1.noteStateNotSaved()
+        fc1.execPendingActions()
+        fc1.dispatchStart()
+        fc1.dispatchResume()
+        fc1.execPendingActions()
+
+        // Simulate an activity restart
+        val fc2 =
+            FragmentTestUtil.restartFragmentController(activityRule.activity, fc1, viewModelStore)
+
+        // Bring the state back down to destroyed before we finish the test
+        FragmentTestUtil.shutdownFragmentController(fc2, viewModelStore)
+    }
+
+    @Test
+    @UiThreadTest
+    fun targetFragmentRestoreLifecycleStateManagerOrder() {
+        val viewModelStore = ViewModelStore()
+        val fc1 = FragmentController.createController(
+            FragmentTestUtil.HostCallbacks(activityRule.activity, viewModelStore)
+        )
+
+        val fm1 = fc1.supportFragmentManager
+
+        fc1.attachHost(null)
+        fc1.dispatchCreate()
+
+        val target1 = TargetFragment()
+        val referrer1 = ReferrerFragment()
+        referrer1.setTargetFragment(target1, 0)
+
+        fm1.beginTransaction().add(target1, "target1").add(referrer1, "referrer1").commitNow()
+
+        val target2 = TargetFragment()
+        val referrer2 = ReferrerFragment()
+        referrer2.setTargetFragment(target2, 0)
+
+        // Order shouldn't matter.
+        fm1.beginTransaction().add(referrer2, "referrer2").add(target2, "target2").commitNow()
+
+        fc1.dispatchActivityCreated()
+        fc1.noteStateNotSaved()
+        fc1.execPendingActions()
+        fc1.dispatchStart()
+        fc1.dispatchResume()
+        fc1.execPendingActions()
+
+        // Simulate an activity restart
+        val fc2 =
+            FragmentTestUtil.restartFragmentController(activityRule.activity, fc1, viewModelStore)
+
+        // Bring the state back down to destroyed before we finish the test
+        FragmentTestUtil.shutdownFragmentController(fc2, viewModelStore)
+    }
+
+    @Test
+    @UiThreadTest
+    fun targetFragmentClearedWhenSetToNull() {
+        val viewModelStore = ViewModelStore()
+        val fc =
+            FragmentTestUtil.startupFragmentController(activityRule.activity, null, viewModelStore)
+
+        val fm = fc.supportFragmentManager
+
+        val target = TargetFragment()
+        val referrer = ReferrerFragment()
+        referrer.setTargetFragment(target, 0)
+
+        assertWithMessage("Target Fragment should be accessible before being added")
+            .that(referrer.targetFragment).isSameAs(target)
+
+        fm.beginTransaction().add(target, "target").add(referrer, "referrer").commitNow()
+
+        assertWithMessage("Target Fragment should be accessible after being added")
+            .that(referrer.targetFragment).isSameAs(target)
+
+        referrer.setTargetFragment(null, 0)
+
+        assertWithMessage("Target Fragment should cleared after setTargetFragment with null")
+            .that(referrer.targetFragment).isNull()
+
+        fm.beginTransaction().remove(referrer).commitNow()
+
+        assertWithMessage("Target Fragment should still be cleared after being removed")
+            .that(referrer.targetFragment).isNull()
+
+        FragmentTestUtil.shutdownFragmentController(fc, viewModelStore)
+    }
+
+    @Test
+    @UiThreadTest
+    fun targetFragment_replacement() {
+        val viewModelStore = ViewModelStore()
+        val fc =
+            FragmentTestUtil.startupFragmentController(activityRule.activity, null, viewModelStore)
+
+        val fm = fc.supportFragmentManager
+
+        val referrer = ReferrerFragment()
+        val target = TargetFragment()
+        referrer.setTargetFragment(target, 0)
+
+        assertWithMessage("Target Fragment should be accessible before being added")
+            .that(referrer.targetFragment).isSameAs(target)
+
+        fm.beginTransaction().add(referrer, "referrer").add(target, "target").commitNow()
+
+        assertWithMessage("Target Fragment should be accessible after being added")
+            .that(referrer.targetFragment).isSameAs(target)
+
+        val newTarget = TargetFragment()
+        referrer.setTargetFragment(newTarget, 0)
+
+        assertWithMessage("New Target Fragment should returned despite not being added")
+            .that(referrer.targetFragment).isSameAs(newTarget)
+
+        referrer.setTargetFragment(target, 0)
+
+        assertWithMessage("Replacement Target Fragment should override previous target")
+            .that(referrer.targetFragment).isSameAs(target)
+
+        FragmentTestUtil.shutdownFragmentController(fc, viewModelStore)
+    }
+
+    /**
+     * Test the availability of getTargetFragment() when the target Fragment is already
+     * attached to a FragmentManager, but the referrer Fragment is not attached.
+     */
+    @Test
+    @UiThreadTest
+    fun targetFragmentOnlyTargetAdded() {
+        val viewModelStore = ViewModelStore()
+        val fc =
+            FragmentTestUtil.startupFragmentController(activityRule.activity, null, viewModelStore)
+
+        val fm = fc.supportFragmentManager
+
+        val target = TargetFragment()
+        // Add just the target Fragment to the FragmentManager
+        fm.beginTransaction().add(target, "target").commitNow()
+
+        val referrer = ReferrerFragment()
+        referrer.setTargetFragment(target, 0)
+
+        assertWithMessage("Target Fragment should be accessible before being added")
+            .that(referrer.targetFragment).isSameAs(target)
+
+        fm.beginTransaction().add(referrer, "referrer").commitNow()
+
+        assertWithMessage("Target Fragment should be accessible after being added")
+            .that(referrer.targetFragment).isSameAs(target)
+
+        fm.beginTransaction().remove(referrer).commitNow()
+
+        assertWithMessage("Target Fragment should be accessible after being removed")
+            .that(referrer.targetFragment).isSameAs(target)
+
+        FragmentTestUtil.shutdownFragmentController(fc, viewModelStore)
+    }
+
+    /**
+     * Test the availability of getTargetFragment() when the target fragment is
+     * not retained and the referrer fragment is not retained.
+     */
+    @Test
+    @UiThreadTest
+    fun targetFragmentNonRetainedNonRetained() {
+        val viewModelStore = ViewModelStore()
+        val fc =
+            FragmentTestUtil.startupFragmentController(activityRule.activity, null, viewModelStore)
+
+        val fm = fc.supportFragmentManager
+
+        val target = TargetFragment()
+        val referrer = ReferrerFragment()
+        referrer.setTargetFragment(target, 0)
+
+        assertWithMessage("Target Fragment should be accessible before being added")
+            .that(referrer.targetFragment).isSameAs(target)
+
+        fm.beginTransaction().add(target, "target").add(referrer, "referrer").commitNow()
+
+        assertWithMessage("Target Fragment should be accessible after being added")
+            .that(referrer.targetFragment).isSameAs(target)
+
+        fm.beginTransaction().remove(referrer).commitNow()
+
+        assertWithMessage("Target Fragment should be accessible after being removed")
+            .that(referrer.targetFragment).isSameAs(target)
+
+        FragmentTestUtil.shutdownFragmentController(fc, viewModelStore)
+
+        assertWithMessage("Target Fragment should be accessible after destruction")
+            .that(referrer.targetFragment).isSameAs(target)
+    }
+
+    /**
+     * Test the availability of getTargetFragment() when the target fragment is
+     * retained and the referrer fragment is not retained.
+     */
+    @Test
+    @UiThreadTest
+    fun targetFragmentRetainedNonRetained() {
+        val viewModelStore = ViewModelStore()
+        val fc =
+            FragmentTestUtil.startupFragmentController(activityRule.activity, null, viewModelStore)
+
+        val fm = fc.supportFragmentManager
+
+        val target = TargetFragment()
+        target.retainInstance = true
+        val referrer = ReferrerFragment()
+        referrer.setTargetFragment(target, 0)
+
+        assertWithMessage("Target Fragment should be accessible before being added")
+            .that(referrer.targetFragment).isSameAs(target)
+
+        fm.beginTransaction().add(target, "target").add(referrer, "referrer").commitNow()
+
+        assertWithMessage("Target Fragment should be accessible after being added")
+            .that(referrer.targetFragment).isSameAs(target)
+
+        fm.beginTransaction().remove(referrer).commitNow()
+
+        assertWithMessage("Target Fragment should be accessible after being removed")
+            .that(referrer.targetFragment).isSameAs(target)
+
+        FragmentTestUtil.shutdownFragmentController(fc, viewModelStore)
+
+        assertWithMessage("Target Fragment should be accessible after destruction")
+            .that(referrer.targetFragment).isSameAs(target)
+    }
+
+    /**
+     * Test the availability of getTargetFragment() when the target fragment is
+     * not retained and the referrer fragment is retained.
+     */
+    @Test
+    @UiThreadTest
+    fun targetFragmentNonRetainedRetained() {
+        val viewModelStore = ViewModelStore()
+        val fc =
+            FragmentTestUtil.startupFragmentController(activityRule.activity, null, viewModelStore)
+
+        val fm = fc.supportFragmentManager
+
+        val target = TargetFragment()
+        val referrer = ReferrerFragment()
+        referrer.setTargetFragment(target, 0)
+        referrer.retainInstance = true
+
+        assertWithMessage("Target Fragment should be accessible before being added")
+            .that(referrer.targetFragment).isSameAs(target)
+
+        fm.beginTransaction().add(target, "target").add(referrer, "referrer").commitNow()
+
+        assertWithMessage("Target Fragment should be accessible after being added")
+            .that(referrer.targetFragment).isSameAs(target)
+
+        // Save the state
+        fc.dispatchPause()
+        fc.saveAllState()
+        fc.dispatchStop()
+        fc.dispatchDestroy()
+
+        assertWithMessage("Target Fragment should be accessible after target Fragment destruction")
+            .that(referrer.targetFragment).isSameAs(target)
+    }
+
+    /**
+     * Test the availability of getTargetFragment() when the target fragment is
+     * retained and the referrer fragment is also retained.
+     */
+    @Test
+    @UiThreadTest
+    fun targetFragmentRetainedRetained() {
+        val viewModelStore = ViewModelStore()
+        val fc =
+            FragmentTestUtil.startupFragmentController(activityRule.activity, null, viewModelStore)
+
+        val fm = fc.supportFragmentManager
+
+        val target = TargetFragment()
+        target.retainInstance = true
+        val referrer = ReferrerFragment()
+        referrer.retainInstance = true
+        referrer.setTargetFragment(target, 0)
+
+        assertWithMessage("Target Fragment should be accessible before being added")
+            .that(referrer.targetFragment).isSameAs(target)
+
+        fm.beginTransaction().add(target, "target").add(referrer, "referrer").commitNow()
+
+        assertWithMessage("Target Fragment should be accessible after being added")
+            .that(referrer.targetFragment).isSameAs(target)
+
+        // Save the state
+        fc.dispatchPause()
+        fc.saveAllState()
+        fc.dispatchStop()
+        fc.dispatchDestroy()
+
+        assertWithMessage("Target Fragment should be accessible after FragmentManager destruction")
+            .that(referrer.targetFragment).isSameAs(target)
+    }
+
+    class TargetFragment : Fragment() {
+        var calledCreate: Boolean = false
+
+        override fun onCreate(savedInstanceState: Bundle?) {
+            super.onCreate(savedInstanceState)
+            calledCreate = true
+        }
+    }
+
+    class ReferrerFragment : Fragment() {
+        override fun onCreate(savedInstanceState: Bundle?) {
+            super.onCreate(savedInstanceState)
+
+            val target = targetFragment
+            assertWithMessage("target fragment was null during referrer onCreate")
+                .that(target).isNotNull()
+
+            if (target !is TargetFragment) {
+                throw IllegalStateException("target fragment was not a TargetFragment")
+            }
+
+            assertWithMessage("target fragment has not yet been created")
+                .that(target.calledCreate).isTrue()
+        }
+    }
+}
\ No newline at end of file
diff --git a/fragment/src/androidTest/java/androidx/fragment/app/TrackingVisibility.java b/fragment/src/androidTest/java/androidx/fragment/app/TrackingVisibility.java
index 3b0dbb1a..2c54f2d 100644
--- a/fragment/src/androidTest/java/androidx/fragment/app/TrackingVisibility.java
+++ b/fragment/src/androidTest/java/androidx/fragment/app/TrackingVisibility.java
@@ -28,7 +28,7 @@
  * Visibility transition that tracks which targets are applied to it.
  * This transition does no animation.
  */
-class TrackingVisibility extends Visibility implements TargetTracking {
+public class TrackingVisibility extends Visibility implements TargetTracking {
     public final ArrayList<View> targets = new ArrayList<>();
     private final Rect[] mEpicenter = new Rect[1];
 
diff --git a/fragment/src/androidTest/java/androidx/fragment/app/TransitionFragment.java b/fragment/src/androidTest/java/androidx/fragment/app/TransitionFragment.java
deleted file mode 100644
index ad8b45b1..0000000
--- a/fragment/src/androidTest/java/androidx/fragment/app/TransitionFragment.java
+++ /dev/null
@@ -1,80 +0,0 @@
-/*
- * Copyright 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package androidx.fragment.app;
-
-import static org.mockito.Matchers.any;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.reset;
-import static org.mockito.Mockito.verify;
-
-import android.os.Bundle;
-import android.os.SystemClock;
-import android.transition.Transition;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-/**
- * A fragment that has transitions that can be tracked.
- */
-public class TransitionFragment extends StrictViewFragment {
-    public final TrackingVisibility enterTransition = new TrackingVisibility();
-    public final TrackingVisibility reenterTransition = new TrackingVisibility();
-    public final TrackingVisibility exitTransition = new TrackingVisibility();
-    public final TrackingVisibility returnTransition = new TrackingVisibility();
-    public final TrackingTransition sharedElementEnter = new TrackingTransition();
-    public final TrackingTransition sharedElementReturn = new TrackingTransition();
-
-    private Transition.TransitionListener mListener = mock(Transition.TransitionListener.class);
-
-    public TransitionFragment() {
-        setEnterTransition(enterTransition);
-        setReenterTransition(reenterTransition);
-        setExitTransition(exitTransition);
-        setReturnTransition(returnTransition);
-        setSharedElementEnterTransition(sharedElementEnter);
-        setSharedElementReturnTransition(sharedElementReturn);
-        enterTransition.addListener(mListener);
-        sharedElementEnter.addListener(mListener);
-        reenterTransition.addListener(mListener);
-        exitTransition.addListener(mListener);
-        returnTransition.addListener(mListener);
-        sharedElementReturn.addListener(mListener);
-    }
-
-    @Override
-    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
-            @Nullable Bundle savedInstanceState) {
-        checkGetActivity();
-        checkState("onCreateView", CREATED);
-        mOnCreateViewCalled = true;
-        return super.onCreateView(inflater, container, savedInstanceState);
-    }
-
-    void waitForTransition() throws InterruptedException {
-        verify(mListener, CtsMockitoUtils.within(1000)).onTransitionEnd((Transition) any());
-        reset(mListener);
-    }
-
-    void waitForNoTransition() throws InterruptedException {
-        SystemClock.sleep(250);
-        verify(mListener, never()).onTransitionStart((Transition) any());
-    }
-}
diff --git a/fragment/src/androidTest/java/androidx/fragment/app/TransitionFragment.kt b/fragment/src/androidTest/java/androidx/fragment/app/TransitionFragment.kt
new file mode 100644
index 0000000..bd20bd9
--- /dev/null
+++ b/fragment/src/androidTest/java/androidx/fragment/app/TransitionFragment.kt
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package androidx.fragment.app
+
+import android.os.SystemClock
+import android.transition.Transition
+import androidx.annotation.LayoutRes
+import androidx.fragment.test.R
+import org.mockito.ArgumentMatchers
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.reset
+import org.mockito.Mockito.verify
+
+/**
+ * A fragment that has transitions that can be tracked.
+ */
+open class TransitionFragment(
+    @LayoutRes contentLayoutId: Int = R.layout.strict_view_fragment
+) : StrictViewFragment(contentLayoutId) {
+    val enterTransition = TrackingVisibility()
+    val reenterTransition = TrackingVisibility()
+    val exitTransition = TrackingVisibility()
+    val returnTransition = TrackingVisibility()
+    val sharedElementEnter = TrackingTransition()
+    val sharedElementReturn = TrackingTransition()
+
+    private val listener = mock(Transition.TransitionListener::class.java)
+
+    init {
+        @Suppress("LeakingThis")
+        setEnterTransition(enterTransition)
+        @Suppress("LeakingThis")
+        setReenterTransition(reenterTransition)
+        @Suppress("LeakingThis")
+        setExitTransition(exitTransition)
+        @Suppress("LeakingThis")
+        setReturnTransition(returnTransition)
+        sharedElementEnterTransition = sharedElementEnter
+        sharedElementReturnTransition = sharedElementReturn
+        enterTransition.addListener(listener)
+        sharedElementEnter.addListener(listener)
+        reenterTransition.addListener(listener)
+        exitTransition.addListener(listener)
+        returnTransition.addListener(listener)
+        sharedElementReturn.addListener(listener)
+    }
+
+    internal fun waitForTransition() {
+        verify(
+            listener,
+            CtsMockitoUtils.within(1000)
+        ).onTransitionEnd(ArgumentMatchers.any())
+        reset(listener)
+    }
+
+    internal fun waitForNoTransition() {
+        SystemClock.sleep(250)
+        verify(
+            listener,
+            never()
+        ).onTransitionStart(ArgumentMatchers.any())
+    }
+}
diff --git a/fragment/src/androidTest/java/androidx/fragment/app/test/FragmentTestActivity.java b/fragment/src/androidTest/java/androidx/fragment/app/test/FragmentTestActivity.java
deleted file mode 100644
index 0d7443d..0000000
--- a/fragment/src/androidTest/java/androidx/fragment/app/test/FragmentTestActivity.java
+++ /dev/null
@@ -1,291 +0,0 @@
-/*
- * Copyright 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package androidx.fragment.app.test;
-
-import static org.junit.Assert.assertFalse;
-
-import android.app.Activity;
-import android.content.Context;
-import android.content.Intent;
-import android.os.Build.VERSION;
-import android.os.Build.VERSION_CODES;
-import android.os.Bundle;
-import android.transition.Transition;
-import android.transition.Transition.TransitionListener;
-import android.transition.TransitionInflater;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-
-import androidx.annotation.ContentView;
-import androidx.annotation.Nullable;
-import androidx.fragment.app.Fragment;
-import androidx.fragment.app.FragmentActivity;
-import androidx.fragment.test.R;
-
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-/**
- * A simple activity used for Fragment Transitions and lifecycle event ordering
- */
-@ContentView(R.layout.activity_content)
-public class FragmentTestActivity extends FragmentActivity {
-    public final CountDownLatch onDestroyLatch = new CountDownLatch(1);
-
-    @Override
-    public void onCreate(Bundle icicle) {
-        super.onCreate(icicle);
-        Intent intent = getIntent();
-        if (intent != null && intent.getBooleanExtra("finishEarly", false)) {
-            finish();
-            getSupportFragmentManager().beginTransaction()
-                    .add(new AssertNotDestroyed(), "not destroyed")
-                    .commit();
-        }
-    }
-
-    @Override
-    protected void onDestroy() {
-        super.onDestroy();
-        onDestroyLatch.countDown();
-    }
-
-    public static class TestFragment extends Fragment {
-        public static final int ENTER = 0;
-        public static final int RETURN = 1;
-        public static final int EXIT = 2;
-        public static final int REENTER = 3;
-        public static final int SHARED_ELEMENT_ENTER = 4;
-        public static final int SHARED_ELEMENT_RETURN = 5;
-        private static final int TRANSITION_COUNT = 6;
-
-        private static final String LAYOUT_ID = "layoutId";
-        private static final String TRANSITION_KEY = "transition_";
-        private int mLayoutId = R.layout.fragment_start;
-        private final int[] mTransitionIds = new int[] {
-                R.transition.fade,
-                R.transition.fade,
-                R.transition.fade,
-                R.transition.fade,
-                R.transition.change_bounds,
-                R.transition.change_bounds,
-        };
-        private final Object[] mListeners = new Object[TRANSITION_COUNT];
-
-        public TestFragment() {
-            if (VERSION.SDK_INT >= VERSION_CODES.KITKAT) {
-                for (int i = 0; i < TRANSITION_COUNT; i++) {
-                    mListeners[i] = new TransitionCalledListener();
-                }
-            }
-        }
-
-        public static TestFragment create(int layoutId) {
-            TestFragment testFragment = new TestFragment();
-            testFragment.mLayoutId = layoutId;
-            return testFragment;
-        }
-
-        public void clearTransitions() {
-            for (int i = 0; i < TRANSITION_COUNT; i++) {
-                mTransitionIds[i] = 0;
-            }
-        }
-
-        public void clearNotifications() {
-            for (int i = 0; i < TRANSITION_COUNT; i++) {
-                ((TransitionCalledListener)mListeners[i]).startLatch = new CountDownLatch(1);
-                ((TransitionCalledListener)mListeners[i]).endLatch = new CountDownLatch(1);
-            }
-        }
-
-        @Override
-        public void onCreate(Bundle savedInstanceState) {
-            super.onCreate(savedInstanceState);
-            if (savedInstanceState != null) {
-                mLayoutId = savedInstanceState.getInt(LAYOUT_ID, mLayoutId);
-                for (int i = 0; i < TRANSITION_COUNT; i++) {
-                    String key = TRANSITION_KEY + i;
-                    mTransitionIds[i] = savedInstanceState.getInt(key, mTransitionIds[i]);
-                }
-            }
-        }
-
-        @Override
-        public void onSaveInstanceState(Bundle outState) {
-            super.onSaveInstanceState(outState);
-            outState.putInt(LAYOUT_ID, mLayoutId);
-            for (int i = 0; i < TRANSITION_COUNT; i++) {
-                String key = TRANSITION_KEY + i;
-                outState.putInt(key, mTransitionIds[i]);
-            }
-        }
-
-        @Override
-        public View onCreateView(LayoutInflater inflater, ViewGroup container,
-                Bundle savedInstanceState) {
-            return inflater.inflate(mLayoutId, container, false);
-        }
-
-        @SuppressWarnings("deprecation")
-        @Override
-        public void onAttach(Activity activity) {
-            super.onAttach(activity);
-            if (VERSION.SDK_INT > VERSION_CODES.KITKAT) {
-                setEnterTransition(loadTransition(ENTER));
-                setReenterTransition(loadTransition(REENTER));
-                setExitTransition(loadTransition(EXIT));
-                setReturnTransition(loadTransition(RETURN));
-                setSharedElementEnterTransition(loadTransition(SHARED_ELEMENT_ENTER));
-                setSharedElementReturnTransition(loadTransition(SHARED_ELEMENT_RETURN));
-            }
-        }
-
-        public boolean wasStartCalled(int transitionKey) {
-            return ((TransitionCalledListener)mListeners[transitionKey]).startLatch.getCount() == 0;
-        }
-
-        public boolean wasEndCalled(int transitionKey) {
-            return ((TransitionCalledListener)mListeners[transitionKey]).endLatch.getCount() == 0;
-        }
-
-        public boolean waitForStart(int transitionKey)
-                throws InterruptedException {
-            TransitionCalledListener l = ((TransitionCalledListener)mListeners[transitionKey]);
-            return l.startLatch.await(500,TimeUnit.MILLISECONDS);
-        }
-
-        public boolean waitForEnd(int transitionKey)
-                throws InterruptedException {
-            TransitionCalledListener l = ((TransitionCalledListener)mListeners[transitionKey]);
-            return l.endLatch.await(500,TimeUnit.MILLISECONDS);
-        }
-
-        private Transition loadTransition(int key) {
-            final int id = mTransitionIds[key];
-            if (id == 0) {
-                return null;
-            }
-            Transition transition = TransitionInflater.from(getActivity()).inflateTransition(id);
-            transition.addListener(((TransitionCalledListener)mListeners[key]));
-            return transition;
-        }
-
-        private class TransitionCalledListener implements TransitionListener {
-            public CountDownLatch startLatch = new CountDownLatch(1);
-            public CountDownLatch endLatch = new CountDownLatch(1);
-
-            public TransitionCalledListener() {
-            }
-
-            @Override
-            public void onTransitionStart(Transition transition) {
-                startLatch.countDown();
-            }
-
-            @Override
-            public void onTransitionEnd(Transition transition) {
-                endLatch.countDown();
-            }
-
-            @Override
-            public void onTransitionCancel(Transition transition) {
-            }
-
-            @Override
-            public void onTransitionPause(Transition transition) {
-            }
-
-            @Override
-            public void onTransitionResume(Transition transition) {
-            }
-        }
-    }
-
-    public static class ParentFragment extends Fragment {
-        static final String CHILD_FRAGMENT_TAG = "childFragment";
-        public boolean wasAttachedInTime;
-
-        private boolean mRetainChild;
-
-        @Override
-        public void onCreate(Bundle savedInstanceState) {
-            super.onCreate(savedInstanceState);
-
-            ChildFragment f = getChildFragment();
-            if (f == null) {
-                f = new ChildFragment();
-                if (mRetainChild) {
-                    f.setRetainInstance(true);
-                }
-                getChildFragmentManager().beginTransaction().add(f, CHILD_FRAGMENT_TAG).commitNow();
-            }
-            wasAttachedInTime = f.attached;
-        }
-
-        public ChildFragment getChildFragment() {
-            return (ChildFragment) getChildFragmentManager().findFragmentByTag(CHILD_FRAGMENT_TAG);
-        }
-
-        public void setRetainChildInstance(boolean retainChild) {
-            mRetainChild = retainChild;
-        }
-    }
-
-    public static class ChildFragment extends Fragment {
-        private OnAttachListener mOnAttachListener;
-
-        public boolean attached;
-        public boolean onActivityResultCalled;
-        public int onActivityResultRequestCode;
-        public int onActivityResultResultCode;
-
-        @Override
-        public void onAttach(Context activity) {
-            super.onAttach(activity);
-            attached = true;
-            if (mOnAttachListener != null) {
-                mOnAttachListener.onAttach(activity, this);
-            }
-        }
-
-        public void setOnAttachListener(OnAttachListener listener) {
-            mOnAttachListener = listener;
-        }
-
-        public interface OnAttachListener {
-            void onAttach(Context activity, ChildFragment fragment);
-        }
-
-        @Override
-        public void onActivityResult(int requestCode, int resultCode, Intent data) {
-            onActivityResultCalled = true;
-            onActivityResultRequestCode = requestCode;
-            onActivityResultResultCode = resultCode;
-        }
-    }
-
-    public static class AssertNotDestroyed extends Fragment {
-        @Override
-        public void onActivityCreated(@Nullable Bundle savedInstanceState) {
-            super.onActivityCreated(savedInstanceState);
-            if (VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN_MR1) {
-                assertFalse(getActivity().isDestroyed());
-            }
-        }
-    }
-}
diff --git a/fragment/src/androidTest/java/androidx/fragment/app/test/FragmentTestActivity.kt b/fragment/src/androidTest/java/androidx/fragment/app/test/FragmentTestActivity.kt
new file mode 100644
index 0000000..3ebf7c9
--- /dev/null
+++ b/fragment/src/androidTest/java/androidx/fragment/app/test/FragmentTestActivity.kt
@@ -0,0 +1,109 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package androidx.fragment.app.test
+
+import android.content.Context
+import android.content.Intent
+import android.os.Build.VERSION
+import android.os.Build.VERSION_CODES
+import android.os.Bundle
+import androidx.annotation.ContentView
+import androidx.fragment.app.Fragment
+import androidx.fragment.app.FragmentActivity
+import androidx.fragment.test.R
+import org.junit.Assert.assertFalse
+import java.util.concurrent.CountDownLatch
+
+/**
+ * A simple activity used for Fragment Transitions and lifecycle event ordering
+ */
+@ContentView(R.layout.activity_content)
+class FragmentTestActivity : FragmentActivity() {
+    val onDestroyLatch = CountDownLatch(1)
+
+    public override fun onCreate(icicle: Bundle?) {
+        super.onCreate(icicle)
+        if (intent?.getBooleanExtra("finishEarly", false) == true) {
+            finish()
+            supportFragmentManager.beginTransaction()
+                .add(AssertNotDestroyed(), "not destroyed")
+                .commit()
+        }
+    }
+
+    override fun onDestroy() {
+        super.onDestroy()
+        onDestroyLatch.countDown()
+    }
+
+    class ParentFragment : Fragment() {
+        var wasAttachedInTime: Boolean = false
+
+        var retainChildInstance: Boolean = false
+
+        val childFragment: ChildFragment
+            get() = childFragmentManager.findFragmentByTag(CHILD_FRAGMENT_TAG) as ChildFragment
+
+        override fun onCreate(savedInstanceState: Bundle?) {
+            super.onCreate(savedInstanceState)
+
+            if (childFragmentManager.findFragmentByTag(CHILD_FRAGMENT_TAG) == null) {
+                childFragmentManager.beginTransaction()
+                    .add(ChildFragment().apply {
+                        if (retainChildInstance) {
+                            retainInstance = true
+                        }
+                    }, CHILD_FRAGMENT_TAG)
+                    .commitNow()
+            }
+            wasAttachedInTime = childFragment.attached
+        }
+
+        companion object {
+            internal const val CHILD_FRAGMENT_TAG = "childFragment"
+        }
+    }
+
+    class ChildFragment : Fragment() {
+        var onAttachListener: (context: Context) -> Unit = {}
+
+        var attached: Boolean = false
+        var onActivityResultCalled: Boolean = false
+        var onActivityResultRequestCode: Int = 0
+        var onActivityResultResultCode: Int = 0
+
+        override fun onAttach(context: Context) {
+            super.onAttach(context)
+            attached = true
+            onAttachListener.invoke(context)
+        }
+
+        override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
+            onActivityResultCalled = true
+            onActivityResultRequestCode = requestCode
+            onActivityResultResultCode = resultCode
+        }
+    }
+
+    class AssertNotDestroyed : Fragment() {
+        override fun onActivityCreated(savedInstanceState: Bundle?) {
+            super.onActivityCreated(savedInstanceState)
+            if (VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN_MR1) {
+                assertFalse(requireActivity().isDestroyed)
+            }
+        }
+    }
+}
diff --git a/fragment/src/androidTest/java/androidx/fragment/app/test/HangingFragmentActivity.java b/fragment/src/androidTest/java/androidx/fragment/app/test/HangingFragmentActivity.java
deleted file mode 100644
index 971314b..0000000
--- a/fragment/src/androidTest/java/androidx/fragment/app/test/HangingFragmentActivity.java
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * Copyright 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.fragment.app.test;
-
-import android.os.Bundle;
-
-import androidx.annotation.Nullable;
-import androidx.fragment.test.R;
-import androidx.testutils.RecreatedActivity;
-
-public class HangingFragmentActivity extends RecreatedActivity {
-
-    @Override
-    protected void onCreate(@Nullable Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        setContentView(savedInstanceState == null ? R.layout.activity_inflated_fragment
-                : R.layout.activity_content);
-    }
-}
diff --git a/fragment/src/androidTest/java/androidx/fragment/app/test/HangingFragmentActivity.kt b/fragment/src/androidTest/java/androidx/fragment/app/test/HangingFragmentActivity.kt
new file mode 100644
index 0000000..39a4aff
--- /dev/null
+++ b/fragment/src/androidTest/java/androidx/fragment/app/test/HangingFragmentActivity.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.fragment.app.test
+
+import android.os.Bundle
+import androidx.fragment.test.R
+import androidx.testutils.RecreatedActivity
+
+class HangingFragmentActivity : RecreatedActivity() {
+
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+        setContentView(
+            if (savedInstanceState == null)
+                R.layout.activity_inflated_fragment
+            else
+                R.layout.activity_content
+        )
+    }
+}
diff --git a/fragment/src/androidTest/java/androidx/fragment/app/test/LoaderActivity.java b/fragment/src/androidTest/java/androidx/fragment/app/test/LoaderActivity.java
deleted file mode 100644
index 77f4145..0000000
--- a/fragment/src/androidTest/java/androidx/fragment/app/test/LoaderActivity.java
+++ /dev/null
@@ -1,124 +0,0 @@
-/*
- * Copyright 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.fragment.app.test;
-
-import android.content.Context;
-import android.os.Bundle;
-import android.view.View;
-import android.widget.TextView;
-
-import androidx.annotation.ContentView;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.fragment.app.Fragment;
-import androidx.fragment.test.R;
-import androidx.loader.app.LoaderManager;
-import androidx.loader.content.AsyncTaskLoader;
-import androidx.loader.content.Loader;
-import androidx.testutils.RecreatedActivity;
-
-@ContentView(R.layout.activity_loader)
-public class LoaderActivity extends RecreatedActivity
-        implements LoaderManager.LoaderCallbacks<String> {
-    private static final int TEXT_LOADER_ID = 14;
-
-    public TextView textView;
-    public TextView textViewB;
-
-    @Override
-    protected void onCreate(@Nullable Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        textView = findViewById(R.id.textA);
-        textViewB = findViewById(R.id.textB);
-
-        if (savedInstanceState == null) {
-            getSupportFragmentManager()
-                    .beginTransaction()
-                    .add(R.id.fragmentContainer, new TextLoaderFragment())
-                    .commit();
-        }
-    }
-
-    @Override
-    protected void onResume() {
-        super.onResume();
-        LoaderManager.getInstance(this).initLoader(TEXT_LOADER_ID, null, this);
-    }
-
-    @NonNull
-    @Override
-    public Loader<String> onCreateLoader(int id, @Nullable Bundle args) {
-        return new TextLoader(this);
-    }
-
-    @Override
-    public void onLoadFinished(@NonNull Loader<String> loader, String data) {
-        textView.setText(data);
-    }
-
-    @Override
-    public void onLoaderReset(@NonNull Loader<String> loader) {
-    }
-
-    static class TextLoader extends AsyncTaskLoader<String> {
-        TextLoader(Context context) {
-            super(context);
-        }
-
-        @Override
-        protected void onStartLoading() {
-            forceLoad();
-        }
-
-        @Override
-        public String loadInBackground() {
-            return "Loaded!";
-        }
-    }
-
-    @ContentView(R.layout.fragment_c)
-    public static class TextLoaderFragment extends Fragment
-            implements LoaderManager.LoaderCallbacks<String> {
-        public TextView textView;
-
-        @Override
-        public void onCreate(@Nullable Bundle savedInstanceState) {
-            super.onCreate(savedInstanceState);
-            LoaderManager.getInstance(this).initLoader(TEXT_LOADER_ID, null, this);
-        }
-
-        @Override
-        public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
-            textView = view.findViewById(R.id.textC);
-        }
-
-        @NonNull
-        @Override
-        public Loader<String> onCreateLoader(int id, @Nullable Bundle args) {
-            return new TextLoader(getContext());
-        }
-
-        @Override
-        public void onLoadFinished(@NonNull Loader<String> loader, String data) {
-            textView.setText(data);
-        }
-
-        @Override
-        public void onLoaderReset(@NonNull Loader<String> loader) {
-        }
-    }
-}
diff --git a/fragment/src/androidTest/java/androidx/fragment/app/test/LoaderActivity.kt b/fragment/src/androidTest/java/androidx/fragment/app/test/LoaderActivity.kt
new file mode 100644
index 0000000..ea44bf9
--- /dev/null
+++ b/fragment/src/androidTest/java/androidx/fragment/app/test/LoaderActivity.kt
@@ -0,0 +1,107 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.fragment.app.test
+
+import android.content.Context
+import android.os.Bundle
+import android.view.View
+import android.widget.TextView
+
+import androidx.annotation.ContentView
+import androidx.fragment.app.Fragment
+import androidx.fragment.test.R
+import androidx.loader.app.LoaderManager
+import androidx.loader.content.AsyncTaskLoader
+import androidx.loader.content.Loader
+import androidx.testutils.RecreatedActivity
+
+@ContentView(R.layout.activity_loader)
+class LoaderActivity : RecreatedActivity(), LoaderManager.LoaderCallbacks<String> {
+
+    lateinit var textView: TextView
+    lateinit var textViewB: TextView
+
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+        textView = findViewById(R.id.textA)
+        textViewB = findViewById(R.id.textB)
+
+        if (savedInstanceState == null) {
+            supportFragmentManager
+                .beginTransaction()
+                .add(R.id.fragmentContainer, TextLoaderFragment())
+                .commit()
+        }
+    }
+
+    override fun onResume() {
+        super.onResume()
+        LoaderManager.getInstance(this).initLoader(TEXT_LOADER_ID, null, this)
+    }
+
+    override fun onCreateLoader(id: Int, args: Bundle?): Loader<String> {
+        return TextLoader(this)
+    }
+
+    override fun onLoadFinished(loader: Loader<String>, data: String) {
+        textView.text = data
+    }
+
+    override fun onLoaderReset(loader: Loader<String>) {
+    }
+
+    internal class TextLoader(context: Context) : AsyncTaskLoader<String>(context) {
+
+        override fun onStartLoading() {
+            forceLoad()
+        }
+
+        override fun loadInBackground(): String? {
+            return "Loaded!"
+        }
+    }
+
+    @ContentView(R.layout.fragment_c)
+    class TextLoaderFragment : Fragment(), LoaderManager.LoaderCallbacks<String> {
+        lateinit var textView: TextView
+
+        override fun onCreate(savedInstanceState: Bundle?) {
+            super.onCreate(savedInstanceState)
+            LoaderManager.getInstance(this).initLoader(TEXT_LOADER_ID, null, this)
+        }
+
+        override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+            textView = view.findViewById(R.id.textC)
+        }
+
+        override fun onCreateLoader(id: Int, args: Bundle?): Loader<String> {
+            return TextLoader(requireContext())
+        }
+
+        override fun onLoadFinished(loader: Loader<String>, data: String) {
+            textView.text = data
+        }
+
+        override fun onLoaderReset(loader: Loader<String>) {}
+    }
+
+    companion object {
+        private const val TEXT_LOADER_ID = 14
+
+        val activity get() = RecreatedActivity.activity
+    }
+}
diff --git a/fragment/src/androidTest/java/androidx/fragment/app/test/NonConfigOnStopActivity.java b/fragment/src/androidTest/java/androidx/fragment/app/test/NonConfigOnStopActivity.java
deleted file mode 100644
index 7cba3a7..0000000
--- a/fragment/src/androidTest/java/androidx/fragment/app/test/NonConfigOnStopActivity.java
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * Copyright 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.fragment.app.test;
-
-import androidx.fragment.app.Fragment;
-import androidx.testutils.RecreatedActivity;
-
-public class NonConfigOnStopActivity extends RecreatedActivity {
-    @Override
-    protected void onStop() {
-        super.onStop();
-
-        getSupportFragmentManager()
-                .beginTransaction()
-                .add(new RetainedFragment(), "1")
-                .commitNowAllowingStateLoss();
-    }
-
-    public static class RetainedFragment extends Fragment {
-        public RetainedFragment() {
-            setRetainInstance(true);
-        }
-    }
-}
diff --git a/fragment/src/androidTest/java/androidx/fragment/app/test/NonConfigOnStopActivity.kt b/fragment/src/androidTest/java/androidx/fragment/app/test/NonConfigOnStopActivity.kt
new file mode 100644
index 0000000..2c011c6
--- /dev/null
+++ b/fragment/src/androidTest/java/androidx/fragment/app/test/NonConfigOnStopActivity.kt
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.fragment.app.test
+
+import androidx.fragment.app.Fragment
+import androidx.testutils.RecreatedActivity
+
+class NonConfigOnStopActivity : RecreatedActivity() {
+    override fun onStop() {
+        super.onStop()
+
+        supportFragmentManager
+            .beginTransaction()
+            .add(RetainedFragment(), "1")
+            .commitNowAllowingStateLoss()
+    }
+
+    class RetainedFragment : Fragment() {
+        init {
+            retainInstance = true
+        }
+    }
+}
diff --git a/fragment/src/main/java/androidx/fragment/app/Fragment.java b/fragment/src/main/java/androidx/fragment/app/Fragment.java
index 6e52230..ff82951 100644
--- a/fragment/src/main/java/androidx/fragment/app/Fragment.java
+++ b/fragment/src/main/java/androidx/fragment/app/Fragment.java
@@ -686,8 +686,10 @@
         } else if (mFragmentManager != null && fragment.mFragmentManager != null) {
             // Just save the reference to the Fragment
             mTargetWho = fragment.mWho;
+            mTarget = null;
         } else {
             // Save the Fragment itself, waiting until we're attached
+            mTargetWho = null;
             mTarget = fragment;
         }
         mTargetRequestCode = requestCode;
diff --git a/fragment/src/main/java/androidx/fragment/app/FragmentActivity.java b/fragment/src/main/java/androidx/fragment/app/FragmentActivity.java
index a0801b3..717cade 100644
--- a/fragment/src/main/java/androidx/fragment/app/FragmentActivity.java
+++ b/fragment/src/main/java/androidx/fragment/app/FragmentActivity.java
@@ -110,14 +110,10 @@
     public FragmentActivity() {
         super();
         // Route onBackPressed() callbacks to the FragmentManager
-        addOnBackPressedCallback(new OnBackPressedCallback() {
+        getOnBackPressedDispatcher().addCallback(this, new OnBackPressedCallback() {
             @Override
             public boolean handleOnBackPressed() {
                 FragmentManager fragmentManager = mFragments.getSupportFragmentManager();
-                if (fragmentManager.isStateSaved()) {
-                    // Cannot pop after state is saved
-                    return false;
-                }
                 return fragmentManager.popBackStackImmediate();
             }
         });
diff --git a/fragment/src/main/java/androidx/fragment/app/FragmentTabHost.java b/fragment/src/main/java/androidx/fragment/app/FragmentTabHost.java
index 72b7a20..8f16d90 100644
--- a/fragment/src/main/java/androidx/fragment/app/FragmentTabHost.java
+++ b/fragment/src/main/java/androidx/fragment/app/FragmentTabHost.java
@@ -41,16 +41,10 @@
  * the hierarchy you must call {@link #setup(Context, FragmentManager, int)}
  * to complete the initialization of the tab host.
  *
- * <p>Here is a simple example of using a FragmentTabHost in an Activity:
- *
- * {@sample frameworks/support/samples/Support4Demos/src/main/java/com/example/android/supportv4/app/FragmentTabs.java
- *      complete}
- *
- * <p>This can also be used inside of a fragment through fragment nesting:
- *
- * {@sample frameworks/support/samples/Support4Demos/src/main/java/com/example/android/supportv4/app/FragmentTabsFragmentSupport.java
- *      complete}
+ * @deprecated Use <a href="https://developer.android.com/guide/navigation/navigation-swipe-view ">
+ *  TabLayout and ViewPager</a> instead.
  */
+@Deprecated
 public class FragmentTabHost extends TabHost
         implements TabHost.OnTabChangeListener {
     private final ArrayList<TabInfo> mTabs = new ArrayList<>();
@@ -131,6 +125,12 @@
         };
     }
 
+    /**
+     * @deprecated Use
+     * <a href="https://developer.android.com/guide/navigation/navigation-swipe-view ">
+     *  TabLayout and ViewPager</a> instead.
+     */
+    @Deprecated
     public FragmentTabHost(@NonNull Context context) {
         // Note that we call through to the version that takes an AttributeSet,
         // because the simple Context construct can result in a broken object!
@@ -138,6 +138,12 @@
         initFragmentTabHost(context, null);
     }
 
+    /**
+     * @deprecated Use
+     * <a href="https://developer.android.com/guide/navigation/navigation-swipe-view ">
+     *  TabLayout and ViewPager</a> instead.
+     */
+    @Deprecated
     public FragmentTabHost(@NonNull Context context, @Nullable AttributeSet attrs) {
         super(context, attrs);
         initFragmentTabHost(context, attrs);
@@ -181,9 +187,9 @@
     }
 
     /**
-     * @deprecated Don't call the original TabHost setup, you must instead
-     * call {@link #setup(Context, FragmentManager)} or
-     * {@link #setup(Context, FragmentManager, int)}.
+     * @deprecated Use
+     * <a href="https://developer.android.com/guide/navigation/navigation-swipe-view ">
+     *  TabLayout and ViewPager</a> instead.
      */
     @Override @Deprecated
     public void setup() {
@@ -193,7 +199,12 @@
 
     /**
      * Set up the FragmentTabHost to use the given FragmentManager
+     *
+     * @deprecated Use
+     * <a href="https://developer.android.com/guide/navigation/navigation-swipe-view ">
+     *  TabLayout and ViewPager</a> instead.
      */
+    @Deprecated
     public void setup(@NonNull Context context, @NonNull FragmentManager manager) {
         ensureHierarchy(context);  // Ensure views required by super.setup()
         super.setup();
@@ -204,7 +215,12 @@
 
     /**
      * Set up the FragmentTabHost to use the given FragmentManager
+     *
+     * @deprecated Use
+     * <a href="https://developer.android.com/guide/navigation/navigation-swipe-view ">
+     *  TabLayout and ViewPager</a> instead.
      */
+    @Deprecated
     public void setup(@NonNull Context context, @NonNull FragmentManager manager,
             int containerId) {
         ensureHierarchy(context);  // Ensure views required by super.setup()
@@ -232,11 +248,23 @@
         }
     }
 
+    /**
+     * @deprecated Use
+     * <a href="https://developer.android.com/guide/navigation/navigation-swipe-view ">
+     *  TabLayout and ViewPager</a> instead.
+     */
+    @Deprecated
     @Override
     public void setOnTabChangedListener(@Nullable OnTabChangeListener l) {
         mOnTabChangeListener = l;
     }
 
+    /**
+     * @deprecated Use
+     * <a href="https://developer.android.com/guide/navigation/navigation-swipe-view ">
+     *  TabLayout and ViewPager</a> instead.
+     */
+    @Deprecated
     public void addTab(@NonNull TabHost.TabSpec tabSpec, @NonNull Class<?> clss,
             @Nullable Bundle args) {
         tabSpec.setContent(new DummyTabFactory(mContext));
@@ -260,6 +288,12 @@
         addTab(tabSpec);
     }
 
+    /**
+     * @deprecated Use
+     * <a href="https://developer.android.com/guide/navigation/navigation-swipe-view ">
+     *  TabLayout and ViewPager</a> instead.
+     */
+    @Deprecated
     @Override
     protected void onAttachedToWindow() {
         super.onAttachedToWindow();
@@ -299,12 +333,24 @@
         }
     }
 
+    /**
+     * @deprecated Use
+     * <a href="https://developer.android.com/guide/navigation/navigation-swipe-view ">
+     *  TabLayout and ViewPager</a> instead.
+     */
+    @Deprecated
     @Override
     protected void onDetachedFromWindow() {
         super.onDetachedFromWindow();
         mAttached = false;
     }
 
+    /**
+     * @deprecated Use
+     * <a href="https://developer.android.com/guide/navigation/navigation-swipe-view ">
+     *  TabLayout and ViewPager</a> instead.
+     */
+    @Deprecated
     @Override
     @NonNull
     protected Parcelable onSaveInstanceState() {
@@ -314,6 +360,12 @@
         return ss;
     }
 
+    /**
+     * @deprecated Use
+     * <a href="https://developer.android.com/guide/navigation/navigation-swipe-view ">
+     *  TabLayout and ViewPager</a> instead.
+     */
+    @Deprecated
     @Override
     protected void onRestoreInstanceState(@SuppressLint("UnknownNullness") Parcelable state) {
         if (!(state instanceof SavedState)) {
@@ -325,6 +377,12 @@
         setCurrentTabByTag(ss.curTab);
     }
 
+    /**
+     * @deprecated Use
+     * <a href="https://developer.android.com/guide/navigation/navigation-swipe-view ">
+     *  TabLayout and ViewPager</a> instead.
+     */
+    @Deprecated
     @Override
     public void onTabChanged(@Nullable String tabId) {
         if (mAttached) {
diff --git a/gradle.properties b/gradle.properties
index fd60892..c788833 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -1,4 +1,4 @@
-org.gradle.jvmargs=-Xmx4g
+org.gradle.jvmargs=-Xmx8g
 org.gradle.daemon=true
 org.gradle.configureondemand=true
 org.gradle.parallel=true
diff --git a/graphics/drawable/animated/build.gradle b/graphics/drawable/animated/build.gradle
index 4ce8b2a..fae5204 100644
--- a/graphics/drawable/animated/build.gradle
+++ b/graphics/drawable/animated/build.gradle
@@ -8,7 +8,7 @@
 
 dependencies {
     api(project(":vectordrawable"))
-    implementation(project(":interpolator"))
+    implementation("androidx.interpolator:interpolator:1.0.0")
     implementation(project(":collection"))
 
     androidTestImplementation(TEST_EXT_JUNIT)
diff --git a/jetifier/jetifier/core/src/main/resources/default.config b/jetifier/jetifier/core/src/main/resources/default.config
index 52b33c3..4a18f8d 100644
--- a/jetifier/jetifier/core/src/main/resources/default.config
+++ b/jetifier/jetifier/core/src/main/resources/default.config
@@ -1534,14 +1534,38 @@
         #    "from": { "groupId": "android.arch.background.workmanager", "artifactId": "workmanager-firebase", "version": "{newArchVersion}" },
         #    "to": { "groupId": "androidx.work", "artifactId": "runtime-firebase", "version": "{newArchVersion}" }
         #},
-        #{
-        #    "from": { "groupId": "android.arch.navigation", "artifactId": "runtime", "version": "{newArchVersion}" },
-        #    "to": { "groupId": "androidx.navigation", "artifactId": "navigation-runtime", "version": "{newArchVersion}" }
-        #},
-        #{
-        #    "from": { "groupId": "android.arch.navigation", "artifactId": "fragment", "version": "{newArchVersion}" },
-        #    "to": { "groupId": "androidx.navigation", "artifactId": "navigation-fragment", "version": "{newArchVersion}" }
-        #},
+        {
+            "from": { "groupId": "android.arch.navigation", "artifactId": "common", "version": "{oldNavigationVersion}" },
+            "to": { "groupId": "androidx.navigation", "artifactId": "navigation-common", "version": "{newNavigationVersion}" }
+        },
+        {
+            "from": { "groupId": "android.arch.navigation", "artifactId": "common-ktx", "version": "{oldNavigationVersion}" },
+            "to": { "groupId": "androidx.navigation", "artifactId": "navigation-common-ktx", "version": "{newNavigationVersion}" }
+        },
+        {
+            "from": { "groupId": "android.arch.navigation", "artifactId": "fragment", "version": "{oldNavigationVersion}" },
+            "to": { "groupId": "androidx.navigation", "artifactId": "navigation-fragment", "version": "{newNavigationVersion}" }
+        },
+        {
+            "from": { "groupId": "android.arch.navigation", "artifactId": "fragment-ktx", "version": "{oldNavigationVersion}" },
+            "to": { "groupId": "androidx.navigation", "artifactId": "navigation-fragment-ktx", "version": "{newNavigationVersion}" }
+        },
+        {
+            "from": { "groupId": "android.arch.navigation", "artifactId": "runtime", "version": "{oldNavigationVersion}" },
+            "to": { "groupId": "androidx.navigation", "artifactId": "navigation-runtime", "version": "{newNavigationVersion}" }
+        },
+        {
+            "from": { "groupId": "android.arch.navigation", "artifactId": "runtime-ktx", "version": "{oldNavigationVersion}" },
+            "to": { "groupId": "androidx.navigation", "artifactId": "navigation-runtime-ktx", "version": "{newNavigationVersion}" }
+        },
+        {
+            "from": { "groupId": "android.arch.navigation", "artifactId": "ui", "version": "{oldNavigationVersion}" },
+            "to": { "groupId": "androidx.navigation", "artifactId": "navigation-ui", "version": "{newNavigationVersion}" }
+        },
+        {
+            "from": { "groupId": "android.arch.navigation", "artifactId": "ui-ktx", "version": "{oldNavigationVersion}" },
+            "to": { "groupId": "androidx.navigation", "artifactId": "navigation-ui-ktx", "version": "{newNavigationVersion}" }
+        },
         {
             "from": { "groupId": "android.arch.core", "artifactId": "common", "version": "1.1.1" },
             "to": { "groupId": "androidx.arch.core", "artifactId": "core-common", "version": "{newArchCoreVersion}" }
@@ -1770,6 +1794,7 @@
             "oldMedia2Version": "28.0.0-alpha03",
             "oldExoplayerVersion": "28.0.0-alpha01",
             "oldBiometricVersion": "28.0.0-alpha03",
+            "oldNavigationVersion": "1.0.0",
             "newSlVersion": "1.0.0",
             "newMaterialVersion": "1.0.0",
             "newArchCoreVersion": "2.0.0",
@@ -1785,7 +1810,8 @@
             "newMedia2Version": "1.0.0-alpha03",
             "newExoplayerVersion": "1.0.0-alpha01",
             "newBiometricVersion": "1.0.0-alpha03",
-            "newDataBindingVersion": "undefined"
+            "newDataBindingVersion": "undefined",
+            "newNavigationVersion": "2.0.0"
         }
     },
     # Manual fallback types map
diff --git a/jetifier/jetifier/core/src/main/resources/default.generated.config b/jetifier/jetifier/core/src/main/resources/default.generated.config
index 7c257f0..fd6d282 100644
--- a/jetifier/jetifier/core/src/main/resources/default.generated.config
+++ b/jetifier/jetifier/core/src/main/resources/default.generated.config
@@ -1928,6 +1928,102 @@
     },
     {
       "from": {
+        "groupId": "android.arch.navigation",
+        "artifactId": "common",
+        "version": "{oldNavigationVersion}"
+      },
+      "to": {
+        "groupId": "androidx.navigation",
+        "artifactId": "navigation-common",
+        "version": "{newNavigationVersion}"
+      }
+    },
+    {
+      "from": {
+        "groupId": "android.arch.navigation",
+        "artifactId": "common-ktx",
+        "version": "{oldNavigationVersion}"
+      },
+      "to": {
+        "groupId": "androidx.navigation",
+        "artifactId": "navigation-common-ktx",
+        "version": "{newNavigationVersion}"
+      }
+    },
+    {
+      "from": {
+        "groupId": "android.arch.navigation",
+        "artifactId": "fragment",
+        "version": "{oldNavigationVersion}"
+      },
+      "to": {
+        "groupId": "androidx.navigation",
+        "artifactId": "navigation-fragment",
+        "version": "{newNavigationVersion}"
+      }
+    },
+    {
+      "from": {
+        "groupId": "android.arch.navigation",
+        "artifactId": "fragment-ktx",
+        "version": "{oldNavigationVersion}"
+      },
+      "to": {
+        "groupId": "androidx.navigation",
+        "artifactId": "navigation-fragment-ktx",
+        "version": "{newNavigationVersion}"
+      }
+    },
+    {
+      "from": {
+        "groupId": "android.arch.navigation",
+        "artifactId": "runtime",
+        "version": "{oldNavigationVersion}"
+      },
+      "to": {
+        "groupId": "androidx.navigation",
+        "artifactId": "navigation-runtime",
+        "version": "{newNavigationVersion}"
+      }
+    },
+    {
+      "from": {
+        "groupId": "android.arch.navigation",
+        "artifactId": "runtime-ktx",
+        "version": "{oldNavigationVersion}"
+      },
+      "to": {
+        "groupId": "androidx.navigation",
+        "artifactId": "navigation-runtime-ktx",
+        "version": "{newNavigationVersion}"
+      }
+    },
+    {
+      "from": {
+        "groupId": "android.arch.navigation",
+        "artifactId": "ui",
+        "version": "{oldNavigationVersion}"
+      },
+      "to": {
+        "groupId": "androidx.navigation",
+        "artifactId": "navigation-ui",
+        "version": "{newNavigationVersion}"
+      }
+    },
+    {
+      "from": {
+        "groupId": "android.arch.navigation",
+        "artifactId": "ui-ktx",
+        "version": "{oldNavigationVersion}"
+      },
+      "to": {
+        "groupId": "androidx.navigation",
+        "artifactId": "navigation-ui-ktx",
+        "version": "{newNavigationVersion}"
+      }
+    },
+    {
+      "from": {
         "groupId": "android.arch.core",
         "artifactId": "common",
         "version": "1.1.1"
@@ -2561,6 +2657,7 @@
       "oldMedia2Version": "28.0.0-alpha03",
       "oldExoplayerVersion": "28.0.0-alpha01",
       "oldBiometricVersion": "28.0.0-alpha03",
+      "oldNavigationVersion": "1.0.0",
       "newSlVersion": "1.0.0",
       "newMaterialVersion": "1.0.0",
       "newArchCoreVersion": "2.0.0",
@@ -2576,7 +2673,8 @@
       "newMedia2Version": "1.0.0-alpha03",
       "newExoplayerVersion": "1.0.0-alpha01",
       "newBiometricVersion": "1.0.0-alpha03",
-      "newDataBindingVersion": "undefined"
+      "newDataBindingVersion": "undefined",
+      "newNavigationVersion": "2.0.0"
     }
   },
   "map": {
diff --git a/jetifier/jetifier/migration.config b/jetifier/jetifier/migration.config
index c1e50c9..2ba37b5 100644
--- a/jetifier/jetifier/migration.config
+++ b/jetifier/jetifier/migration.config
@@ -670,6 +670,14 @@
       "to": "ignore"
     },
     {
+      "from": "androidx/legacy/app/ActionBarDrawerToggle(.*)",
+      "to": "ignore"
+    },
+    {
+      "from": "androidx/legacy/widget/Space(.*)",
+      "to": "ignore"
+    },
+    {
       "from": "androidx/interpolator/view/animation/(.*)",
       "to": "ignore"
     },
@@ -722,6 +730,26 @@
       "to": "ignore"
     },
     {
+      "from": "android/support/customtabs/ICustomTabsCallback(.*)",
+      "to": "ignore"
+    },
+    {
+      "from": "android/support/customtabs/ICustomTabsService(.*)",
+      "to": "ignore"
+    },
+    {
+      "from": "android/support/customtabs/IPostMessageService(.*)",
+      "to": "ignore"
+    },
+    {
+      "from": "androidx/browser/customtabs/(.*)",
+      "to": "ignore"
+    },
+    {
+      "from": "androidx/browser/R(.*)",
+      "to": "ignore"
+    },
+    {
       "from": "androidx/legacy/view/ViewCompat(.*)",
       "to": "ignore"
     },
@@ -730,14 +758,6 @@
       "to": "ignore"
     },
     {
-      "from": "androidx/legacy/app/ActionBarDrawerToggle(.*)",
-      "to": "ignore"
-    },
-    {
-      "from": "androidx/legacy/widget/Space(.*)",
-      "to": "ignore"
-    },
-    {
       "from": "androidx/cardview/R(.*)",
       "to": "ignore"
     },
@@ -798,6 +818,18 @@
       "to": "ignore"
     },
     {
+      "from": "androidx/leanback/preference/internal/OutlineOnlyWithChildrenFrameLayout(.*)",
+      "to": "ignore"
+    },
+    {
+      "from": "androidx/leanback/preference/(.*)",
+      "to": "ignore"
+    },
+    {
+      "from": "androidx/leanback/(.*)",
+      "to": "ignore"
+    },
+    {
       "from": "androidx/print/(.*)",
       "to": "ignore"
     },
@@ -814,7 +846,7 @@
       "to": "ignore"
     },
     {
-      "from": "androidx/browser/(.*)",
+      "from": "androidx/browser/browseractions/(.*)",
       "to": "ignore"
     },
     {
@@ -846,18 +878,6 @@
       "to": "ignore"
     },
     {
-      "from": "androidx/leanback/preference/internal/OutlineOnlyWithChildrenFrameLayout(.*)",
-      "to": "ignore"
-    },
-    {
-      "from": "androidx/leanback/preference/(.*)",
-      "to": "ignore"
-    },
-    {
-      "from": "androidx/leanback/(.*)",
-      "to": "ignore"
-    },
-    {
       "from": "androidx/media2/(.*)",
       "to": "ignore"
     },
diff --git a/legacy/v13/src/main/java/androidx/legacy/app/FragmentTabHost.java b/legacy/v13/src/main/java/androidx/legacy/app/FragmentTabHost.java
index 98de1bb..09f23eb 100644
--- a/legacy/v13/src/main/java/androidx/legacy/app/FragmentTabHost.java
+++ b/legacy/v13/src/main/java/androidx/legacy/app/FragmentTabHost.java
@@ -39,7 +39,8 @@
  * used with the platform {@link android.app.Fragment} APIs.  You will not
  * normally use this, instead using action bar tabs.
  *
- * @deprecated Use {@link androidx.fragment.app.FragmentTabHost} instead.
+ * @deprecated Use <a href="https://developer.android.com/guide/navigation/navigation-swipe-view ">
+ *     TabLayout and ViewPager</a> instead.
  */
 @Deprecated
 public class FragmentTabHost extends TabHost implements TabHost.OnTabChangeListener {
@@ -121,7 +122,9 @@
     }
 
     /**
-     * @deprecated Use {@link androidx.fragment.app.FragmentTabHost} instead.
+     * @deprecated Use
+     * <a href="https://developer.android.com/guide/navigation/navigation-swipe-view ">
+     *  TabLayout and ViewPager</a> instead.
      */
     @Deprecated
     public FragmentTabHost(Context context) {
@@ -132,7 +135,9 @@
     }
 
     /**
-     * @deprecated Use {@link androidx.fragment.app.FragmentTabHost} instead.
+     * @deprecated Use
+     * <a href="https://developer.android.com/guide/navigation/navigation-swipe-view ">
+     *  TabLayout and ViewPager</a> instead.
      */
     @Deprecated
     public FragmentTabHost(Context context, AttributeSet attrs) {
@@ -178,7 +183,9 @@
     }
 
     /**
-     * @deprecated Use {@link androidx.fragment.app.FragmentTabHost} instead.
+     * @deprecated Use
+     * <a href="https://developer.android.com/guide/navigation/navigation-swipe-view ">
+     *  TabLayout and ViewPager</a> instead.
      */
     @Override
     @Deprecated
@@ -188,7 +195,9 @@
     }
 
     /**
-     * @deprecated Use {@link androidx.fragment.app.FragmentTabHost} instead.
+     * @deprecated Use
+     * <a href="https://developer.android.com/guide/navigation/navigation-swipe-view ">
+     *  TabLayout and ViewPager</a> instead.
      */
     @Deprecated
     public void setup(Context context, FragmentManager manager) {
@@ -200,7 +209,9 @@
     }
 
     /**
-     * @deprecated Use {@link androidx.fragment.app.FragmentTabHost} instead.
+     * @deprecated Use
+     * <a href="https://developer.android.com/guide/navigation/navigation-swipe-view ">
+     *  TabLayout and ViewPager</a> instead.
      */
     @Deprecated
     public void setup(Context context, FragmentManager manager, int containerId) {
@@ -230,7 +241,9 @@
     }
 
     /**
-     * @deprecated Use {@link androidx.fragment.app.FragmentTabHost} instead.
+     * @deprecated Use
+     * <a href="https://developer.android.com/guide/navigation/navigation-swipe-view ">
+     *  TabLayout and ViewPager</a> instead.
      */
     @Deprecated
     @Override
@@ -239,7 +252,9 @@
     }
 
     /**
-     * @deprecated Use {@link androidx.fragment.app.FragmentTabHost} instead.
+     * @deprecated Use
+     * <a href="https://developer.android.com/guide/navigation/navigation-swipe-view ">
+     *  TabLayout and ViewPager</a> instead.
      */
     @Deprecated
     public void addTab(TabHost.TabSpec tabSpec, Class<?> clss, Bundle args) {
@@ -265,7 +280,9 @@
     }
 
     /**
-     * @deprecated Use {@link androidx.fragment.app.FragmentTabHost} instead.
+     * @deprecated Use
+     * <a href="https://developer.android.com/guide/navigation/navigation-swipe-view ">
+     *  TabLayout and ViewPager</a> instead.
      */
     @Deprecated
     @Override
@@ -308,7 +325,9 @@
     }
 
     /**
-     * @deprecated Use {@link androidx.fragment.app.FragmentTabHost} instead.
+     * @deprecated Use
+     * <a href="https://developer.android.com/guide/navigation/navigation-swipe-view ">
+     *  TabLayout and ViewPager</a> instead.
      */
     @Deprecated
     @Override
@@ -318,7 +337,9 @@
     }
 
     /**
-     * @deprecated Use {@link androidx.fragment.app.FragmentTabHost} instead.
+     * @deprecated Use
+     * <a href="https://developer.android.com/guide/navigation/navigation-swipe-view ">
+     *  TabLayout and ViewPager</a> instead.
      */
     @Deprecated
     @Override
@@ -330,7 +351,9 @@
     }
 
     /**
-     * @deprecated Use {@link androidx.fragment.app.FragmentTabHost} instead.
+     * @deprecated Use
+     * <a href="https://developer.android.com/guide/navigation/navigation-swipe-view ">
+     *  TabLayout and ViewPager</a> instead.
      */
     @Deprecated
     @Override
@@ -345,7 +368,9 @@
     }
 
     /**
-     * @deprecated Use {@link androidx.fragment.app.FragmentTabHost} instead.
+     * @deprecated Use
+     * <a href="https://developer.android.com/guide/navigation/navigation-swipe-view ">
+     *  TabLayout and ViewPager</a> instead.
      */
     @Deprecated
     @Override
diff --git a/media/build.gradle b/media/build.gradle
index 26e120a..16eb997 100644
--- a/media/build.gradle
+++ b/media/build.gradle
@@ -7,7 +7,7 @@
 }
 
 dependencies {
-    api(project(":core"))
+    api("androidx.core:core:1.1.0-alpha05")
     implementation("androidx.collection:collection:1.0.0")
 
     androidTestImplementation(TEST_EXT_JUNIT)
diff --git a/media2-widget/src/androidTest/java/androidx/media2/widget/MediaControlViewTest.java b/media2-widget/src/androidTest/java/androidx/media2/widget/MediaControlViewTest.java
index e6acd7e..c381616 100644
--- a/media2-widget/src/androidTest/java/androidx/media2/widget/MediaControlViewTest.java
+++ b/media2-widget/src/androidTest/java/androidx/media2/widget/MediaControlViewTest.java
@@ -462,7 +462,7 @@
     }
 
     private MediaItem createTestMediaItem2(Uri uri) {
-        return new UriMediaItem.Builder(mVideoView.getContext(), uri).build();
+        return new UriMediaItem.Builder(uri).build();
     }
 
     private MediaController createController(MediaController.ControllerCallback callback) {
diff --git a/media2-widget/src/androidTest/java/androidx/media2/widget/VideoViewTest.java b/media2-widget/src/androidTest/java/androidx/media2/widget/VideoViewTest.java
index 5d4bf45..4f01d40 100644
--- a/media2-widget/src/androidTest/java/androidx/media2/widget/VideoViewTest.java
+++ b/media2-widget/src/androidTest/java/androidx/media2/widget/VideoViewTest.java
@@ -398,7 +398,6 @@
         Uri testVideoUri = Uri.parse(
                 "android.resource://" + mContext.getPackageName() + "/"
                         + R.raw.testvideo_with_2_subtitle_tracks);
-        return new UriMediaItem.Builder(mVideoView.getContext(), testVideoUri)
-                .build();
+        return new UriMediaItem.Builder(testVideoUri).build();
     }
 }
diff --git a/media2-widget/src/main/java/androidx/media2/widget/MediaControlView.java b/media2-widget/src/main/java/androidx/media2/widget/MediaControlView.java
index dcb8bf8..37d53e9 100644
--- a/media2-widget/src/main/java/androidx/media2/widget/MediaControlView.java
+++ b/media2-widget/src/main/java/androidx/media2/widget/MediaControlView.java
@@ -1243,16 +1243,8 @@
                     if (position != mSelectedSubtitleTrackIndex) {
                         if (position > 0) {
                             mController.showSubtitle(position - 1);
-                            mSubtitleButton.setImageDrawable(
-                                    mResources.getDrawable(R.drawable.ic_subtitle_on));
-                            mSubtitleButton.setContentDescription(
-                                    mResources.getString(R.string.mcv2_cc_is_on));
                         } else {
                             mController.hideSubtitle();
-                            mSubtitleButton.setImageDrawable(
-                                    mResources.getDrawable(R.drawable.ic_subtitle_off));
-                            mSubtitleButton.setContentDescription(
-                                    mResources.getString(R.string.mcv2_cc_is_off));
                         }
                     }
                     dismissSettingsWindow();
@@ -2211,12 +2203,20 @@
                         if (mSettingsMode == SETTINGS_MODE_SUBTITLE_TRACK) {
                             mSubSettingsAdapter.setCheckPosition(mSelectedSubtitleTrackIndex);
                         }
+                        mSubtitleButton.setImageDrawable(
+                                mResources.getDrawable(R.drawable.ic_subtitle_on));
+                        mSubtitleButton.setContentDescription(
+                                mResources.getString(R.string.mcv2_cc_is_on));
                         break;
                     case EVENT_UPDATE_SUBTITLE_DESELECTED:
                         mSelectedSubtitleTrackIndex = 0;
                         if (mSettingsMode == SETTINGS_MODE_SUBTITLE_TRACK) {
                             mSubSettingsAdapter.setCheckPosition(mSelectedSubtitleTrackIndex);
                         }
+                        mSubtitleButton.setImageDrawable(
+                                mResources.getDrawable(R.drawable.ic_subtitle_off));
+                        mSubtitleButton.setContentDescription(
+                                mResources.getString(R.string.mcv2_cc_is_off));
                         break;
                     default:
                         return new SessionResult(
diff --git a/media2-widget/src/main/java/androidx/media2/widget/VideoView.java b/media2-widget/src/main/java/androidx/media2/widget/VideoView.java
index cf1ba6f..0ab34d2 100644
--- a/media2-widget/src/main/java/androidx/media2/widget/VideoView.java
+++ b/media2-widget/src/main/java/androidx/media2/widget/VideoView.java
@@ -197,7 +197,7 @@
     int mSelectedAudioTrackIndex;
     int mSelectedSubtitleTrackIndex;
 
-    private SubtitleAnchorView mSubtitleAnchorView;
+    SubtitleAnchorView mSubtitleAnchorView;
 
     private MediaRouter mMediaRouter;
     @SuppressWarnings("WeakerAccess") /* synthetic access */
@@ -296,9 +296,6 @@
             if (DEBUG) {
                 Log.d(TAG, "onSurfaceTakeOverDone(). Now current view is: " + view);
             }
-            if (mCurrentState != STATE_PLAYING && mMediaSession != null) {
-                mMediaSession.getPlayer().seekTo(mMediaSession.getPlayer().getCurrentPosition());
-            }
             if (view != mCurrentView) {
                 ((View) mCurrentView).setVisibility(View.GONE);
                 mCurrentView = view;
@@ -467,7 +464,14 @@
 
     /**
      * Selects which view will be used to render video between SurfaceView and TextureView.
-     *
+     * <p>
+     * Note: There are two known issues on API level 28+ devices.
+     * <ul>
+     * <li> When changing view type to SurfaceView from TextureView in "paused" playback state,
+     * a blank screen can be shown.
+     * <li> When changing view type to TextureView from SurfaceView repeatedly in "paused" playback
+     * state, the lastly rendered frame on TextureView can be shown.
+     * </ul>
      * @param viewType the view type to render video
      * <ul>
      * <li>{@link #VIEW_TYPE_SURFACEVIEW}
@@ -685,7 +689,35 @@
             mMediaPlayer.setMediaItem(mMediaItem);
 
             final Context context = getContext();
-            mSubtitleController = new SubtitleController(context);
+            SubtitleController.Listener listener = new SubtitleController.Listener() {
+                @Override
+                public void onSubtitleTrackSelected(SubtitleTrack track) {
+                    if (track == null) {
+                        mMediaPlayer.deselectTrack(mSelectedSubtitleTrackIndex);
+                        mSelectedSubtitleTrackIndex = INVALID_TRACK_INDEX;
+                        mSubtitleAnchorView.setVisibility(View.GONE);
+
+                        mMediaSession.broadcastCustomCommand(new SessionCommand(
+                                MediaControlView.EVENT_UPDATE_SUBTITLE_DESELECTED, null), null);
+                        return;
+                    }
+                    int indexInSubtitleTrackList = mSubtitleTracks.indexOfValue(track);
+                    if (indexInSubtitleTrackList >= 0) {
+                        int indexInEntireTrackList =
+                                mSubtitleTracks.keyAt(indexInSubtitleTrackList);
+                        mMediaPlayer.selectTrack(indexInEntireTrackList);
+                        mSelectedSubtitleTrackIndex = indexInEntireTrackList;
+                        mSubtitleAnchorView.setVisibility(View.VISIBLE);
+
+                        Bundle data = new Bundle();
+                        data.putInt(MediaControlView.KEY_SELECTED_SUBTITLE_INDEX,
+                                indexInSubtitleTrackList);
+                        mMediaSession.broadcastCustomCommand(new SessionCommand(
+                                MediaControlView.EVENT_UPDATE_SUBTITLE_SELECTED, null), data);
+                    }
+                }
+            };
+            mSubtitleController = new SubtitleController(context, null, listener);
             mSubtitleController.registerRenderer(new ClosedCaptionRenderer(context));
             mSubtitleController.registerRenderer(new Cea708CaptionRenderer(context));
             mSubtitleController.setAnchor(mSubtitleAnchorView);
@@ -728,17 +760,7 @@
         }
         SubtitleTrack track = mSubtitleTracks.get(trackIndex);
         if (track != null) {
-            mMediaPlayer.selectTrack(trackIndex);
             mSubtitleController.selectTrack(track);
-            mSelectedSubtitleTrackIndex = trackIndex;
-            mSubtitleAnchorView.setVisibility(View.VISIBLE);
-
-            Bundle data = new Bundle();
-            data.putInt(MediaControlView.KEY_SELECTED_SUBTITLE_INDEX,
-                    mSubtitleTracks.indexOfKey(trackIndex));
-            mMediaSession.broadcastCustomCommand(
-                    new SessionCommand(MediaControlView.EVENT_UPDATE_SUBTITLE_SELECTED, null),
-                    data);
         }
     }
 
@@ -746,13 +768,7 @@
         if (!isMediaPrepared() || mSelectedSubtitleTrackIndex == INVALID_TRACK_INDEX) {
             return;
         }
-        mMediaPlayer.deselectTrack(mSelectedSubtitleTrackIndex);
-        mSelectedSubtitleTrackIndex = INVALID_TRACK_INDEX;
-        mSubtitleAnchorView.setVisibility(View.GONE);
-
-        mMediaSession.broadcastCustomCommand(
-                new SessionCommand(MediaControlView.EVENT_UPDATE_SUBTITLE_DESELECTED, null),
-                null);
+        mSubtitleController.selectTrack(null);
     }
 
     // TODO: move this method inside callback to make sure it runs inside the callback thread.
@@ -762,6 +778,7 @@
         mAudioTrackIndices = new ArrayList<>();
         mSubtitleTracks = new SparseArray<>();
         ArrayList<String> subtitleTracksLanguageList = new ArrayList<>();
+        int selectedSubtitleTrackIndex = mSelectedSubtitleTrackIndex;
         mSubtitleController.reset();
         for (int i = 0; i < trackInfos.size(); ++i) {
             int trackType = trackInfos.get(i).getTrackType();
@@ -773,9 +790,7 @@
                 SubtitleTrack track = mSubtitleController.addTrack(trackInfos.get(i).getFormat());
                 if (track != null) {
                     mSubtitleTracks.put(i, track);
-                    String language =
-                            (trackInfos.get(i).getLanguage().equals(SUBTITLE_TRACK_LANG_UNDEFINED))
-                                    ? "" : trackInfos.get(i).getLanguage();
+                    String language = trackInfos.get(i).getLanguage().getISO3Language();
                     subtitleTracksLanguageList.add(language);
                 }
             }
@@ -784,6 +799,10 @@
         if (mAudioTrackIndices.size() > 0) {
             mSelectedAudioTrackIndex = 0;
         }
+        // Re-select originally selected subtitle track since SubtitleController has been reset.
+        if (selectedSubtitleTrackIndex != INVALID_TRACK_INDEX) {
+            selectSubtitleTrack(selectedSubtitleTrackIndex);
+        }
 
         Bundle data = new Bundle();
         data.putInt(MediaControlView.KEY_VIDEO_TRACK_COUNT, mVideoTrackIndices.size());
@@ -1059,13 +1078,14 @@
             }
             switch (customCommand.getCustomCommand()) {
                 case MediaControlView.COMMAND_SHOW_SUBTITLE:
-                    int subtitleIndex = args != null ? args.getInt(
+                    int indexInSubtitleTrackList = args != null ? args.getInt(
                             MediaControlView.KEY_SELECTED_SUBTITLE_INDEX,
                             INVALID_TRACK_INDEX) : INVALID_TRACK_INDEX;
-                    if (subtitleIndex != INVALID_TRACK_INDEX) {
-                        int subtitleTrackIndex = mSubtitleTracks.keyAt(subtitleIndex);
-                        if (subtitleTrackIndex != mSelectedSubtitleTrackIndex) {
-                            selectSubtitleTrack(subtitleTrackIndex);
+                    if (indexInSubtitleTrackList != INVALID_TRACK_INDEX) {
+                        int indexInEntireTrackList =
+                                mSubtitleTracks.keyAt(indexInSubtitleTrackList);
+                        if (indexInEntireTrackList != mSelectedSubtitleTrackIndex) {
+                            selectSubtitleTrack(indexInEntireTrackList);
                         }
                     }
                     break;
diff --git a/media2/api/1.0.0-alpha05.txt b/media2/api/1.0.0-alpha05.txt
index ba118d1..d753947 100644
--- a/media2/api/1.0.0-alpha05.txt
+++ b/media2/api/1.0.0-alpha05.txt
@@ -316,7 +316,7 @@
     method public float getMaxPlayerVolume();
     method public int getNextMediaItemIndex();
     method public androidx.media2.PlaybackParams getPlaybackParams();
-    method public float getPlaybackSpeed();
+    method @FloatRange(from=0.0f, to=java.lang.Float.MAX_VALUE, fromInclusive=false) public float getPlaybackSpeed();
     method public int getPlayerState();
     method public float getPlayerVolume();
     method public java.util.List<androidx.media2.MediaItem>? getPlaylist();
@@ -331,6 +331,7 @@
     method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.SessionPlayer.PlayerResult> pause();
     method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.SessionPlayer.PlayerResult> play();
     method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.SessionPlayer.PlayerResult> prepare();
+    method public void registerPlayerCallback(java.util.concurrent.Executor, androidx.media2.MediaPlayer.PlayerCallback);
     method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.SessionPlayer.PlayerResult> removePlaylistItem(@IntRange(from=0) int);
     method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.SessionPlayer.PlayerResult> replacePlaylistItem(int, androidx.media2.MediaItem);
     method public void reset();
@@ -342,7 +343,7 @@
     method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.SessionPlayer.PlayerResult> setAuxEffectSendLevel(@FloatRange(from=0, to=1) float);
     method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.SessionPlayer.PlayerResult> setMediaItem(androidx.media2.MediaItem);
     method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.SessionPlayer.PlayerResult> setPlaybackParams(androidx.media2.PlaybackParams);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.SessionPlayer.PlayerResult> setPlaybackSpeed(@FloatRange(from=0, to=1) float);
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.SessionPlayer.PlayerResult> setPlaybackSpeed(@FloatRange(from=0.0f, to=java.lang.Float.MAX_VALUE, fromInclusive=false) float);
     method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.SessionPlayer.PlayerResult> setPlayerVolume(@FloatRange(from=0, to=1) float);
     method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.SessionPlayer.PlayerResult> setPlaylist(java.util.List<androidx.media2.MediaItem>, androidx.media2.MediaMetadata?);
     method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.SessionPlayer.PlayerResult> setRepeatMode(int);
@@ -351,6 +352,7 @@
     method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.SessionPlayer.PlayerResult> skipToNextPlaylistItem();
     method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.SessionPlayer.PlayerResult> skipToPlaylistItem(@IntRange(from=0) int);
     method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.SessionPlayer.PlayerResult> skipToPreviousPlaylistItem();
+    method public void unregisterPlayerCallback(androidx.media2.MediaPlayer.PlayerCallback);
     method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.SessionPlayer.PlayerResult> updatePlaylistMetadata(androidx.media2.MediaMetadata?);
     field public static final int MEDIA_INFO_AUDIO_NOT_PLAYING = 804; // 0x324
     field public static final int MEDIA_INFO_BAD_INTERLEAVING = 800; // 0x320
@@ -360,6 +362,7 @@
     field public static final int MEDIA_INFO_VIDEO_NOT_PLAYING = 805; // 0x325
     field public static final int MEDIA_INFO_VIDEO_RENDERING_START = 3; // 0x3
     field public static final int MEDIA_INFO_VIDEO_TRACK_LAGGING = 700; // 0x2bc
+    field public static final int NO_TRACK_SELECTED = -2147483648; // 0x80000000
     field public static final int PLAYER_ERROR_IO = -1004; // 0xfffffc14
     field public static final int PLAYER_ERROR_MALFORMED = -1007; // 0xfffffc11
     field public static final int PLAYER_ERROR_TIMED_OUT = -110; // 0xffffff92
@@ -383,7 +386,7 @@
 
   public static final class MediaPlayer.TrackInfo {
     method public android.media.MediaFormat? getFormat();
-    method public String getLanguage();
+    method public java.util.Locale getLanguage();
     method public int getTrackType();
     field public static final int MEDIA_TRACK_TYPE_AUDIO = 2; // 0x2
     field public static final int MEDIA_TRACK_TYPE_METADATA = 5; // 0x5
@@ -501,8 +504,8 @@
     ctor public PlaybackParams.Builder(androidx.media2.PlaybackParams);
     method public androidx.media2.PlaybackParams build();
     method public androidx.media2.PlaybackParams.Builder setAudioFallbackMode(int);
-    method public androidx.media2.PlaybackParams.Builder setPitch(float);
-    method public androidx.media2.PlaybackParams.Builder setSpeed(float);
+    method public androidx.media2.PlaybackParams.Builder setPitch(@FloatRange(from=0.0f, to=java.lang.Float.MAX_VALUE) float);
+    method public androidx.media2.PlaybackParams.Builder setSpeed(@FloatRange(from=0.0f, to=java.lang.Float.MAX_VALUE, fromInclusive=false) float);
   }
 
   public interface Rating extends androidx.versionedparcelable.VersionedParcelable {
@@ -724,14 +727,13 @@
 
   public class UriMediaItem extends androidx.media2.MediaItem {
     method public android.net.Uri getUri();
-    method public android.content.Context getUriContext();
     method public java.util.List<java.net.HttpCookie>? getUriCookies();
     method public java.util.Map<java.lang.String,java.lang.String>? getUriHeaders();
   }
 
   public static final class UriMediaItem.Builder extends androidx.media2.MediaItem.Builder {
-    ctor public UriMediaItem.Builder(android.content.Context, android.net.Uri);
-    ctor public UriMediaItem.Builder(android.content.Context, android.net.Uri, java.util.Map<java.lang.String,java.lang.String>?, java.util.List<java.net.HttpCookie>?);
+    ctor public UriMediaItem.Builder(android.net.Uri);
+    ctor public UriMediaItem.Builder(android.net.Uri, java.util.Map<java.lang.String,java.lang.String>?, java.util.List<java.net.HttpCookie>?);
     method public androidx.media2.UriMediaItem build();
     method public androidx.media2.UriMediaItem.Builder setEndPosition(long);
     method public androidx.media2.UriMediaItem.Builder setMetadata(androidx.media2.MediaMetadata?);
diff --git a/media2/api/current.txt b/media2/api/current.txt
index ba118d1..d753947 100644
--- a/media2/api/current.txt
+++ b/media2/api/current.txt
@@ -316,7 +316,7 @@
     method public float getMaxPlayerVolume();
     method public int getNextMediaItemIndex();
     method public androidx.media2.PlaybackParams getPlaybackParams();
-    method public float getPlaybackSpeed();
+    method @FloatRange(from=0.0f, to=java.lang.Float.MAX_VALUE, fromInclusive=false) public float getPlaybackSpeed();
     method public int getPlayerState();
     method public float getPlayerVolume();
     method public java.util.List<androidx.media2.MediaItem>? getPlaylist();
@@ -331,6 +331,7 @@
     method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.SessionPlayer.PlayerResult> pause();
     method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.SessionPlayer.PlayerResult> play();
     method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.SessionPlayer.PlayerResult> prepare();
+    method public void registerPlayerCallback(java.util.concurrent.Executor, androidx.media2.MediaPlayer.PlayerCallback);
     method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.SessionPlayer.PlayerResult> removePlaylistItem(@IntRange(from=0) int);
     method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.SessionPlayer.PlayerResult> replacePlaylistItem(int, androidx.media2.MediaItem);
     method public void reset();
@@ -342,7 +343,7 @@
     method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.SessionPlayer.PlayerResult> setAuxEffectSendLevel(@FloatRange(from=0, to=1) float);
     method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.SessionPlayer.PlayerResult> setMediaItem(androidx.media2.MediaItem);
     method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.SessionPlayer.PlayerResult> setPlaybackParams(androidx.media2.PlaybackParams);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.SessionPlayer.PlayerResult> setPlaybackSpeed(@FloatRange(from=0, to=1) float);
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.SessionPlayer.PlayerResult> setPlaybackSpeed(@FloatRange(from=0.0f, to=java.lang.Float.MAX_VALUE, fromInclusive=false) float);
     method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.SessionPlayer.PlayerResult> setPlayerVolume(@FloatRange(from=0, to=1) float);
     method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.SessionPlayer.PlayerResult> setPlaylist(java.util.List<androidx.media2.MediaItem>, androidx.media2.MediaMetadata?);
     method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.SessionPlayer.PlayerResult> setRepeatMode(int);
@@ -351,6 +352,7 @@
     method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.SessionPlayer.PlayerResult> skipToNextPlaylistItem();
     method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.SessionPlayer.PlayerResult> skipToPlaylistItem(@IntRange(from=0) int);
     method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.SessionPlayer.PlayerResult> skipToPreviousPlaylistItem();
+    method public void unregisterPlayerCallback(androidx.media2.MediaPlayer.PlayerCallback);
     method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.SessionPlayer.PlayerResult> updatePlaylistMetadata(androidx.media2.MediaMetadata?);
     field public static final int MEDIA_INFO_AUDIO_NOT_PLAYING = 804; // 0x324
     field public static final int MEDIA_INFO_BAD_INTERLEAVING = 800; // 0x320
@@ -360,6 +362,7 @@
     field public static final int MEDIA_INFO_VIDEO_NOT_PLAYING = 805; // 0x325
     field public static final int MEDIA_INFO_VIDEO_RENDERING_START = 3; // 0x3
     field public static final int MEDIA_INFO_VIDEO_TRACK_LAGGING = 700; // 0x2bc
+    field public static final int NO_TRACK_SELECTED = -2147483648; // 0x80000000
     field public static final int PLAYER_ERROR_IO = -1004; // 0xfffffc14
     field public static final int PLAYER_ERROR_MALFORMED = -1007; // 0xfffffc11
     field public static final int PLAYER_ERROR_TIMED_OUT = -110; // 0xffffff92
@@ -383,7 +386,7 @@
 
   public static final class MediaPlayer.TrackInfo {
     method public android.media.MediaFormat? getFormat();
-    method public String getLanguage();
+    method public java.util.Locale getLanguage();
     method public int getTrackType();
     field public static final int MEDIA_TRACK_TYPE_AUDIO = 2; // 0x2
     field public static final int MEDIA_TRACK_TYPE_METADATA = 5; // 0x5
@@ -501,8 +504,8 @@
     ctor public PlaybackParams.Builder(androidx.media2.PlaybackParams);
     method public androidx.media2.PlaybackParams build();
     method public androidx.media2.PlaybackParams.Builder setAudioFallbackMode(int);
-    method public androidx.media2.PlaybackParams.Builder setPitch(float);
-    method public androidx.media2.PlaybackParams.Builder setSpeed(float);
+    method public androidx.media2.PlaybackParams.Builder setPitch(@FloatRange(from=0.0f, to=java.lang.Float.MAX_VALUE) float);
+    method public androidx.media2.PlaybackParams.Builder setSpeed(@FloatRange(from=0.0f, to=java.lang.Float.MAX_VALUE, fromInclusive=false) float);
   }
 
   public interface Rating extends androidx.versionedparcelable.VersionedParcelable {
@@ -724,14 +727,13 @@
 
   public class UriMediaItem extends androidx.media2.MediaItem {
     method public android.net.Uri getUri();
-    method public android.content.Context getUriContext();
     method public java.util.List<java.net.HttpCookie>? getUriCookies();
     method public java.util.Map<java.lang.String,java.lang.String>? getUriHeaders();
   }
 
   public static final class UriMediaItem.Builder extends androidx.media2.MediaItem.Builder {
-    ctor public UriMediaItem.Builder(android.content.Context, android.net.Uri);
-    ctor public UriMediaItem.Builder(android.content.Context, android.net.Uri, java.util.Map<java.lang.String,java.lang.String>?, java.util.List<java.net.HttpCookie>?);
+    ctor public UriMediaItem.Builder(android.net.Uri);
+    ctor public UriMediaItem.Builder(android.net.Uri, java.util.Map<java.lang.String,java.lang.String>?, java.util.List<java.net.HttpCookie>?);
     method public androidx.media2.UriMediaItem build();
     method public androidx.media2.UriMediaItem.Builder setEndPosition(long);
     method public androidx.media2.UriMediaItem.Builder setMetadata(androidx.media2.MediaMetadata?);
diff --git a/media2/src/androidTest/java/androidx/media2/MediaPlayer2DrmTestBase.java b/media2/src/androidTest/java/androidx/media2/MediaPlayer2DrmTestBase.java
index cd59aca..15eb2ba 100644
--- a/media2/src/androidTest/java/androidx/media2/MediaPlayer2DrmTestBase.java
+++ b/media2/src/androidTest/java/androidx/media2/MediaPlayer2DrmTestBase.java
@@ -302,8 +302,7 @@
 
         mPlayer.setEventCallback(mExecutor, mECb);
         Log.v(TAG, "playLoadedVideo: setMediaItem()");
-        mPlayer.setMediaItem(
-                new UriMediaItem.Builder(mContext, file).build());
+        mPlayer.setMediaItem(new UriMediaItem.Builder(file).build());
         mSetDataSourceCallCompleted.waitForSignal();
         if (mCallStatus != MediaPlayer2.CALL_STATUS_NO_ERROR) {
             throw new PrepareFailedException();
@@ -557,8 +556,7 @@
                 mPlayer.setEventCallback(mExecutor, mECb);
 
                 Log.v(TAG, "playLoadedVideo: setMediaItem()");
-                mPlayer.setMediaItem(
-                        new UriMediaItem.Builder(mContext, file).build());
+                mPlayer.setMediaItem(new UriMediaItem.Builder(file).build());
 
                 Log.v(TAG, "playLoadedVideo: prepare()");
                 mPlayer.prepare();
diff --git a/media2/src/androidTest/java/androidx/media2/MediaPlayer2Test.java b/media2/src/androidTest/java/androidx/media2/MediaPlayer2Test.java
index 1ac7d97..d7827d1 100644
--- a/media2/src/androidTest/java/androidx/media2/MediaPlayer2Test.java
+++ b/media2/src/androidTest/java/androidx/media2/MediaPlayer2Test.java
@@ -219,7 +219,7 @@
             // test stop and restart
             mp.reset();
             mp.setEventCallback(mExecutor, ecb);
-            mp.setMediaItem(new UriMediaItem.Builder(mContext, uri).build());
+            mp.setMediaItem(new UriMediaItem.Builder(uri).build());
             onPrepareCalled.reset();
             mp.prepare();
             onPrepareCalled.waitForSignal();
@@ -2335,7 +2335,7 @@
             Uri uri = Uri.parse(outputFileLocation);
             MediaPlayer2 mp = MediaPlayer2.create(mActivity);
             try {
-                mp.setMediaItem(new UriMediaItem.Builder(mContext, uri).build());
+                mp.setMediaItem(new UriMediaItem.Builder(uri).build());
                 mp.prepare();
                 Thread.sleep(SLEEP_TIME);
                 playAndStop(mp);
diff --git a/media2/src/androidTest/java/androidx/media2/MediaPlayer2TestBase.java b/media2/src/androidTest/java/androidx/media2/MediaPlayer2TestBase.java
index c473620..13dd115 100644
--- a/media2/src/androidTest/java/androidx/media2/MediaPlayer2TestBase.java
+++ b/media2/src/androidTest/java/androidx/media2/MediaPlayer2TestBase.java
@@ -121,7 +121,7 @@
                     new AudioAttributesCompat.Builder().build();
             mp.setAudioAttributes(aa);
             mp.setAudioSessionId(audioSessionId);
-            mp.setMediaItem(new UriMediaItem.Builder(context, uri).build());
+            mp.setMediaItem(new UriMediaItem.Builder(uri).build());
             if (holder != null) {
                 mp.setSurface(holder.getSurface());
             }
@@ -417,7 +417,7 @@
         final Uri uri = Uri.parse(path);
         for (int i = 0; i < STREAM_RETRIES; i++) {
             try {
-                mPlayer.setMediaItem(new UriMediaItem.Builder(mContext, uri).build());
+                mPlayer.setMediaItem(new UriMediaItem.Builder(uri).build());
                 playLoadedVideo(width, height, playTime);
                 playedSuccessfully = true;
                 break;
@@ -449,8 +449,7 @@
         boolean playedSuccessfully = false;
         for (int i = 0; i < STREAM_RETRIES; i++) {
             try {
-                mPlayer.setMediaItem(new UriMediaItem.Builder(
-                        mContext, uri, headers, cookies).build());
+                mPlayer.setMediaItem(new UriMediaItem.Builder(uri, headers, cookies).build());
                 playLoadedVideo(width, height, playTime);
                 playedSuccessfully = true;
                 break;
diff --git a/media2/src/androidTest/java/androidx/media2/MediaPlayerDrmTest.java b/media2/src/androidTest/java/androidx/media2/MediaPlayerDrmTest.java
index 0195c56..ca8caec 100644
--- a/media2/src/androidTest/java/androidx/media2/MediaPlayerDrmTest.java
+++ b/media2/src/androidTest/java/androidx/media2/MediaPlayerDrmTest.java
@@ -402,7 +402,7 @@
         mPlayer.registerPlayerCallback(mExecutor, mECb);
         Log.v(TAG, "playLoadedVideo: setMediaItem()");
         ListenableFuture<PlayerResult> future =
-                mPlayer.setMediaItem(new UriMediaItem.Builder(mContext, file).build());
+                mPlayer.setMediaItem(new UriMediaItem.Builder(file).build());
         assertEquals(PlayerResult.RESULT_SUCCESS, future.get().getResultCode());
 
         SurfaceHolder surfaceHolder = mActivity.getSurfaceHolder();
@@ -645,8 +645,7 @@
                 mPlayer.registerPlayerCallback(mExecutor, mECb);
 
                 Log.v(TAG, "playLoadedVideo: setMediaItem()");
-                mPlayer.setMediaItem(
-                        new UriMediaItem.Builder(mContext, file).build());
+                mPlayer.setMediaItem(new UriMediaItem.Builder(file).build());
 
                 Log.v(TAG, "playLoadedVideo: prepare()");
                 ListenableFuture<PlayerResult> future = mPlayer.prepare();
diff --git a/media2/src/androidTest/java/androidx/media2/MediaPlayerTestBase.java b/media2/src/androidTest/java/androidx/media2/MediaPlayerTestBase.java
index 50cbb19..9a19cb9 100644
--- a/media2/src/androidTest/java/androidx/media2/MediaPlayerTestBase.java
+++ b/media2/src/androidTest/java/androidx/media2/MediaPlayerTestBase.java
@@ -117,7 +117,7 @@
                 .build();
 
         return mPlayer.setMediaItem(new UriMediaItem.Builder(
-                mContext, testVideoUri).build()).get().getResultCode()
+                testVideoUri).build()).get().getResultCode()
                 == androidx.media2.SessionPlayer.PlayerResult.RESULT_SUCCESS;
     }
 
diff --git a/media2/src/main/java/androidx/media2/MediaPlayer.java b/media2/src/main/java/androidx/media2/MediaPlayer.java
index f1d2383..50add35 100644
--- a/media2/src/main/java/androidx/media2/MediaPlayer.java
+++ b/media2/src/main/java/androidx/media2/MediaPlayer.java
@@ -62,6 +62,7 @@
 import java.util.HashMap;
 import java.util.Iterator;
 import java.util.List;
+import java.util.Locale;
 import java.util.Map;
 import java.util.Objects;
 import java.util.UUID;
@@ -127,6 +128,10 @@
  *     <td>This is to handle error</td></tr>
  * </table>
  * <p>
+ * If an {@link AudioAttributesCompat} is not specified by {@link #setAudioAttributes},
+ * {@link #getAudioAttributes} will return {@code null} and the default audio focus behavior will
+ * follow the {@code null} case on the table above.
+ * <p>
  * For more information about the audio focus, take a look at
  * <a href="{@docRoot}guide/topics/media-apps/audio-focus.html">Managing audio focus</a>
  * <p>
@@ -426,6 +431,13 @@
     @RestrictTo(LIBRARY_GROUP_PREFIX)
     public @interface SeekMode {}
 
+    /**
+     * The return value of {@link #getSelectedTrack} when there is no selected track for the given
+     * type.
+     * @see #getSelectedTrack(int)
+     */
+    public static final int NO_TRACK_SELECTED = Integer.MIN_VALUE;
+
     private static final int CALL_COMPLETE_PLAYLIST_BASE = -1000;
     private static final int END_OF_PLAYLIST = -1;
     private static final int NO_MEDIA_ITEM = -2;
@@ -805,7 +817,8 @@
     @Override
     @NonNull
     public ListenableFuture<PlayerResult> setPlaybackSpeed(
-            @FloatRange(from = 0, to = 1) final float playbackSpeed) {
+            @FloatRange(from = 0.0f, to = Float.MAX_VALUE, fromInclusive = false)
+            final float playbackSpeed) {
         PendingFuture<PlayerResult> pendingFuture = new PendingFuture<PlayerResult>(mExecutor) {
             @Override
             List<ResolvableFuture<PlayerResult>> onExecute() {
@@ -849,7 +862,8 @@
     }
 
     @Override
-    public @PlayerState int getPlayerState() {
+    @PlayerState
+    public int getPlayerState() {
         synchronized (mStateLock) {
             return mState;
         }
@@ -895,7 +909,8 @@
     }
 
     @Override
-    public @BuffState int getBufferingState() {
+    @BuffState
+    public int getBufferingState() {
         Integer buffState;
         synchronized (mStateLock) {
             buffState = mMediaItemToBuffState.get(mPlayer.getCurrentMediaItem());
@@ -904,6 +919,7 @@
     }
 
     @Override
+    @FloatRange(from = 0.0f, to = Float.MAX_VALUE, fromInclusive = false)
     public float getPlaybackSpeed() {
         try {
             return mPlayer.getPlaybackParams().getSpeed();
@@ -1611,7 +1627,8 @@
      * receive a notification {@link PlayerCallback#onVideoSizeChanged} when the size
      * is available.
      */
-    public @NonNull VideoSize getVideoSize() {
+    @NonNull
+    public VideoSize getVideoSize() {
         return new VideoSize(mPlayer.getVideoWidth(), mPlayer.getVideoHeight());
     }
 
@@ -1630,13 +1647,7 @@
     }
 
     /**
-     * Sets playback rate using {@link PlaybackParams}.
-     * <p>
-     * The player sets its internal PlaybackParams to the given input. This does not change the
-     * player state. For example, if this is called with the speed of 2.0f in
-     * {@link #PLAYER_STATE_PAUSED}, the player will just update internal property and stay paused.
-     * Once the client calls {@link #play()} afterwards, the player will start playback with the
-     * given speed. Calling this with zero speed is not allowed.
+     * Sets playback params using {@link PlaybackParams}.
      *
      * @param params the playback params.
      * @return a {@link ListenableFuture} which represents the pending completion of the command.
@@ -1887,8 +1898,9 @@
      * @see #selectTrack(int)
      * @see #deselectTrack(int)
      */
-    public int getSelectedTrack(int trackType) {
-        return mPlayer.getSelectedTrack(trackType);
+    public int getSelectedTrack(@TrackInfo.MediaTrackType int trackType) {
+        final int ret = mPlayer.getSelectedTrack(trackType);
+        return (ret < 0) ? NO_TRACK_SELECTED : ret;
     }
 
     /**
@@ -1974,6 +1986,29 @@
     }
 
     /**
+     * Register {@link PlayerCallback} to listen changes.
+     *
+     * @param executor a callback Executor
+     * @param callback a PlayerCallback
+     * @throws IllegalArgumentException if executor or callback is {@code null}.
+     */
+    public void registerPlayerCallback(
+            @NonNull /*@CallbackExecutor*/ Executor executor,
+            @NonNull PlayerCallback callback) {
+        super.registerPlayerCallback(executor, callback);
+    }
+
+    /**
+     * Unregister the previously registered {@link PlayerCallback}.
+     *
+     * @param callback the callback to be removed
+     * @throws IllegalArgumentException if the callback is {@code null}.
+     */
+    public void unregisterPlayerCallback(@NonNull PlayerCallback callback) {
+        super.unregisterPlayerCallback(callback);
+    }
+
+    /**
      * Retrieves the DRM Info associated with the current media item.
      *
      * @throws IllegalStateException if called before being prepared
@@ -2793,6 +2828,20 @@
         public static final int MEDIA_TRACK_TYPE_SUBTITLE = 4;
         public static final int MEDIA_TRACK_TYPE_METADATA = 5;
 
+        /**
+         * @hide
+         */
+        @IntDef(flag = false, /*prefix = "PLAYER_ERROR",*/ value = {
+                MEDIA_TRACK_TYPE_UNKNOWN,
+                MEDIA_TRACK_TYPE_VIDEO,
+                MEDIA_TRACK_TYPE_AUDIO,
+                MEDIA_TRACK_TYPE_SUBTITLE,
+                MEDIA_TRACK_TYPE_METADATA,
+        })
+        @Retention(RetentionPolicy.SOURCE)
+        @RestrictTo(LIBRARY_GROUP_PREFIX)
+        public @interface MediaTrackType {}
+
         private final int mTrackType;
         private final MediaFormat mFormat;
 
@@ -2800,20 +2849,21 @@
          * Gets the track type.
          * @return TrackType which indicates if the track is video, audio, timed text.
          */
-        public int getTrackType() {
+        public @MediaTrackType int getTrackType() {
             return mTrackType;
         }
 
         /**
          * Gets the language code of the track.
-         * @return a language code in either way of ISO-639-1 or ISO-639-2.
-         * When the language is unknown or could not be determined,
-         * ISO-639-2 language code, "und", is returned.
+         * @return {@link Locale} which includes the language information.
          */
         @NonNull
-        public String getLanguage() {
-            String language = mFormat.getString(MediaFormat.KEY_LANGUAGE);
-            return language == null ? "und" : language;
+        public Locale getLanguage() {
+            String language = mFormat != null ? mFormat.getString(MediaFormat.KEY_LANGUAGE) : null;
+            if (language == null) {
+                language = "und";
+            }
+            return new Locale(language);
         }
 
         /**
diff --git a/media2/src/main/java/androidx/media2/MediaPlayer2.java b/media2/src/main/java/androidx/media2/MediaPlayer2.java
index 243d7d1..690d3c1 100644
--- a/media2/src/main/java/androidx/media2/MediaPlayer2.java
+++ b/media2/src/main/java/androidx/media2/MediaPlayer2.java
@@ -243,7 +243,7 @@
         if (Build.VERSION.SDK_INT <= 27 || DEBUG_USE_EXOPLAYER) {
             return new ExoPlayerMediaPlayer2Impl(context);
         } else {
-            return new MediaPlayer2Impl();
+            return new MediaPlayer2Impl(context);
         }
     }
 
diff --git a/media2/src/main/java/androidx/media2/MediaPlayer2Impl.java b/media2/src/main/java/androidx/media2/MediaPlayer2Impl.java
index 8ead1fe..7816fe1 100644
--- a/media2/src/main/java/androidx/media2/MediaPlayer2Impl.java
+++ b/media2/src/main/java/androidx/media2/MediaPlayer2Impl.java
@@ -17,6 +17,7 @@
 
 import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX;
 
+import android.content.Context;
 import android.media.AudioAttributes;
 import android.media.DeniedByServerException;
 import android.media.MediaDataSource;
@@ -138,6 +139,7 @@
     MediaPlayerSourceQueue mPlayer;
 
     private HandlerThread mHandlerThread;
+    private final Context mContext;
     private final Handler mEndPositionHandler;
     private final Handler mTaskHandler;
     @SuppressWarnings("WeakerAccess") /* synthetic access */
@@ -174,7 +176,8 @@
      * to free the resources. If not released, too many MediaPlayer2Impl instances may
      * result in an exception.</p>
      */
-    public MediaPlayer2Impl() {
+    public MediaPlayer2Impl(Context context) {
+        mContext = context;
         mHandlerThread = new HandlerThread("MediaPlayer2TaskThread");
         mHandlerThread.start();
         Looper looper = mHandlerThread.getLooper();
@@ -451,7 +454,7 @@
     }
 
     @SuppressWarnings("WeakerAccess") /* synthetic access */
-    static void handleDataSource(MediaPlayerSource src)
+    void handleDataSource(MediaPlayerSource src)
             throws IOException {
         final MediaItem item = src.getDSD();
         Preconditions.checkArgument(item != null, "the MediaItem cannot be null");
@@ -488,7 +491,7 @@
         } else if (item instanceof UriMediaItem) {
             UriMediaItem uitem = (UriMediaItem) item;
             player.setDataSource(
-                    uitem.getUriContext(),
+                    mContext,
                     uitem.getUri(),
                     uitem.getUriHeaders(),
                     uitem.getUriCookies());
diff --git a/media2/src/main/java/androidx/media2/PlaybackParams.java b/media2/src/main/java/androidx/media2/PlaybackParams.java
index 2b93cd6..b3d4380 100644
--- a/media2/src/main/java/androidx/media2/PlaybackParams.java
+++ b/media2/src/main/java/androidx/media2/PlaybackParams.java
@@ -21,6 +21,7 @@
 import android.media.AudioTrack;
 import android.os.Build;
 
+import androidx.annotation.FloatRange;
 import androidx.annotation.IntDef;
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
@@ -36,8 +37,19 @@
  * Used by {@link MediaPlayer} {@link MediaPlayer#getPlaybackParams()} and
  * {@link MediaPlayer#setPlaybackParams(PlaybackParams)}
  * to control playback behavior.
- * <p> <strong>audio fallback mode:</strong>
- * select out-of-range parameter handling.
+ * <p>
+ * PlaybackParams returned by {@link MediaPlayer#getPlaybackParams()} will always have values.
+ * In case of {@link MediaPlayer#setPlaybackParams}, the player will not update the param if the
+ * value is not set. For example, if pitch is set while speed is not set, only pitch will be
+ * updated.
+ * <p>
+ * Note that the speed value does not change the player state. For example, if
+ * {@link MediaPlayer#getPlaybackParams()} is called with the speed of 2.0f in
+ * {@link MediaPlayer#PLAYER_STATE_PAUSED}, the player will just update internal property and stay
+ * paused. Once {@link MediaPlayer#play()} is called afterwards, the player will start
+ * playback with the given speed. Calling this with zero speed is not allowed.
+ * <p>
+ * <strong>audio fallback mode:</strong> select out-of-range parameter handling.
  * <ul>
  * <li> {@link PlaybackParams#AUDIO_FALLBACK_MODE_DEFAULT}:
  *   System will determine best handling. </li>
@@ -219,7 +231,8 @@
          * @return this <code>Builder</code> instance.
          * @throws IllegalArgumentException if the pitch is negative.
          */
-        public @NonNull Builder setPitch(float pitch) {
+        public @NonNull Builder setPitch(
+                @FloatRange(from = 0.0f, to = Float.MAX_VALUE) float pitch) {
             if (pitch < 0.f) {
                 throw new IllegalArgumentException("pitch must not be negative");
             }
@@ -236,7 +249,14 @@
          *
          * @return this <code>Builder</code> instance.
          */
-        public @NonNull Builder setSpeed(float speed) {
+        public @NonNull Builder setSpeed(
+                @FloatRange(from = 0.0f, to = Float.MAX_VALUE, fromInclusive = false) float speed) {
+            if (speed == 0.f) {
+                throw new IllegalArgumentException("0 speed is not allowed.");
+            }
+            if (speed < 0.f) {
+                throw new IllegalArgumentException("negative speed is not supported.");
+            }
             if (Build.VERSION.SDK_INT >= 23) {
                 mPlaybackParams.setSpeed(speed);
             } else {
diff --git a/media2/src/main/java/androidx/media2/UriMediaItem.java b/media2/src/main/java/androidx/media2/UriMediaItem.java
index a867f40..f7d55b6 100644
--- a/media2/src/main/java/androidx/media2/UriMediaItem.java
+++ b/media2/src/main/java/androidx/media2/UriMediaItem.java
@@ -16,7 +16,6 @@
 
 package androidx.media2;
 
-import android.content.Context;
 import android.net.Uri;
 
 import androidx.annotation.NonNull;
@@ -55,9 +54,6 @@
     @NonParcelField
     @SuppressWarnings("WeakerAccess") /* synthetic access */
     List<HttpCookie> mUriCookies;
-    @NonParcelField
-    @SuppressWarnings("WeakerAccess") /* synthetic access */
-    Context mUriContext;
 
     /**
      * Used for VersionedParcelable
@@ -71,7 +67,6 @@
         mUri = builder.mUri;
         mUriHeader = builder.mUriHeader;
         mUriCookies = builder.mUriCookies;
-        mUriContext = builder.mUriContext;
     }
 
     /**
@@ -105,14 +100,6 @@
     }
 
     /**
-     * Return the Context used for resolving the Uri of this media item.
-     * @return the Context used for resolving the Uri of this media item
-     */
-    public @NonNull Context getUriContext() {
-        return mUriContext;
-    }
-
-    /**
      * This Builder class simplifies the creation of a {@link UriMediaItem} object.
      */
     public static final class Builder extends MediaItem.Builder {
@@ -123,17 +110,14 @@
         Map<String, String> mUriHeader;
         @SuppressWarnings("WeakerAccess") /* synthetic access */
         List<HttpCookie> mUriCookies;
-        @SuppressWarnings("WeakerAccess") /* synthetic access */
-        Context mUriContext;
 
         /**
          * Creates a new Builder object with a content Uri.
          *
-         * @param context the Context to use when resolving the Uri
          * @param uri the Content URI of the data you want to play
          */
-        public Builder(@NonNull Context context, @NonNull Uri uri) {
-            this(context, uri, null, null);
+        public Builder(@NonNull Uri uri) {
+            this(uri, null, null);
         }
 
         /**
@@ -147,7 +131,6 @@
          * "android-allow-cross-domain-redirect" as the key and "0" or "1" as the value to
          * disallow or allow cross domain redirection.
          *
-         * @param context the Context to use when resolving the Uri
          * @param uri the Content URI of the data you want to play
          * @param headers the headers to be sent together with the request for the data
          *                The headers must not include cookies. Instead, use the cookies param.
@@ -155,12 +138,10 @@
          * @throws IllegalArgumentException if the cookie handler is not of CookieManager type
          *                                  when cookies are provided.
          */
-        public Builder(@NonNull Context context, @NonNull Uri uri,
-                @Nullable Map<String, String> headers, @Nullable List<HttpCookie> cookies) {
-            Preconditions.checkNotNull(context, "context cannot be null");
+        public Builder(@NonNull Uri uri, @Nullable Map<String, String> headers,
+                @Nullable List<HttpCookie> cookies) {
             Preconditions.checkNotNull(uri, "uri cannot be null");
             mUri = uri;
-            mUriContext = context;
             if (cookies != null) {
                 CookieHandler cookieHandler = CookieHandler.getDefault();
                 if (cookieHandler != null && !(cookieHandler instanceof CookieManager)) {
@@ -177,7 +158,6 @@
             if (cookies != null) {
                 mUriCookies = new ArrayList<HttpCookie>(cookies);
             }
-            mUriContext = context;
         }
 
         // Override just to change return type.
diff --git a/media2/src/main/java/androidx/media2/subtitle/SubtitleController.java b/media2/src/main/java/androidx/media2/subtitle/SubtitleController.java
index 7b246fa..80028ca 100644
--- a/media2/src/main/java/androidx/media2/subtitle/SubtitleController.java
+++ b/media2/src/main/java/androidx/media2/subtitle/SubtitleController.java
@@ -523,7 +523,10 @@
         }
     }
 
-    interface Listener {
+    /**
+     * Listener for when subtitle track has been selected.
+     */
+    public interface Listener {
         /**
          * Called when a subtitle track has been selected.
          *
diff --git a/media2/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/tests/MediaItemTest.java b/media2/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/tests/MediaItemTest.java
index 8a44064..b217031 100644
--- a/media2/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/tests/MediaItemTest.java
+++ b/media2/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/tests/MediaItemTest.java
@@ -83,7 +83,7 @@
         public MediaItem create(Context context) {
             final MediaMetadata testMetadata = new MediaMetadata.Builder()
                     .putString("MediaItemTest", "MediaItemTest").build();
-            return new UriMediaItem.Builder(context, Uri.parse("test://test"))
+            return new UriMediaItem.Builder(Uri.parse("test://test"))
                     .setMetadata(testMetadata)
                     .setStartPosition(1)
                     .setEndPosition(1000)
diff --git a/mediarouter/build.gradle b/mediarouter/build.gradle
index 4381eae..98b4ae4 100644
--- a/mediarouter/build.gradle
+++ b/mediarouter/build.gradle
@@ -10,7 +10,7 @@
     api(project(":media"))
     implementation("androidx.appcompat:appcompat:1.0.2")
     implementation("androidx.palette:palette:1.0.0")
-    implementation(project(":recyclerview"))
+    implementation("androidx.recyclerview:recyclerview:1.0.0")
 
     androidTestImplementation(TEST_EXT_JUNIT)
     androidTestImplementation(TEST_CORE)
diff --git a/navigation/common/api/2.1.0-alpha02.txt b/navigation/common/api/2.1.0-alpha02.txt
new file mode 100644
index 0000000..6dd3420
--- /dev/null
+++ b/navigation/common/api/2.1.0-alpha02.txt
@@ -0,0 +1,192 @@
+// Signature format: 3.0
+package androidx.navigation {
+
+  public final class ActionOnlyNavDirections implements androidx.navigation.NavDirections {
+    ctor public ActionOnlyNavDirections(int);
+    method public int getActionId();
+    method public android.os.Bundle getArguments();
+  }
+
+  public final class NavAction {
+    ctor public NavAction(@IdRes int);
+    ctor public NavAction(@IdRes int, androidx.navigation.NavOptions?);
+    ctor public NavAction(@IdRes int, androidx.navigation.NavOptions?, android.os.Bundle?);
+    method public android.os.Bundle? getDefaultArguments();
+    method public int getDestinationId();
+    method public androidx.navigation.NavOptions? getNavOptions();
+    method public void setDefaultArguments(android.os.Bundle?);
+    method public void setNavOptions(androidx.navigation.NavOptions?);
+  }
+
+  public interface NavArgs {
+  }
+
+  public final class NavArgument {
+    method public Object? getDefaultValue();
+    method public androidx.navigation.NavType<?> getType();
+    method public boolean isDefaultValuePresent();
+    method public boolean isNullable();
+  }
+
+  public static final class NavArgument.Builder {
+    ctor public NavArgument.Builder();
+    method public androidx.navigation.NavArgument build();
+    method public androidx.navigation.NavArgument.Builder setDefaultValue(Object?);
+    method public androidx.navigation.NavArgument.Builder setIsNullable(boolean);
+    method public androidx.navigation.NavArgument.Builder setType(androidx.navigation.NavType<?>);
+  }
+
+  public class NavDestination {
+    ctor public NavDestination(androidx.navigation.Navigator<? extends androidx.navigation.NavDestination>);
+    ctor public NavDestination(String);
+    method public final void addArgument(String, androidx.navigation.NavArgument);
+    method public final void addDeepLink(String);
+    method public final androidx.navigation.NavAction? getAction(@IdRes int);
+    method public final java.util.Map<java.lang.String,androidx.navigation.NavArgument> getArguments();
+    method @IdRes public final int getId();
+    method public final CharSequence? getLabel();
+    method public final String getNavigatorName();
+    method public final androidx.navigation.NavGraph? getParent();
+    method @CallSuper public void onInflate(android.content.Context, android.util.AttributeSet);
+    method protected static <C> Class<? extends C> parseClassFromName(android.content.Context, String, Class<? extends C>);
+    method public final void putAction(@IdRes int, @IdRes int);
+    method public final void putAction(@IdRes int, androidx.navigation.NavAction);
+    method public final void removeAction(@IdRes int);
+    method public final void removeArgument(String);
+    method public final void setId(@IdRes int);
+    method public final void setLabel(CharSequence?);
+  }
+
+  @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.TYPE}) public static @interface NavDestination.ClassType {
+    method public abstract Class value();
+  }
+
+  public interface NavDirections {
+    method @IdRes public int getActionId();
+    method public android.os.Bundle getArguments();
+  }
+
+  public class NavGraph extends androidx.navigation.NavDestination implements java.lang.Iterable<androidx.navigation.NavDestination> {
+    ctor public NavGraph(androidx.navigation.Navigator<? extends androidx.navigation.NavGraph>);
+    method public final void addAll(androidx.navigation.NavGraph);
+    method public final void addDestination(androidx.navigation.NavDestination);
+    method public final void addDestinations(java.util.Collection<androidx.navigation.NavDestination>);
+    method public final void addDestinations(androidx.navigation.NavDestination...);
+    method public final void clear();
+    method public final androidx.navigation.NavDestination? findNode(@IdRes int);
+    method @IdRes public final int getStartDestination();
+    method public final java.util.Iterator<androidx.navigation.NavDestination> iterator();
+    method public final void remove(androidx.navigation.NavDestination);
+    method public final void setStartDestination(@IdRes int);
+  }
+
+  @androidx.navigation.Navigator.Name("navigation") public class NavGraphNavigator extends androidx.navigation.Navigator<androidx.navigation.NavGraph> {
+    ctor public NavGraphNavigator(androidx.navigation.NavigatorProvider);
+    method public androidx.navigation.NavGraph createDestination();
+    method public androidx.navigation.NavDestination? navigate(androidx.navigation.NavGraph, android.os.Bundle?, androidx.navigation.NavOptions?, androidx.navigation.Navigator.Extras?);
+    method public boolean popBackStack();
+  }
+
+  public final class NavOptions {
+    method @AnimRes @AnimatorRes public int getEnterAnim();
+    method @AnimRes @AnimatorRes public int getExitAnim();
+    method @AnimRes @AnimatorRes public int getPopEnterAnim();
+    method @AnimRes @AnimatorRes public int getPopExitAnim();
+    method @IdRes public int getPopUpTo();
+    method public boolean isPopUpToInclusive();
+    method public boolean shouldLaunchSingleTop();
+  }
+
+  public static final class NavOptions.Builder {
+    ctor public NavOptions.Builder();
+    method public androidx.navigation.NavOptions build();
+    method public androidx.navigation.NavOptions.Builder setEnterAnim(@AnimRes @AnimatorRes int);
+    method public androidx.navigation.NavOptions.Builder setExitAnim(@AnimRes @AnimatorRes int);
+    method public androidx.navigation.NavOptions.Builder setLaunchSingleTop(boolean);
+    method public androidx.navigation.NavOptions.Builder setPopEnterAnim(@AnimRes @AnimatorRes int);
+    method public androidx.navigation.NavOptions.Builder setPopExitAnim(@AnimRes @AnimatorRes int);
+    method public androidx.navigation.NavOptions.Builder setPopUpTo(@IdRes int, boolean);
+  }
+
+  public abstract class NavType<T> {
+    method public static androidx.navigation.NavType<?> fromArgType(String?, String?);
+    method public abstract T? get(android.os.Bundle, String);
+    method public abstract String getName();
+    method public boolean isNullableAllowed();
+    method public abstract T parseValue(String);
+    method public abstract void put(android.os.Bundle, String, T?);
+    field public static final androidx.navigation.NavType<boolean[]> BoolArrayType;
+    field public static final androidx.navigation.NavType<java.lang.Boolean> BoolType;
+    field public static final androidx.navigation.NavType<float[]> FloatArrayType;
+    field public static final androidx.navigation.NavType<java.lang.Float> FloatType;
+    field public static final androidx.navigation.NavType<int[]> IntArrayType;
+    field public static final androidx.navigation.NavType<java.lang.Integer> IntType;
+    field public static final androidx.navigation.NavType<long[]> LongArrayType;
+    field public static final androidx.navigation.NavType<java.lang.Long> LongType;
+    field public static final androidx.navigation.NavType<java.lang.Integer> ReferenceType;
+    field public static final androidx.navigation.NavType<java.lang.String[]> StringArrayType;
+    field public static final androidx.navigation.NavType<java.lang.String> StringType;
+  }
+
+  public static final class NavType.EnumType<D extends java.lang.Enum> extends androidx.navigation.NavType.SerializableType<D> {
+    ctor public NavType.EnumType(Class<D>);
+  }
+
+  public static final class NavType.ParcelableArrayType<D extends android.os.Parcelable> extends androidx.navigation.NavType<D[]> {
+    ctor public NavType.ParcelableArrayType(Class<D>);
+    method public D[]? get(android.os.Bundle, String);
+    method public String getName();
+    method public D[] parseValue(String);
+    method public void put(android.os.Bundle, String, D[]?);
+  }
+
+  public static final class NavType.ParcelableType<D> extends androidx.navigation.NavType<D> {
+    ctor public NavType.ParcelableType(Class<D>);
+    method public D? get(android.os.Bundle, String);
+    method public String getName();
+    method public D parseValue(String);
+    method public void put(android.os.Bundle, String, D?);
+  }
+
+  public static final class NavType.SerializableArrayType<D extends java.io.Serializable> extends androidx.navigation.NavType<D[]> {
+    ctor public NavType.SerializableArrayType(Class<D>);
+    method public D[]? get(android.os.Bundle, String);
+    method public String getName();
+    method public D[] parseValue(String);
+    method public void put(android.os.Bundle, String, D[]?);
+  }
+
+  public static class NavType.SerializableType<D extends java.io.Serializable> extends androidx.navigation.NavType<D> {
+    ctor public NavType.SerializableType(Class<D>);
+    method public D? get(android.os.Bundle, String);
+    method public String getName();
+    method public D parseValue(String);
+    method public void put(android.os.Bundle, String, D?);
+  }
+
+  public abstract class Navigator<D extends androidx.navigation.NavDestination> {
+    ctor public Navigator();
+    method public abstract D createDestination();
+    method public abstract androidx.navigation.NavDestination? navigate(D, android.os.Bundle?, androidx.navigation.NavOptions?, androidx.navigation.Navigator.Extras?);
+    method public void onRestoreState(android.os.Bundle);
+    method public android.os.Bundle? onSaveState();
+    method public abstract boolean popBackStack();
+  }
+
+  public static interface Navigator.Extras {
+  }
+
+  @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.RUNTIME) @java.lang.annotation.Target({java.lang.annotation.ElementType.TYPE}) public static @interface Navigator.Name {
+    method public abstract String value();
+  }
+
+  public class NavigatorProvider {
+    ctor public NavigatorProvider();
+    method public final androidx.navigation.Navigator<? extends androidx.navigation.NavDestination>? addNavigator(androidx.navigation.Navigator<? extends androidx.navigation.NavDestination>);
+    method @CallSuper public androidx.navigation.Navigator<? extends androidx.navigation.NavDestination>? addNavigator(String, androidx.navigation.Navigator<? extends androidx.navigation.NavDestination>);
+    method public final <T extends androidx.navigation.Navigator<?>> T getNavigator(Class<T>);
+    method @CallSuper public <T extends androidx.navigation.Navigator<?>> T getNavigator(String);
+  }
+
+}
+
diff --git a/navigation/common/api/res-2.1.0-alpha02.txt b/navigation/common/api/res-2.1.0-alpha02.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/navigation/common/api/res-2.1.0-alpha02.txt
diff --git a/navigation/common/api/restricted_2.1.0-alpha02.txt b/navigation/common/api/restricted_2.1.0-alpha02.txt
new file mode 100644
index 0000000..37d70c2
--- /dev/null
+++ b/navigation/common/api/restricted_2.1.0-alpha02.txt
@@ -0,0 +1,17 @@
+// Signature format: 3.0
+package androidx.navigation {
+
+  public abstract class Navigator<D extends androidx.navigation.NavDestination> {
+    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public final void addOnNavigatorBackPressListener(androidx.navigation.Navigator.OnNavigatorBackPressListener);
+    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public final void dispatchOnNavigatorBackPress();
+    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) protected void onBackPressAdded();
+    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) protected void onBackPressRemoved();
+    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public final void removeOnNavigatorBackPressListener(androidx.navigation.Navigator.OnNavigatorBackPressListener);
+  }
+
+  @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public static interface Navigator.OnNavigatorBackPressListener {
+    method public void onPopBackStack(androidx.navigation.Navigator);
+  }
+
+}
+
diff --git a/navigation/common/ktx/api/2.1.0-alpha02.txt b/navigation/common/ktx/api/2.1.0-alpha02.txt
new file mode 100644
index 0000000..6da57db
--- /dev/null
+++ b/navigation/common/ktx/api/2.1.0-alpha02.txt
@@ -0,0 +1,129 @@
+// Signature format: 3.0
+package androidx.navigation {
+
+  @androidx.navigation.NavOptionsDsl public final class AnimBuilder {
+    ctor public AnimBuilder();
+    method public int getEnter();
+    method public int getExit();
+    method public int getPopEnter();
+    method public int getPopExit();
+    method public void setEnter(int p);
+    method public void setExit(int p);
+    method public void setPopEnter(int p);
+    method public void setPopExit(int p);
+    property public final int enter;
+    property public final int exit;
+    property public final int popEnter;
+    property public final int popExit;
+  }
+
+  @androidx.navigation.NavDestinationDsl public final class NavActionBuilder {
+    ctor public NavActionBuilder();
+    method public int getDestinationId();
+    method public void navOptions(kotlin.jvm.functions.Function1<? super androidx.navigation.NavOptionsBuilder,kotlin.Unit> optionsBuilder);
+    method public void setDestinationId(int p);
+    property public final int destinationId;
+  }
+
+  public final class NavArgsLazy<Args extends androidx.navigation.NavArgs> implements kotlin.Lazy<Args> {
+    ctor public NavArgsLazy(kotlin.reflect.KClass<Args> navArgsClass, kotlin.jvm.functions.Function0<android.os.Bundle> argumentProducer);
+    method public Args getValue();
+    method public boolean isInitialized();
+    property public Args value;
+  }
+
+  public final class NavArgsLazyKt {
+    ctor public NavArgsLazyKt();
+  }
+
+  @androidx.navigation.NavDestinationDsl public final class NavArgumentBuilder {
+    ctor public NavArgumentBuilder();
+    method public androidx.navigation.NavArgument build();
+    method public Object? getDefaultValue();
+    method public boolean getNullable();
+    method public androidx.navigation.NavType<?> getType();
+    method public void setDefaultValue(Object? value);
+    method public void setNullable(boolean value);
+    method public void setType(androidx.navigation.NavType<?> value);
+    property public final Object? defaultValue;
+    property public final boolean nullable;
+    property public final androidx.navigation.NavType<?> type;
+  }
+
+  @androidx.navigation.NavDestinationDsl public class NavDestinationBuilder<D extends androidx.navigation.NavDestination> {
+    ctor public NavDestinationBuilder(androidx.navigation.Navigator<? extends D> navigator, @IdRes int id);
+    method public final void action(int actionId, kotlin.jvm.functions.Function1<? super androidx.navigation.NavActionBuilder,kotlin.Unit> actionBuilder);
+    method public final void argument(String name, kotlin.jvm.functions.Function1<? super androidx.navigation.NavArgumentBuilder,kotlin.Unit> argumentBuilder);
+    method public D build();
+    method public final void deepLink(String uriPattern);
+    method public final int getId();
+    method public final CharSequence? getLabel();
+    method protected final androidx.navigation.Navigator<? extends D> getNavigator();
+    method public final void setLabel(CharSequence? p);
+    property public final CharSequence? label;
+  }
+
+  @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.RUNTIME) public @interface NavDestinationDsl {
+  }
+
+  @androidx.navigation.NavDestinationDsl public final class NavGraphBuilder extends androidx.navigation.NavDestinationBuilder<androidx.navigation.NavGraph> {
+    ctor public NavGraphBuilder(androidx.navigation.NavigatorProvider provider, @IdRes int id, @IdRes int startDestination);
+    method public void addDestination(androidx.navigation.NavDestination destination);
+    method public androidx.navigation.NavGraph build();
+    method public <D extends androidx.navigation.NavDestination> void destination(androidx.navigation.NavDestinationBuilder<? extends D> navDestination);
+    method public androidx.navigation.NavigatorProvider getProvider();
+    method public operator void unaryPlus(androidx.navigation.NavDestination);
+  }
+
+  public final class NavGraphBuilderKt {
+    ctor public NavGraphBuilderKt();
+    method public static inline androidx.navigation.NavGraph navigation(androidx.navigation.NavigatorProvider, @IdRes int id = 0, @IdRes int startDestination, kotlin.jvm.functions.Function1<? super androidx.navigation.NavGraphBuilder,kotlin.Unit> builder);
+    method public static inline void navigation(androidx.navigation.NavGraphBuilder, @IdRes int id, @IdRes int startDestination, kotlin.jvm.functions.Function1<? super androidx.navigation.NavGraphBuilder,kotlin.Unit> builder);
+  }
+
+  public final class NavGraphKt {
+    ctor public NavGraphKt();
+    method public static operator boolean contains(androidx.navigation.NavGraph, @IdRes int id);
+    method public static inline operator androidx.navigation.NavDestination get(androidx.navigation.NavGraph, @IdRes int id);
+    method public static inline operator void minusAssign(androidx.navigation.NavGraph, androidx.navigation.NavDestination node);
+    method public static inline operator void plusAssign(androidx.navigation.NavGraph, androidx.navigation.NavDestination node);
+    method public static inline operator void plusAssign(androidx.navigation.NavGraph, androidx.navigation.NavGraph other);
+  }
+
+  @androidx.navigation.NavOptionsDsl public final class NavOptionsBuilder {
+    ctor public NavOptionsBuilder();
+    method public void anim(kotlin.jvm.functions.Function1<? super androidx.navigation.AnimBuilder,kotlin.Unit> animBuilder);
+    method public boolean getLaunchSingleTop();
+    method public int getPopUpTo();
+    method public void popUpTo(@IdRes int id, kotlin.jvm.functions.Function1<? super androidx.navigation.PopUpToBuilder,kotlin.Unit> popUpToBuilder);
+    method public void setLaunchSingleTop(boolean p);
+    method public void setPopUpTo(int value);
+    property public final boolean launchSingleTop;
+    property public final int popUpTo;
+  }
+
+  public final class NavOptionsBuilderKt {
+    ctor public NavOptionsBuilderKt();
+    method public static androidx.navigation.NavOptions navOptions(kotlin.jvm.functions.Function1<? super androidx.navigation.NavOptionsBuilder,kotlin.Unit> optionsBuilder);
+  }
+
+  @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.RUNTIME) public @interface NavOptionsDsl {
+  }
+
+  public final class NavigatorProviderKt {
+    ctor public NavigatorProviderKt();
+    method public static inline operator <T extends androidx.navigation.Navigator<? extends androidx.navigation.NavDestination>> T get(androidx.navigation.NavigatorProvider, String name);
+    method public static inline operator <T extends androidx.navigation.Navigator<? extends androidx.navigation.NavDestination>> T get(androidx.navigation.NavigatorProvider, kotlin.reflect.KClass<T> clazz);
+    method public static inline operator void plusAssign(androidx.navigation.NavigatorProvider, androidx.navigation.Navigator<? extends androidx.navigation.NavDestination> navigator);
+    method public static inline operator androidx.navigation.Navigator<? extends androidx.navigation.NavDestination>? set(androidx.navigation.NavigatorProvider, String name, androidx.navigation.Navigator<? extends androidx.navigation.NavDestination> navigator);
+  }
+
+  @androidx.navigation.NavOptionsDsl public final class PopUpToBuilder {
+    ctor public PopUpToBuilder();
+    method public boolean getInclusive();
+    method public void setInclusive(boolean p);
+    property public final boolean inclusive;
+  }
+
+}
+
diff --git a/navigation/common/ktx/api/res-2.1.0-alpha02.txt b/navigation/common/ktx/api/res-2.1.0-alpha02.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/navigation/common/ktx/api/res-2.1.0-alpha02.txt
diff --git a/navigation/common/ktx/api/restricted_2.1.0-alpha02.txt b/navigation/common/ktx/api/restricted_2.1.0-alpha02.txt
new file mode 100644
index 0000000..da4f6cc
--- /dev/null
+++ b/navigation/common/ktx/api/restricted_2.1.0-alpha02.txt
@@ -0,0 +1 @@
+// Signature format: 3.0
diff --git a/navigation/fragment/api/2.1.0-alpha02.txt b/navigation/fragment/api/2.1.0-alpha02.txt
new file mode 100644
index 0000000..8cd8b69
--- /dev/null
+++ b/navigation/fragment/api/2.1.0-alpha02.txt
@@ -0,0 +1,40 @@
+// Signature format: 3.0
+package androidx.navigation.fragment {
+
+  @androidx.navigation.Navigator.Name("fragment") public class FragmentNavigator extends androidx.navigation.Navigator<androidx.navigation.fragment.FragmentNavigator.Destination> {
+    ctor public FragmentNavigator(android.content.Context, androidx.fragment.app.FragmentManager, int);
+    method public androidx.navigation.fragment.FragmentNavigator.Destination createDestination();
+    method @Deprecated public androidx.fragment.app.Fragment instantiateFragment(android.content.Context, androidx.fragment.app.FragmentManager, String, android.os.Bundle?);
+    method public androidx.navigation.NavDestination? navigate(androidx.navigation.fragment.FragmentNavigator.Destination, android.os.Bundle?, androidx.navigation.NavOptions?, androidx.navigation.Navigator.Extras?);
+    method public boolean popBackStack();
+  }
+
+  @androidx.navigation.NavDestination.ClassType(Fragment.class) public static class FragmentNavigator.Destination extends androidx.navigation.NavDestination {
+    ctor public FragmentNavigator.Destination(androidx.navigation.NavigatorProvider);
+    ctor public FragmentNavigator.Destination(androidx.navigation.Navigator<? extends androidx.navigation.fragment.FragmentNavigator.Destination>);
+    method public final String getClassName();
+    method public final androidx.navigation.fragment.FragmentNavigator.Destination setClassName(String);
+  }
+
+  public static final class FragmentNavigator.Extras implements androidx.navigation.Navigator.Extras {
+    method public java.util.Map<android.view.View,java.lang.String> getSharedElements();
+  }
+
+  public static final class FragmentNavigator.Extras.Builder {
+    ctor public FragmentNavigator.Extras.Builder();
+    method public androidx.navigation.fragment.FragmentNavigator.Extras.Builder addSharedElement(android.view.View, String);
+    method public androidx.navigation.fragment.FragmentNavigator.Extras.Builder addSharedElements(java.util.Map<android.view.View,java.lang.String>);
+    method public androidx.navigation.fragment.FragmentNavigator.Extras build();
+  }
+
+  public class NavHostFragment extends androidx.fragment.app.Fragment implements androidx.navigation.NavHost {
+    ctor public NavHostFragment();
+    method public static androidx.navigation.fragment.NavHostFragment create(@NavigationRes int);
+    method public static androidx.navigation.fragment.NavHostFragment create(@NavigationRes int, android.os.Bundle?);
+    method protected androidx.navigation.Navigator<? extends androidx.navigation.fragment.FragmentNavigator.Destination> createFragmentNavigator();
+    method public static androidx.navigation.NavController findNavController(androidx.fragment.app.Fragment);
+    method public final androidx.navigation.NavController getNavController();
+  }
+
+}
+
diff --git a/navigation/fragment/api/res-2.1.0-alpha02.txt b/navigation/fragment/api/res-2.1.0-alpha02.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/navigation/fragment/api/res-2.1.0-alpha02.txt
diff --git a/navigation/fragment/api/restricted_2.1.0-alpha02.txt b/navigation/fragment/api/restricted_2.1.0-alpha02.txt
new file mode 100644
index 0000000..da4f6cc
--- /dev/null
+++ b/navigation/fragment/api/restricted_2.1.0-alpha02.txt
@@ -0,0 +1 @@
+// Signature format: 3.0
diff --git a/navigation/fragment/build.gradle b/navigation/fragment/build.gradle
index fdcf9f7..6e279e7 100644
--- a/navigation/fragment/build.gradle
+++ b/navigation/fragment/build.gradle
@@ -31,7 +31,7 @@
 }
 
 dependencies {
-    api("androidx.fragment:fragment:1.1.0-alpha05")
+    api(project(":fragment"))
     api(project(":navigation:navigation-runtime"))
 
     testImplementation(JUNIT)
diff --git a/navigation/fragment/ktx/api/2.1.0-alpha02.txt b/navigation/fragment/ktx/api/2.1.0-alpha02.txt
new file mode 100644
index 0000000..b8b9aba
--- /dev/null
+++ b/navigation/fragment/ktx/api/2.1.0-alpha02.txt
@@ -0,0 +1,31 @@
+// Signature format: 3.0
+package androidx.navigation.fragment {
+
+  public final class FragmentKt {
+    ctor public FragmentKt();
+    method public static androidx.navigation.NavController findNavController(androidx.fragment.app.Fragment);
+  }
+
+  public final class FragmentNavArgsLazyKt {
+    ctor public FragmentNavArgsLazyKt();
+    method @MainThread public static inline <reified Args extends androidx.navigation.NavArgs> androidx.navigation.NavArgsLazy<Args>! navArgs(androidx.fragment.app.Fragment);
+  }
+
+  public final class FragmentNavigatorDestinationBuilder extends androidx.navigation.NavDestinationBuilder<androidx.navigation.fragment.FragmentNavigator.Destination> {
+    ctor public FragmentNavigatorDestinationBuilder(androidx.navigation.fragment.FragmentNavigator navigator, @IdRes int id, kotlin.reflect.KClass<? extends androidx.fragment.app.Fragment> fragmentClass);
+    method public androidx.navigation.fragment.FragmentNavigator.Destination build();
+  }
+
+  public final class FragmentNavigatorDestinationBuilderKt {
+    ctor public FragmentNavigatorDestinationBuilderKt();
+    method public static inline <reified F extends androidx.fragment.app.Fragment> void fragment(androidx.navigation.NavGraphBuilder, @IdRes int id);
+    method public static inline <reified F extends androidx.fragment.app.Fragment> void fragment(androidx.navigation.NavGraphBuilder, @IdRes int id, kotlin.jvm.functions.Function1<? super androidx.navigation.fragment.FragmentNavigatorDestinationBuilder,kotlin.Unit>! builder);
+  }
+
+  public final class FragmentNavigatorExtrasKt {
+    ctor public FragmentNavigatorExtrasKt();
+    method public static androidx.navigation.fragment.FragmentNavigator.Extras FragmentNavigatorExtras(kotlin.Pair<? extends android.view.View,java.lang.String>... sharedElements);
+  }
+
+}
+
diff --git a/navigation/fragment/ktx/api/res-2.1.0-alpha02.txt b/navigation/fragment/ktx/api/res-2.1.0-alpha02.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/navigation/fragment/ktx/api/res-2.1.0-alpha02.txt
diff --git a/navigation/fragment/ktx/api/restricted_2.1.0-alpha02.txt b/navigation/fragment/ktx/api/restricted_2.1.0-alpha02.txt
new file mode 100644
index 0000000..da4f6cc
--- /dev/null
+++ b/navigation/fragment/ktx/api/restricted_2.1.0-alpha02.txt
@@ -0,0 +1 @@
+// Signature format: 3.0
diff --git a/navigation/runtime/api/2.1.0-alpha02.txt b/navigation/runtime/api/2.1.0-alpha02.txt
new file mode 100644
index 0000000..f34f225
--- /dev/null
+++ b/navigation/runtime/api/2.1.0-alpha02.txt
@@ -0,0 +1,103 @@
+// Signature format: 3.0
+package androidx.navigation {
+
+  @androidx.navigation.Navigator.Name("activity") public class ActivityNavigator extends androidx.navigation.Navigator<androidx.navigation.ActivityNavigator.Destination> {
+    ctor public ActivityNavigator(android.content.Context);
+    method public static void applyPopAnimationsToPendingTransition(android.app.Activity);
+    method public androidx.navigation.ActivityNavigator.Destination createDestination();
+    method public androidx.navigation.NavDestination? navigate(androidx.navigation.ActivityNavigator.Destination, android.os.Bundle?, androidx.navigation.NavOptions?, androidx.navigation.Navigator.Extras?);
+    method public boolean popBackStack();
+  }
+
+  @androidx.navigation.NavDestination.ClassType(Activity.class) public static class ActivityNavigator.Destination extends androidx.navigation.NavDestination {
+    ctor public ActivityNavigator.Destination(androidx.navigation.NavigatorProvider);
+    ctor public ActivityNavigator.Destination(androidx.navigation.Navigator<? extends androidx.navigation.ActivityNavigator.Destination>);
+    method public final String? getAction();
+    method public final android.content.ComponentName? getComponent();
+    method public final android.net.Uri? getData();
+    method public final String? getDataPattern();
+    method public final android.content.Intent? getIntent();
+    method public final androidx.navigation.ActivityNavigator.Destination setAction(String?);
+    method public final androidx.navigation.ActivityNavigator.Destination setComponentName(android.content.ComponentName?);
+    method public final androidx.navigation.ActivityNavigator.Destination setData(android.net.Uri?);
+    method public final androidx.navigation.ActivityNavigator.Destination setDataPattern(String?);
+    method public final androidx.navigation.ActivityNavigator.Destination setIntent(android.content.Intent?);
+  }
+
+  public static final class ActivityNavigator.Extras implements androidx.navigation.Navigator.Extras {
+    method public androidx.core.app.ActivityOptionsCompat? getActivityOptions();
+    method public int getFlags();
+  }
+
+  public static final class ActivityNavigator.Extras.Builder {
+    ctor public ActivityNavigator.Extras.Builder();
+    method public androidx.navigation.ActivityNavigator.Extras.Builder addFlags(int);
+    method public androidx.navigation.ActivityNavigator.Extras build();
+    method public androidx.navigation.ActivityNavigator.Extras.Builder setActivityOptions(androidx.core.app.ActivityOptionsCompat);
+  }
+
+  public class NavController {
+    ctor public NavController(android.content.Context);
+    method public void addOnDestinationChangedListener(androidx.navigation.NavController.OnDestinationChangedListener);
+    method public androidx.navigation.NavDeepLinkBuilder createDeepLink();
+    method public androidx.navigation.NavDestination? getCurrentDestination();
+    method public androidx.navigation.NavGraph getGraph();
+    method public androidx.navigation.NavInflater getNavInflater();
+    method public androidx.navigation.NavigatorProvider getNavigatorProvider();
+    method public boolean handleDeepLink(android.content.Intent?);
+    method public void navigate(@IdRes int);
+    method public void navigate(@IdRes int, android.os.Bundle?);
+    method public void navigate(@IdRes int, android.os.Bundle?, androidx.navigation.NavOptions?);
+    method public void navigate(@IdRes int, android.os.Bundle?, androidx.navigation.NavOptions?, androidx.navigation.Navigator.Extras?);
+    method public void navigate(androidx.navigation.NavDirections);
+    method public void navigate(androidx.navigation.NavDirections, androidx.navigation.NavOptions?);
+    method public void navigate(androidx.navigation.NavDirections, androidx.navigation.Navigator.Extras);
+    method public boolean navigateUp();
+    method public boolean popBackStack();
+    method public boolean popBackStack(@IdRes int, boolean);
+    method public void removeOnDestinationChangedListener(androidx.navigation.NavController.OnDestinationChangedListener);
+    method @CallSuper public void restoreState(android.os.Bundle?);
+    method @CallSuper public android.os.Bundle? saveState();
+    method @CallSuper public void setGraph(@NavigationRes int);
+    method @CallSuper public void setGraph(@NavigationRes int, android.os.Bundle?);
+    method @CallSuper public void setGraph(androidx.navigation.NavGraph);
+    method @CallSuper public void setGraph(androidx.navigation.NavGraph, android.os.Bundle?);
+    field public static final String KEY_DEEP_LINK_INTENT = "android-support-nav:controller:deepLinkIntent";
+  }
+
+  public static interface NavController.OnDestinationChangedListener {
+    method public void onDestinationChanged(androidx.navigation.NavController, androidx.navigation.NavDestination, android.os.Bundle?);
+  }
+
+  public final class NavDeepLinkBuilder {
+    ctor public NavDeepLinkBuilder(android.content.Context);
+    method public android.app.PendingIntent createPendingIntent();
+    method public androidx.core.app.TaskStackBuilder createTaskStackBuilder();
+    method public androidx.navigation.NavDeepLinkBuilder setArguments(android.os.Bundle?);
+    method public androidx.navigation.NavDeepLinkBuilder setComponentName(Class<? extends android.app.Activity>);
+    method public androidx.navigation.NavDeepLinkBuilder setComponentName(android.content.ComponentName);
+    method public androidx.navigation.NavDeepLinkBuilder setDestination(@IdRes int);
+    method public androidx.navigation.NavDeepLinkBuilder setGraph(@NavigationRes int);
+    method public androidx.navigation.NavDeepLinkBuilder setGraph(androidx.navigation.NavGraph);
+  }
+
+  public interface NavHost {
+    method public androidx.navigation.NavController getNavController();
+  }
+
+  public final class NavInflater {
+    ctor public NavInflater(android.content.Context, androidx.navigation.NavigatorProvider);
+    method public androidx.navigation.NavGraph inflate(@NavigationRes int);
+  }
+
+  public final class Navigation {
+    method public static android.view.View.OnClickListener createNavigateOnClickListener(@IdRes int);
+    method public static android.view.View.OnClickListener createNavigateOnClickListener(@IdRes int, android.os.Bundle?);
+    method public static android.view.View.OnClickListener createNavigateOnClickListener(androidx.navigation.NavDirections);
+    method public static androidx.navigation.NavController findNavController(android.app.Activity, @IdRes int);
+    method public static androidx.navigation.NavController findNavController(android.view.View);
+    method public static void setViewNavController(android.view.View, androidx.navigation.NavController?);
+  }
+
+}
+
diff --git a/navigation/runtime/api/res-2.1.0-alpha02.txt b/navigation/runtime/api/res-2.1.0-alpha02.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/navigation/runtime/api/res-2.1.0-alpha02.txt
diff --git a/navigation/runtime/api/restricted_2.1.0-alpha02.txt b/navigation/runtime/api/restricted_2.1.0-alpha02.txt
new file mode 100644
index 0000000..da4f6cc
--- /dev/null
+++ b/navigation/runtime/api/restricted_2.1.0-alpha02.txt
@@ -0,0 +1 @@
+// Signature format: 3.0
diff --git a/navigation/runtime/ktx/api/2.1.0-alpha02.txt b/navigation/runtime/ktx/api/2.1.0-alpha02.txt
new file mode 100644
index 0000000..2967c7a
--- /dev/null
+++ b/navigation/runtime/ktx/api/2.1.0-alpha02.txt
@@ -0,0 +1,57 @@
+// Signature format: 3.0
+package androidx.navigation {
+
+  public final class ActivityKt {
+    ctor public ActivityKt();
+    method public static androidx.navigation.NavController findNavController(android.app.Activity, @IdRes int viewId);
+  }
+
+  public final class ActivityNavArgsLazyKt {
+    ctor public ActivityNavArgsLazyKt();
+    method @MainThread public static inline <reified Args extends androidx.navigation.NavArgs> androidx.navigation.NavArgsLazy<Args>! navArgs(android.app.Activity);
+  }
+
+  public final class ActivityNavigatorDestinationBuilder extends androidx.navigation.NavDestinationBuilder<androidx.navigation.ActivityNavigator.Destination> {
+    ctor public ActivityNavigatorDestinationBuilder(androidx.navigation.ActivityNavigator navigator, @IdRes int id);
+    method public androidx.navigation.ActivityNavigator.Destination build();
+    method public String? getAction();
+    method public kotlin.reflect.KClass<? extends android.app.Activity>? getActivityClass();
+    method public android.net.Uri? getData();
+    method public String? getDataPattern();
+    method public void setAction(String? p);
+    method public void setActivityClass(kotlin.reflect.KClass<? extends android.app.Activity>? p);
+    method public void setData(android.net.Uri? p);
+    method public void setDataPattern(String? p);
+    property public final String? action;
+    property public final kotlin.reflect.KClass<? extends android.app.Activity>? activityClass;
+    property public final android.net.Uri? data;
+    property public final String? dataPattern;
+  }
+
+  public final class ActivityNavigatorDestinationBuilderKt {
+    ctor public ActivityNavigatorDestinationBuilderKt();
+    method public static inline void activity(androidx.navigation.NavGraphBuilder, @IdRes int id, kotlin.jvm.functions.Function1<? super androidx.navigation.ActivityNavigatorDestinationBuilder,kotlin.Unit> builder);
+  }
+
+  public final class ActivityNavigatorExtrasKt {
+    ctor public ActivityNavigatorExtrasKt();
+    method public static androidx.navigation.ActivityNavigator.Extras ActivityNavigatorExtras(androidx.core.app.ActivityOptionsCompat? activityOptions = null, int flags = 0);
+  }
+
+  public final class NavControllerKt {
+    ctor public NavControllerKt();
+    method public static inline androidx.navigation.NavGraph createGraph(androidx.navigation.NavController, @IdRes int id = 0, @IdRes int startDestination, kotlin.jvm.functions.Function1<? super androidx.navigation.NavGraphBuilder,kotlin.Unit> builder);
+  }
+
+  public final class NavHostKt {
+    ctor public NavHostKt();
+    method public static inline androidx.navigation.NavGraph createGraph(androidx.navigation.NavHost, @IdRes int id = 0, @IdRes int startDestination, kotlin.jvm.functions.Function1<? super androidx.navigation.NavGraphBuilder,kotlin.Unit> builder);
+  }
+
+  public final class ViewKt {
+    ctor public ViewKt();
+    method public static androidx.navigation.NavController findNavController(android.view.View);
+  }
+
+}
+
diff --git a/navigation/runtime/ktx/api/res-2.1.0-alpha02.txt b/navigation/runtime/ktx/api/res-2.1.0-alpha02.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/navigation/runtime/ktx/api/res-2.1.0-alpha02.txt
diff --git a/navigation/runtime/ktx/api/restricted_2.1.0-alpha02.txt b/navigation/runtime/ktx/api/restricted_2.1.0-alpha02.txt
new file mode 100644
index 0000000..da4f6cc
--- /dev/null
+++ b/navigation/runtime/ktx/api/restricted_2.1.0-alpha02.txt
@@ -0,0 +1 @@
+// Signature format: 3.0
diff --git a/navigation/ui/api/2.1.0-alpha02.txt b/navigation/ui/api/2.1.0-alpha02.txt
new file mode 100644
index 0000000..629226a
--- /dev/null
+++ b/navigation/ui/api/2.1.0-alpha02.txt
@@ -0,0 +1,42 @@
+// Signature format: 3.0
+package androidx.navigation.ui {
+
+  public final class AppBarConfiguration {
+    method public androidx.drawerlayout.widget.DrawerLayout? getDrawerLayout();
+    method public androidx.navigation.ui.AppBarConfiguration.OnNavigateUpListener? getFallbackOnNavigateUpListener();
+    method public java.util.Set<java.lang.Integer> getTopLevelDestinations();
+  }
+
+  public static final class AppBarConfiguration.Builder {
+    ctor public AppBarConfiguration.Builder(androidx.navigation.NavGraph);
+    ctor public AppBarConfiguration.Builder(android.view.Menu);
+    ctor public AppBarConfiguration.Builder(int...);
+    ctor public AppBarConfiguration.Builder(java.util.Set<java.lang.Integer>);
+    method public androidx.navigation.ui.AppBarConfiguration build();
+    method public androidx.navigation.ui.AppBarConfiguration.Builder setDrawerLayout(androidx.drawerlayout.widget.DrawerLayout?);
+    method public androidx.navigation.ui.AppBarConfiguration.Builder setFallbackOnNavigateUpListener(androidx.navigation.ui.AppBarConfiguration.OnNavigateUpListener?);
+  }
+
+  public static interface AppBarConfiguration.OnNavigateUpListener {
+    method public boolean onNavigateUp();
+  }
+
+  public final class NavigationUI {
+    method public static boolean navigateUp(androidx.navigation.NavController, androidx.drawerlayout.widget.DrawerLayout?);
+    method public static boolean navigateUp(androidx.navigation.NavController, androidx.navigation.ui.AppBarConfiguration);
+    method public static boolean onNavDestinationSelected(android.view.MenuItem, androidx.navigation.NavController);
+    method public static void setupActionBarWithNavController(androidx.appcompat.app.AppCompatActivity, androidx.navigation.NavController);
+    method public static void setupActionBarWithNavController(androidx.appcompat.app.AppCompatActivity, androidx.navigation.NavController, androidx.drawerlayout.widget.DrawerLayout?);
+    method public static void setupActionBarWithNavController(androidx.appcompat.app.AppCompatActivity, androidx.navigation.NavController, androidx.navigation.ui.AppBarConfiguration);
+    method public static void setupWithNavController(androidx.appcompat.widget.Toolbar, androidx.navigation.NavController);
+    method public static void setupWithNavController(androidx.appcompat.widget.Toolbar, androidx.navigation.NavController, androidx.drawerlayout.widget.DrawerLayout?);
+    method public static void setupWithNavController(androidx.appcompat.widget.Toolbar, androidx.navigation.NavController, androidx.navigation.ui.AppBarConfiguration);
+    method public static void setupWithNavController(com.google.android.material.appbar.CollapsingToolbarLayout, androidx.appcompat.widget.Toolbar, androidx.navigation.NavController);
+    method public static void setupWithNavController(com.google.android.material.appbar.CollapsingToolbarLayout, androidx.appcompat.widget.Toolbar, androidx.navigation.NavController, androidx.drawerlayout.widget.DrawerLayout?);
+    method public static void setupWithNavController(com.google.android.material.appbar.CollapsingToolbarLayout, androidx.appcompat.widget.Toolbar, androidx.navigation.NavController, androidx.navigation.ui.AppBarConfiguration);
+    method public static void setupWithNavController(com.google.android.material.navigation.NavigationView, androidx.navigation.NavController);
+    method public static void setupWithNavController(com.google.android.material.bottomnavigation.BottomNavigationView, androidx.navigation.NavController);
+  }
+
+}
+
diff --git a/navigation/ui/api/res-2.1.0-alpha02.txt b/navigation/ui/api/res-2.1.0-alpha02.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/navigation/ui/api/res-2.1.0-alpha02.txt
diff --git a/navigation/ui/api/restricted_2.1.0-alpha02.txt b/navigation/ui/api/restricted_2.1.0-alpha02.txt
new file mode 100644
index 0000000..da4f6cc
--- /dev/null
+++ b/navigation/ui/api/restricted_2.1.0-alpha02.txt
@@ -0,0 +1 @@
+// Signature format: 3.0
diff --git a/navigation/ui/ktx/api/2.1.0-alpha02.txt b/navigation/ui/ktx/api/2.1.0-alpha02.txt
new file mode 100644
index 0000000..d20eb8f
--- /dev/null
+++ b/navigation/ui/ktx/api/2.1.0-alpha02.txt
@@ -0,0 +1,51 @@
+// Signature format: 3.0
+package androidx.navigation.ui {
+
+  public final class ActivityKt {
+    ctor public ActivityKt();
+    method public static void setupActionBarWithNavController(androidx.appcompat.app.AppCompatActivity, androidx.navigation.NavController navController, androidx.drawerlayout.widget.DrawerLayout? drawerLayout);
+    method public static void setupActionBarWithNavController(androidx.appcompat.app.AppCompatActivity, androidx.navigation.NavController navController, androidx.navigation.ui.AppBarConfiguration configuration = AppBarConfiguration(navController.graph));
+  }
+
+  public final class AppBarConfigurationKt {
+    ctor public AppBarConfigurationKt();
+    method public static inline androidx.navigation.ui.AppBarConfiguration AppBarConfiguration(androidx.navigation.NavGraph navGraph, androidx.drawerlayout.widget.DrawerLayout? drawerLayout = null, kotlin.jvm.functions.Function0<java.lang.Boolean> fallbackOnNavigateUpListener = { false });
+    method public static inline androidx.navigation.ui.AppBarConfiguration AppBarConfiguration(android.view.Menu topLevelMenu, androidx.drawerlayout.widget.DrawerLayout? drawerLayout = null, kotlin.jvm.functions.Function0<java.lang.Boolean> fallbackOnNavigateUpListener = { false });
+    method public static inline androidx.navigation.ui.AppBarConfiguration AppBarConfiguration(java.util.Set<java.lang.Integer> topLevelDestinationIds, androidx.drawerlayout.widget.DrawerLayout? drawerLayout = null, kotlin.jvm.functions.Function0<java.lang.Boolean> fallbackOnNavigateUpListener = { false });
+  }
+
+  public final class BottomNavigationViewKt {
+    ctor public BottomNavigationViewKt();
+    method public static void setupWithNavController(com.google.android.material.bottomnavigation.BottomNavigationView, androidx.navigation.NavController navController);
+  }
+
+  public final class CollapsingToolbarLayoutKt {
+    ctor public CollapsingToolbarLayoutKt();
+    method public static void setupWithNavController(com.google.android.material.appbar.CollapsingToolbarLayout, androidx.appcompat.widget.Toolbar toolbar, androidx.navigation.NavController navController, androidx.drawerlayout.widget.DrawerLayout? drawerLayout);
+    method public static void setupWithNavController(com.google.android.material.appbar.CollapsingToolbarLayout, androidx.appcompat.widget.Toolbar toolbar, androidx.navigation.NavController navController, androidx.navigation.ui.AppBarConfiguration configuration = AppBarConfiguration(navController.graph));
+  }
+
+  public final class MenuItemKt {
+    ctor public MenuItemKt();
+    method public static boolean onNavDestinationSelected(android.view.MenuItem, androidx.navigation.NavController navController);
+  }
+
+  public final class NavControllerKt {
+    ctor public NavControllerKt();
+    method public static boolean navigateUp(androidx.navigation.NavController, androidx.drawerlayout.widget.DrawerLayout? drawerLayout);
+    method public static boolean navigateUp(androidx.navigation.NavController, androidx.navigation.ui.AppBarConfiguration appBarConfiguration);
+  }
+
+  public final class NavigationViewKt {
+    ctor public NavigationViewKt();
+    method public static void setupWithNavController(com.google.android.material.navigation.NavigationView, androidx.navigation.NavController navController);
+  }
+
+  public final class ToolbarKt {
+    ctor public ToolbarKt();
+    method public static void setupWithNavController(androidx.appcompat.widget.Toolbar, androidx.navigation.NavController navController, androidx.drawerlayout.widget.DrawerLayout? drawerLayout);
+    method public static void setupWithNavController(androidx.appcompat.widget.Toolbar, androidx.navigation.NavController navController, androidx.navigation.ui.AppBarConfiguration configuration = AppBarConfiguration(navController.graph));
+  }
+
+}
+
diff --git a/navigation/ui/ktx/api/res-2.1.0-alpha02.txt b/navigation/ui/ktx/api/res-2.1.0-alpha02.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/navigation/ui/ktx/api/res-2.1.0-alpha02.txt
diff --git a/navigation/ui/ktx/api/restricted_2.1.0-alpha02.txt b/navigation/ui/ktx/api/restricted_2.1.0-alpha02.txt
new file mode 100644
index 0000000..da4f6cc
--- /dev/null
+++ b/navigation/ui/ktx/api/restricted_2.1.0-alpha02.txt
@@ -0,0 +1 @@
+// Signature format: 3.0
diff --git a/preference/src/main/java/androidx/preference/PreferenceFragmentCompat.java b/preference/src/main/java/androidx/preference/PreferenceFragmentCompat.java
index 26af8f8..2efc8f3 100644
--- a/preference/src/main/java/androidx/preference/PreferenceFragmentCompat.java
+++ b/preference/src/main/java/androidx/preference/PreferenceFragmentCompat.java
@@ -604,8 +604,11 @@
         } else if (preference instanceof MultiSelectListPreference) {
             f = MultiSelectListPreferenceDialogFragmentCompat.newInstance(preference.getKey());
         } else {
-            throw new IllegalArgumentException("Tried to display dialog for unknown " +
-                    "preference type. Did you forget to override onDisplayPreferenceDialog()?");
+            throw new IllegalArgumentException(
+                    "Cannot display dialog for an unknown Preference type: "
+                            + preference.getClass().getSimpleName()
+                            + ". Make sure to implement onPreferenceDisplayDialog() to handle "
+                            + "displaying a custom dialog for this Preference.");
         }
         f.setTargetFragment(this, 0);
         f.show(getFragmentManager(), DIALOG_FRAGMENT_TAG);
diff --git a/recyclerview/recyclerview/src/main/java/androidx/recyclerview/widget/LinearLayoutManager.java b/recyclerview/recyclerview/src/main/java/androidx/recyclerview/widget/LinearLayoutManager.java
index f8e522e..9163de3 100644
--- a/recyclerview/recyclerview/src/main/java/androidx/recyclerview/widget/LinearLayoutManager.java
+++ b/recyclerview/recyclerview/src/main/java/androidx/recyclerview/widget/LinearLayoutManager.java
@@ -1314,20 +1314,20 @@
 
         ensureLayoutState();
         final int layoutDirection = delta > 0 ? LayoutState.LAYOUT_END : LayoutState.LAYOUT_START;
-        final int absDy = Math.abs(delta);
-        updateLayoutState(layoutDirection, absDy, true, state);
+        final int absDelta = Math.abs(delta);
+        updateLayoutState(layoutDirection, absDelta, true, state);
         collectPrefetchPositionsForLayoutState(state, mLayoutState, layoutPrefetchRegistry);
     }
 
-    int scrollBy(int dy, RecyclerView.Recycler recycler, RecyclerView.State state) {
-        if (getChildCount() == 0 || dy == 0) {
+    int scrollBy(int delta, RecyclerView.Recycler recycler, RecyclerView.State state) {
+        if (getChildCount() == 0 || delta == 0) {
             return 0;
         }
         ensureLayoutState();
         mLayoutState.mRecycle = true;
-        final int layoutDirection = dy > 0 ? LayoutState.LAYOUT_END : LayoutState.LAYOUT_START;
-        final int absDy = Math.abs(dy);
-        updateLayoutState(layoutDirection, absDy, true, state);
+        final int layoutDirection = delta > 0 ? LayoutState.LAYOUT_END : LayoutState.LAYOUT_START;
+        final int absDelta = Math.abs(delta);
+        updateLayoutState(layoutDirection, absDelta, true, state);
         final int consumed = mLayoutState.mScrollingOffset
                 + fill(recycler, mLayoutState, state, false);
         if (consumed < 0) {
@@ -1336,10 +1336,10 @@
             }
             return 0;
         }
-        final int scrolled = absDy > consumed ? layoutDirection * consumed : dy;
+        final int scrolled = absDelta > consumed ? layoutDirection * consumed : delta;
         mOrientationHelper.offsetChildren(-scrolled);
         if (DEBUG) {
-            Log.d(TAG, "scroll req: " + dy + " scrolled: " + scrolled);
+            Log.d(TAG, "scroll req: " + delta + " scrolled: " + scrolled);
         }
         mLayoutState.mLastScrollDelta = scrolled;
         return scrolled;
@@ -1979,7 +1979,6 @@
             return null;
         }
         ensureLayoutState();
-        ensureLayoutState();
         final int maxScroll = (int) (MAX_SCROLL_FACTOR * mOrientationHelper.getTotalSpace());
         updateLayoutState(layoutDir, maxScroll, false, state);
         mLayoutState.mScrollingOffset = LayoutState.SCROLLING_OFFSET_NaN;
diff --git a/room/common-java8/build.gradle b/room/common-java8/build.gradle
new file mode 100644
index 0000000..4bd7f60
--- /dev/null
+++ b/room/common-java8/build.gradle
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import static androidx.build.dependencies.DependenciesKt.*
+import androidx.build.LibraryGroups
+import androidx.build.LibraryVersions
+import androidx.build.SupportLibraryExtension;
+
+plugins {
+    id("SupportJavaLibraryPlugin")
+}
+
+sourceCompatibility = JavaVersion.VERSION_1_8
+targetCompatibility = JavaVersion.VERSION_1_8
+
+dependencies {
+    compile(ANDROIDX_ANNOTATION)
+}
+
+supportLibrary {
+    name = "Android Room-Common-Java8"
+    publish = false
+    mavenVersion = LibraryVersions.ROOM
+    mavenGroup = LibraryGroups.ROOM
+    inceptionYear = "2019"
+    description = "Android Room-Common-Java8"
+    url = SupportLibraryExtension.ARCHITECTURE_URL
+}
\ No newline at end of file
diff --git a/room/common-java8/src/main/java/androidx/room/util/SneakyThrow.java b/room/common-java8/src/main/java/androidx/room/util/SneakyThrow.java
new file mode 100644
index 0000000..21497d2
--- /dev/null
+++ b/room/common-java8/src/main/java/androidx/room/util/SneakyThrow.java
@@ -0,0 +1,47 @@
+/*
+ * 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.room.util;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.RestrictTo;
+
+/**
+ * Java 8 Sneaky Throw technique.
+ *
+ * @hide
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
+public class SneakyThrow {
+
+    /**
+     * Re-throws a checked exception as if it was a runtime exception without wrapping it.
+     *
+     * @param e the exception to re-throw.
+     */
+    public static void reThrow(@NonNull Exception e) {
+        sneakyThrow(e);
+    }
+
+    @SuppressWarnings("unchecked")
+    private static <E extends Throwable> void sneakyThrow(@NonNull Throwable e) throws E {
+        throw (E) e;
+    }
+
+    private SneakyThrow() {
+
+    }
+}
diff --git a/room/compiler/SQLite.g4 b/room/compiler/SQLite.g4
index 8eb895d..ab60202 100644
--- a/room/compiler/SQLite.g4
+++ b/room/compiler/SQLite.g4
@@ -28,16 +28,16 @@
  *                https://github.com/bkiers/sqlite-parser
  * Developed by : Bart Kiers, [email protected]
  */
-grammar SQLite;
+grammar SQLite; // For version 3.24.0 of SQLite
 
 parse
  : ( sql_stmt_list | error )* EOF
  ;
 
 error
- : UNEXPECTED_CHAR 
-   { 
-     throw new RuntimeException("UNEXPECTED_CHAR=" + $UNEXPECTED_CHAR.text); 
+ : UNEXPECTED_CHAR
+   {
+     throw new RuntimeException("UNEXPECTED_CHAR=" + $UNEXPECTED_CHAR.text);
    }
  ;
 
@@ -51,7 +51,6 @@
                                       | attach_stmt
                                       | begin_stmt
                                       | commit_stmt
-                                      | compound_select_stmt
                                       | create_index_stmt
                                       | create_table_stmt
                                       | create_trigger_stmt
@@ -64,14 +63,12 @@
                                       | drop_table_stmt
                                       | drop_trigger_stmt
                                       | drop_view_stmt
-                                      | factored_select_stmt
                                       | insert_stmt
                                       | pragma_stmt
                                       | reindex_stmt
                                       | release_stmt
                                       | rollback_stmt
                                       | savepoint_stmt
-                                      | simple_select_stmt
                                       | select_stmt
                                       | update_stmt
                                       | update_stmt_limited
@@ -79,18 +76,18 @@
  ;
 
 alter_table_stmt
- : K_ALTER K_TABLE ( database_name '.' )? table_name
+ : K_ALTER K_TABLE ( schema_name '.' )? table_name
    ( K_RENAME K_TO new_table_name
    | K_ADD K_COLUMN? column_def
    )
  ;
 
 analyze_stmt
- : K_ANALYZE ( database_name | table_or_index_name | database_name '.' table_or_index_name )?
+ : K_ANALYZE ( schema_name | table_or_index_name | schema_name '.' table_or_index_name )?
  ;
 
 attach_stmt
- : K_ATTACH K_DATABASE? expr K_AS database_name
+ : K_ATTACH K_DATABASE? expr K_AS schema_name
  ;
 
 begin_stmt
@@ -101,43 +98,36 @@
  : ( K_COMMIT | K_END ) ( K_TRANSACTION transaction_name? )?
  ;
 
-compound_select_stmt
- : with_clause?
-   select_core ( ( K_UNION K_ALL? | K_INTERSECT | K_EXCEPT ) select_core )+
-   ( K_ORDER K_BY ordering_term ( ',' ordering_term )* )?
-   ( K_LIMIT expr ( ( K_OFFSET | ',' ) expr )? )?
- ;
-
 create_index_stmt
  : K_CREATE K_UNIQUE? K_INDEX ( K_IF K_NOT K_EXISTS )?
-   ( database_name '.' )? index_name K_ON table_name '(' indexed_column ( ',' indexed_column )* ')'
+   ( schema_name '.' )? index_name K_ON table_name '(' indexed_column ( ',' indexed_column )* ')'
    ( K_WHERE expr )?
  ;
 
 create_table_stmt
  : K_CREATE ( K_TEMP | K_TEMPORARY )? K_TABLE ( K_IF K_NOT K_EXISTS )?
-   ( database_name '.' )? table_name
-   ( '(' column_def ( ',' column_def )*? ( ',' table_constraint )* ')' ( K_WITHOUT IDENTIFIER )?
+   ( schema_name '.' )? table_name
+   ( '(' column_def ( ',' column_def )*? ( ',' table_constraint )* ')' WITHOUT_ROWID?
    | K_AS select_stmt
    )
  ;
 
 create_trigger_stmt
  : K_CREATE ( K_TEMP | K_TEMPORARY )? K_TRIGGER ( K_IF K_NOT K_EXISTS )?
-   ( database_name '.' )? trigger_name ( K_BEFORE  | K_AFTER | K_INSTEAD K_OF )?
-   ( K_DELETE | K_INSERT | K_UPDATE ( K_OF column_name ( ',' column_name )* )? ) K_ON ( database_name '.' )? table_name
+   ( schema_name '.' )? trigger_name ( K_BEFORE  | K_AFTER | K_INSTEAD K_OF )?
+   ( K_DELETE | K_INSERT | K_UPDATE ( K_OF column_name ( ',' column_name )* )? ) K_ON ( schema_name '.' )? table_name
    ( K_FOR K_EACH K_ROW )? ( K_WHEN expr )?
    K_BEGIN ( ( update_stmt | insert_stmt | delete_stmt | select_stmt ) ';' )+ K_END
  ;
 
 create_view_stmt
  : K_CREATE ( K_TEMP | K_TEMPORARY )? K_VIEW ( K_IF K_NOT K_EXISTS )?
-   ( database_name '.' )? view_name K_AS select_stmt
+   ( schema_name '.' )? view_name ( column_name ( ',' column_name )* )? K_AS select_stmt
  ;
 
 create_virtual_table_stmt
  : K_CREATE K_VIRTUAL K_TABLE ( K_IF K_NOT K_EXISTS )?
-   ( database_name '.' )? table_name
+   ( schema_name '.' )? table_name
    K_USING module_name ( '(' module_argument ( ',' module_argument )* ')' )?
  ;
 
@@ -149,36 +139,27 @@
 delete_stmt_limited
  : with_clause? K_DELETE K_FROM qualified_table_name
    ( K_WHERE expr )?
-   ( ( K_ORDER K_BY ordering_term ( ',' ordering_term )* )?
-     K_LIMIT expr ( ( K_OFFSET | ',' ) expr )?
-   )?
+   ( order_clause? limit_clause )?
  ;
 
 detach_stmt
- : K_DETACH K_DATABASE? database_name
+ : K_DETACH K_DATABASE? schema_name
  ;
 
 drop_index_stmt
- : K_DROP K_INDEX ( K_IF K_EXISTS )? ( database_name '.' )? index_name
+ : K_DROP K_INDEX ( K_IF K_EXISTS )? ( schema_name '.' )? index_name
  ;
 
 drop_table_stmt
- : K_DROP K_TABLE ( K_IF K_EXISTS )? ( database_name '.' )? table_name
+ : K_DROP K_TABLE ( K_IF K_EXISTS )? ( schema_name '.' )? table_name
  ;
 
 drop_trigger_stmt
- : K_DROP K_TRIGGER ( K_IF K_EXISTS )? ( database_name '.' )? trigger_name
+ : K_DROP K_TRIGGER ( K_IF K_EXISTS )? ( schema_name '.' )? trigger_name
  ;
 
 drop_view_stmt
- : K_DROP K_VIEW ( K_IF K_EXISTS )? ( database_name '.' )? view_name
- ;
-
-factored_select_stmt
- : with_clause?
-   select_core ( compound_operator select_core )*
-   ( K_ORDER K_BY ordering_term ( ',' ordering_term )* )?
-   ( K_LIMIT expr ( ( K_OFFSET | ',' ) expr )? )?
+ : K_DROP K_VIEW ( K_IF K_EXISTS )? ( schema_name '.' )? view_name
  ;
 
 insert_stmt
@@ -189,21 +170,30 @@
                 | K_INSERT K_OR K_ABORT
                 | K_INSERT K_OR K_FAIL
                 | K_INSERT K_OR K_IGNORE ) K_INTO
-   ( database_name '.' )? table_name ( '(' column_name ( ',' column_name )* ')' )?
+   ( schema_name '.' )? table_name ( K_AS table_alias )? ( '(' column_name ( ',' column_name )* ')' )?
    ( K_VALUES '(' expr ( ',' expr )* ')' ( ',' '(' expr ( ',' expr )* ')' )*
    | select_stmt
    | K_DEFAULT K_VALUES
    )
+   upsert_clause?
+ ;
+
+upsert_clause
+ : K_ON K_CONFLICT ( '(' indexed_column ( ',' indexed_column )* ')' ( K_WHERE expr )? )?
+   ( DO_NOTHING
+   | DO_UPDATE K_SET ( column_name | column_name_list ) '=' expr
+     ( ',' ( column_name | column_name_list ) '=' expr )*
+     ( K_WHERE expr )?
+   )
  ;
 
 pragma_stmt
- : K_PRAGMA ( database_name '.' )? pragma_name ( '=' pragma_value
-                                               | '(' pragma_value ')' )?
+ : K_PRAGMA ( schema_name '.' )? pragma_name ( '=' pragma_value | '(' pragma_value ')' )?
  ;
 
 reindex_stmt
  : K_REINDEX ( collation_name
-             | ( database_name '.' )? ( table_name | index_name )
+             | ( schema_name '.' )? ( table_name | index_name )
              )?
  ;
 
@@ -219,17 +209,11 @@
  : K_SAVEPOINT savepoint_name
  ;
 
-simple_select_stmt
- : with_clause?
-   select_core ( K_ORDER K_BY ordering_term ( ',' ordering_term )* )?
-   ( K_LIMIT expr ( ( K_OFFSET | ',' ) expr )? )?
- ;
-
 select_stmt
  : with_clause?
    select_or_values ( compound_operator select_or_values )*
-   ( K_ORDER K_BY ordering_term ( ',' ordering_term )* )?
-   ( K_LIMIT expr ( ( K_OFFSET | ',' ) expr )? )?
+   order_clause?
+   limit_clause?
  ;
 
 select_or_values
@@ -246,7 +230,8 @@
                          | K_OR K_REPLACE
                          | K_OR K_FAIL
                          | K_OR K_IGNORE )? qualified_table_name
-   K_SET column_name '=' expr ( ',' column_name '=' expr )* ( K_WHERE expr )?
+   K_SET ( column_name | column_name_list ) '=' expr ( ',' ( column_name | column_name_list ) '=' expr )*
+   ( K_WHERE expr )?
  ;
 
 update_stmt_limited
@@ -255,14 +240,13 @@
                          | K_OR K_REPLACE
                          | K_OR K_FAIL
                          | K_OR K_IGNORE )? qualified_table_name
-   K_SET column_name '=' expr ( ',' column_name '=' expr )* ( K_WHERE expr )?
-   ( ( K_ORDER K_BY ordering_term ( ',' ordering_term )* )?
-     K_LIMIT expr ( ( K_OFFSET | ',' ) expr )?
-   )?
+   K_SET ( column_name | column_name_list ) '=' expr ( ',' ( column_name | column_name_list ) '=' expr )*
+   ( K_WHERE expr )?
+   ( order_clause? limit_clause )?
  ;
 
 vacuum_stmt
- : K_VACUUM
+ : K_VACUUM schema_name?
  ;
 
 column_def
@@ -271,7 +255,7 @@
 
 type_name
  : name+? ( '(' signed_number ')'
-         | '(' signed_number ',' signed_number ')' )?
+          | '(' signed_number ',' signed_number ')' )?
  ;
 
 column_constraint
@@ -296,45 +280,23 @@
    )?
  ;
 
-/*
-    SQLite understands the following binary operators, in order from highest to
-    lowest precedence:
-
-    ||
-    *    /    %
-    +    -
-    <<   >>   &    |
-    <    <=   >    >=
-    =    ==   !=   <>   IS   IS NOT   IN   LIKE   GLOB   MATCH   REGEXP
-    AND
-    OR
-*/
 expr
  : literal_value
  | BIND_PARAMETER
- | ( ( database_name '.' )? table_name '.' )? column_name
+ | ( ( schema_name '.' )? table_name '.' )? column_name
  | unary_operator expr
- | expr '||' expr
- | expr ( '*' | '/' | '%' ) expr
- | expr ( '+' | '-' ) expr
- | expr ( '<<' | '>>' | '&' | '|' ) expr
- | expr ( '<' | '<=' | '>' | '>=' ) expr
- | expr ( '=' | '==' | '!=' | '<>' ) expr
- | expr K_AND expr
- | expr K_OR expr
+ | expr binary_operator expr
  | function_name '(' ( K_DISTINCT? expr ( ',' expr )* | '*' )? ')'
- | '(' expr ')'
+ | '(' expr ( ',' expr )* ')'
  | K_CAST '(' expr K_AS type_name ')'
  | expr K_COLLATE collation_name
  | expr K_NOT? ( K_LIKE | K_GLOB | K_REGEXP | K_MATCH ) expr ( K_ESCAPE expr )?
  | expr ( K_ISNULL | K_NOTNULL | K_NOT K_NULL )
  | expr K_IS K_NOT? expr
  | expr K_NOT? K_BETWEEN expr K_AND expr
- | expr K_NOT? K_IN ( '(' ( select_stmt
-                          | expr ( ',' expr )*
-                          )?
-                      ')'
-                    | ( database_name '.' )? table_name )
+ | expr K_NOT? K_IN ( '(' ( select_stmt | expr ( ',' expr )* )? ')'
+                    | ( schema_name '.' )? table_name
+                    | ( schema_name '.' )? table_function '(' ( expr ( ',' expr )* )? ')' )
  | ( ( K_NOT )? K_EXISTS )? '(' select_stmt ')'
  | K_CASE expr? ( K_WHEN expr K_THEN expr )+ ( K_ELSE expr )? K_END
  | raise_function
@@ -360,7 +322,7 @@
  ;
 
 indexed_column
- : column_name ( K_COLLATE collation_name )? ( K_ASC | K_DESC )?
+ : ( column_name | expr ) ( K_COLLATE collation_name )? ( K_ASC | K_DESC )?
  ;
 
 table_constraint
@@ -375,23 +337,32 @@
  : K_WITH K_RECURSIVE? common_table_expression ( ',' common_table_expression )*
  ;
 
+common_table_expression
+ : table_name ( '(' column_name ( ',' column_name )* ')' )? K_AS '(' select_stmt ')'
+ ;
+
 qualified_table_name
- : ( database_name '.' )? table_name ( K_INDEXED K_BY index_name
-                                     | K_NOT K_INDEXED )?
+ : ( schema_name '.' )? table_name ( K_AS table_alias )?
+   ( K_INDEXED K_BY index_name | K_NOT K_INDEXED )?
+ ;
+
+order_clause
+ : K_ORDER K_BY ordering_term ( ',' ordering_term )*
  ;
 
 ordering_term
  : expr ( K_COLLATE collation_name )? ( K_ASC | K_DESC )?
  ;
 
+limit_clause
+ : K_LIMIT expr ( ( K_OFFSET | ',' ) expr )?
+ ;
+
 pragma_value
  : signed_number
  | name
  | STRING_LITERAL
- ;
-
-common_table_expression
- : table_name ( '(' column_name ( ',' column_name )* ')' )? K_AS '(' select_stmt ')'
+ | boolean_literal
  ;
 
 result_column
@@ -402,12 +373,9 @@
 
 table_or_subquery
  : ( schema_name '.' )? table_name ( K_AS? table_alias )?
-   ( K_INDEXED K_BY index_name
-   | K_NOT K_INDEXED )?
- | ( schema_name '.' )? table_function_name '(' ( expr ( ',' expr )* )? ')' ( K_AS? table_alias )?
- | '(' ( table_or_subquery ( ',' table_or_subquery )*
-       | join_clause )
-   ')'
+   ( K_INDEXED K_BY index_name | K_NOT K_INDEXED )?
+ | ( schema_name '.' )? table_function '(' ( expr ( ',' expr )* )? ')' ( K_AS? table_alias )?
+ | '(' ( table_or_subquery ( ',' table_or_subquery )* | join_clause ) ')'
  | '(' select_stmt ')' ( K_AS? table_alias )?
  ;
 
@@ -425,14 +393,6 @@
    | K_USING '(' column_name ( ',' column_name )* ')' )?
  ;
 
-select_core
- : K_SELECT ( K_DISTINCT | K_ALL )? result_column ( ',' result_column )*
-   ( K_FROM ( table_or_subquery ( ',' table_or_subquery )* | join_clause ) )?
-   ( K_WHERE expr )?
-   ( K_GROUP K_BY expr ( ',' expr )* ( K_HAVING expr )? )?
- | K_VALUES '(' expr ( ',' expr )* ')' ( ',' '(' expr ( ',' expr )* ')' )*
- ;
-
 compound_operator
  : K_UNION
  | K_UNION K_ALL
@@ -452,6 +412,12 @@
  | K_CURRENT_TIME
  | K_CURRENT_DATE
  | K_CURRENT_TIMESTAMP
+ | boolean_literal
+ ;
+
+boolean_literal
+ : TRUE
+ | FALSE
  ;
 
 unary_operator
@@ -461,6 +427,33 @@
  | K_NOT
  ;
 
+/*
+    SQLite understands the following binary operators, in order from highest to
+    lowest precedence:
+
+    ||
+    *    /    %
+    +    -
+    <<   >>   &    |
+    <    <=   >    >=
+    =    ==   !=   <>   IS   IS NOT   IN   LIKE   GLOB   MATCH   REGEXP
+    AND
+    OR
+
+    This rule is only used in `expr`, which has more complete directives for `IS` through `REGEXP`,
+    so we leave them out here.
+*/
+binary_operator
+ : '||'
+ | ( '*' | '/' | '%' )
+ | ( '+' | '-' )
+ | ( '<<' | '>>' | '&' | '|' )
+ | ( '<' | '<=' | '>' | '>=' )
+ | ( '=' | '==' | '!=' | '<>' )
+ | K_AND
+ | K_OR
+ ;
+
 error_message
  : STRING_LITERAL
  ;
@@ -475,6 +468,10 @@
  | STRING_LITERAL
  ;
 
+column_name_list
+ : '(' column_name ( ',' column_name )* ')'
+ ;
+
 keyword
  : K_ABORT
  | K_ACTION
@@ -612,15 +609,11 @@
  : any_name
  ;
 
-database_name
- : any_name
- ;
-
 schema_name
  : any_name
  ;
 
-table_function_name
+table_function
  : any_name
  ;
 
@@ -713,6 +706,8 @@
 EQ : '==';
 NOT_EQ1 : '!=';
 NOT_EQ2 : '<>';
+TRUE : T R U E;
+FALSE : F A L S E;
 
 // http://www.sqlite.org/lang_keywords.html
 K_ABORT : A B O R T;
@@ -840,16 +835,22 @@
 K_WITH : W I T H;
 K_WITHOUT : W I T H O U T;
 
+// These are not keywords, but their constituents might be wrongly matched as identifiers.
+WITHOUT_ROWID: K_WITHOUT SPACES R O W I D;
+DO_NOTHING: D O SPACES N O T H I N G;
+DO_UPDATE: D O SPACES K_UPDATE;
+
 IDENTIFIER
  : '"' (~'"' | '""')* '"'
  | '`' (~'`' | '``')* '`'
  | '[' ~']'* ']'
- | [a-zA-Z_] [a-zA-Z_0-9]* // TODO check: needs more chars in set
+ | [a-zA-Z_\u00a1-\uffff] [a-zA-Z_0-9\u00a1-\uffff]*
  ;
 
 NUMERIC_LITERAL
  : DIGIT+ ( '.' DIGIT* )? ( E [-+]? DIGIT+ )?
  | '.' DIGIT+ ( E [-+]? DIGIT+ )?
+ | '0' X HEXDIGIT+
  ;
 
 BIND_PARAMETER
@@ -882,6 +883,7 @@
  ;
 
 fragment DIGIT : [0-9];
+fragment HEXDIGIT : [0-9a-fA-F];
 
 fragment A : [aA];
 fragment B : [bB];
diff --git a/room/compiler/src/main/kotlin/androidx/room/parser/SqlParser.kt b/room/compiler/src/main/kotlin/androidx/room/parser/SqlParser.kt
index dba9aa7..bb93c23 100644
--- a/room/compiler/src/main/kotlin/androidx/room/parser/SqlParser.kt
+++ b/room/compiler/src/main/kotlin/androidx/room/parser/SqlParser.kt
@@ -51,16 +51,11 @@
 
     private fun findQueryType(statement: ParseTree): QueryType {
         return when (statement) {
-            is SQLiteParser.Factored_select_stmtContext,
-            is SQLiteParser.Compound_select_stmtContext,
-            is SQLiteParser.Select_stmtContext,
-            is SQLiteParser.Simple_select_stmtContext ->
+            is SQLiteParser.Select_stmtContext ->
                 QueryType.SELECT
-
             is SQLiteParser.Delete_stmt_limitedContext,
             is SQLiteParser.Delete_stmtContext ->
                 QueryType.DELETE
-
             is SQLiteParser.Insert_stmtContext ->
                 QueryType.INSERT
             is SQLiteParser.Update_stmtContext,
diff --git a/room/compiler/src/main/kotlin/androidx/room/processor/MethodProcessorDelegate.kt b/room/compiler/src/main/kotlin/androidx/room/processor/MethodProcessorDelegate.kt
index 8dc5742..4d8cfa7 100644
--- a/room/compiler/src/main/kotlin/androidx/room/processor/MethodProcessorDelegate.kt
+++ b/room/compiler/src/main/kotlin/androidx/room/processor/MethodProcessorDelegate.kt
@@ -201,9 +201,10 @@
         adapter = context.typeAdapterStore.findPreparedQueryResultAdapter(returnType, query)
     ) { callableImpl, dbField ->
         addStatement(
-            "return $T.execute($N, $L, $N)",
+            "return $T.execute($N, $L, $L, $N)",
             RoomCoroutinesTypeNames.COROUTINES_ROOM,
             dbField,
+            "true", // inTransaction
             callableImpl,
             continuationParam.simpleName.toString()
         )
@@ -217,9 +218,10 @@
         adapter = context.typeAdapterStore.findInsertAdapter(returnType, params)
     ) { callableImpl, dbField ->
         addStatement(
-            "return $T.execute($N, $L, $N)",
+            "return $T.execute($N, $L, $L, $N)",
             RoomCoroutinesTypeNames.COROUTINES_ROOM,
             dbField,
+            "true", // inTransaction
             callableImpl,
             continuationParam.simpleName.toString()
         )
@@ -231,9 +233,10 @@
             adapter = context.typeAdapterStore.findDeleteOrUpdateAdapter(returnType)
         ) { callableImpl, dbField ->
             addStatement(
-                "return $T.execute($N, $L, $N)",
+                "return $T.execute($N, $L, $L, $N)",
                 RoomCoroutinesTypeNames.COROUTINES_ROOM,
                 dbField,
+                "true", // inTransaction
                 callableImpl,
                 continuationParam.simpleName.toString()
             )
diff --git a/room/compiler/src/main/kotlin/androidx/room/solver/prepared/binderprovider/GuavaListenableFuturePreparedQueryResultBinderProvider.kt b/room/compiler/src/main/kotlin/androidx/room/solver/prepared/binderprovider/GuavaListenableFuturePreparedQueryResultBinderProvider.kt
index 95e4e71..54b3d23 100644
--- a/room/compiler/src/main/kotlin/androidx/room/solver/prepared/binderprovider/GuavaListenableFuturePreparedQueryResultBinderProvider.kt
+++ b/room/compiler/src/main/kotlin/androidx/room/solver/prepared/binderprovider/GuavaListenableFuturePreparedQueryResultBinderProvider.kt
@@ -52,9 +52,10 @@
             adapter = context.typeAdapterStore.findPreparedQueryResultAdapter(typeArg, query)
         ) { callableImpl, dbField ->
             addStatement(
-                "return $T.createListenableFuture($N, $L)",
+                "return $T.createListenableFuture($N, $L, $L)",
                 RoomGuavaTypeNames.GUAVA_ROOM,
                 dbField,
+                "true", // inTransaction
                 callableImpl
             )
         }
diff --git a/room/compiler/src/main/kotlin/androidx/room/solver/query/result/CoroutineResultBinder.kt b/room/compiler/src/main/kotlin/androidx/room/solver/query/result/CoroutineResultBinder.kt
index b59ecc0..d97ba61 100644
--- a/room/compiler/src/main/kotlin/androidx/room/solver/query/result/CoroutineResultBinder.kt
+++ b/room/compiler/src/main/kotlin/androidx/room/solver/query/result/CoroutineResultBinder.kt
@@ -57,9 +57,10 @@
 
         scope.builder().apply {
             addStatement(
-                "return $T.execute($N, $L, $N)",
+                "return $T.execute($N, $L, $L, $N)",
                 RoomCoroutinesTypeNames.COROUTINES_ROOM,
                 dbField,
+                if (inTransaction) "true" else "false",
                 callableImpl,
                 continuationParamName)
         }
diff --git a/room/compiler/src/main/kotlin/androidx/room/solver/query/result/GuavaListenableFutureQueryResultBinder.kt b/room/compiler/src/main/kotlin/androidx/room/solver/query/result/GuavaListenableFutureQueryResultBinder.kt
index 7141786..722242e 100644
--- a/room/compiler/src/main/kotlin/androidx/room/solver/query/result/GuavaListenableFutureQueryResultBinder.kt
+++ b/room/compiler/src/main/kotlin/androidx/room/solver/query/result/GuavaListenableFutureQueryResultBinder.kt
@@ -56,9 +56,10 @@
 
         scope.builder().apply {
             addStatement(
-                "return $T.createListenableFuture($N, $L, $L, $L)",
+                "return $T.createListenableFuture($N, $L, $L, $L, $L)",
                 RoomGuavaTypeNames.GUAVA_ROOM,
                 dbField,
+                if (inTransaction) "true" else "false",
                 callableImpl,
                 roomSQLiteQueryVar,
                 canReleaseQuery
diff --git a/room/compiler/src/main/kotlin/androidx/room/solver/query/result/LiveDataQueryResultBinder.kt b/room/compiler/src/main/kotlin/androidx/room/solver/query/result/LiveDataQueryResultBinder.kt
index 2eebcda5..cd84015 100644
--- a/room/compiler/src/main/kotlin/androidx/room/solver/query/result/LiveDataQueryResultBinder.kt
+++ b/room/compiler/src/main/kotlin/androidx/room/solver/query/result/LiveDataQueryResultBinder.kt
@@ -67,8 +67,12 @@
         scope.builder().apply {
             val tableNamesList = tableNames.joinToString(",") { "\"$it\"" }
             addStatement(
-                "return $N.getInvalidationTracker().createLiveData(new $T{$L}, $L)",
-                dbField, String::class.arrayTypeName(), tableNamesList, callableImpl
+                "return $N.getInvalidationTracker().createLiveData(new $T{$L}, $L, $L)",
+                dbField,
+                String::class.arrayTypeName(),
+                tableNamesList,
+                if (inTransaction) "true" else "false",
+                callableImpl
             )
         }
     }
diff --git a/room/compiler/src/main/kotlin/androidx/room/solver/query/result/RxQueryResultBinder.kt b/room/compiler/src/main/kotlin/androidx/room/solver/query/result/RxQueryResultBinder.kt
index 7b2e54b..d52e804 100644
--- a/room/compiler/src/main/kotlin/androidx/room/solver/query/result/RxQueryResultBinder.kt
+++ b/room/compiler/src/main/kotlin/androidx/room/solver/query/result/RxQueryResultBinder.kt
@@ -58,9 +58,14 @@
         }.build()
         scope.builder().apply {
             val tableNamesList = queryTableNames.joinToString(",") { "\"$it\"" }
-            addStatement("return $T.$N($N, new $T{$L}, $L)",
-                    RoomRxJava2TypeNames.RX_ROOM, rxType.methodName, dbField,
-                    String::class.arrayTypeName(), tableNamesList, callableImpl)
+            addStatement("return $T.$N($N, $L, new $T{$L}, $L)",
+                RoomRxJava2TypeNames.RX_ROOM,
+                rxType.methodName,
+                dbField,
+                if (inTransaction) "true" else "false",
+                String::class.arrayTypeName(),
+                tableNamesList,
+                callableImpl)
         }
     }
 
diff --git a/room/compiler/src/main/kotlin/androidx/room/solver/shortcut/binderprovider/GuavaListenableFutureDeleteOrUpdateMethodBinderProvider.kt b/room/compiler/src/main/kotlin/androidx/room/solver/shortcut/binderprovider/GuavaListenableFutureDeleteOrUpdateMethodBinderProvider.kt
index b43e23c..106bc14 100644
--- a/room/compiler/src/main/kotlin/androidx/room/solver/shortcut/binderprovider/GuavaListenableFutureDeleteOrUpdateMethodBinderProvider.kt
+++ b/room/compiler/src/main/kotlin/androidx/room/solver/shortcut/binderprovider/GuavaListenableFutureDeleteOrUpdateMethodBinderProvider.kt
@@ -54,9 +54,10 @@
         val adapter = context.typeAdapterStore.findDeleteOrUpdateAdapter(typeArg)
         return createDeleteOrUpdateBinder(typeArg, adapter) { callableImpl, dbField ->
             addStatement(
-                "return $T.createListenableFuture($N, $L)",
+                "return $T.createListenableFuture($N, $L, $L)",
                 RoomGuavaTypeNames.GUAVA_ROOM,
                 dbField,
+                "true", // inTransaction
                 callableImpl
             )
         }
diff --git a/room/compiler/src/main/kotlin/androidx/room/solver/shortcut/binderprovider/GuavaListenableFutureInsertMethodBinderProvider.kt b/room/compiler/src/main/kotlin/androidx/room/solver/shortcut/binderprovider/GuavaListenableFutureInsertMethodBinderProvider.kt
index 7b3cb47..f603510 100644
--- a/room/compiler/src/main/kotlin/androidx/room/solver/shortcut/binderprovider/GuavaListenableFutureInsertMethodBinderProvider.kt
+++ b/room/compiler/src/main/kotlin/androidx/room/solver/shortcut/binderprovider/GuavaListenableFutureInsertMethodBinderProvider.kt
@@ -58,9 +58,10 @@
         val adapter = context.typeAdapterStore.findInsertAdapter(typeArg, params)
         return createInsertBinder(typeArg, adapter) { callableImpl, dbField ->
             addStatement(
-                "return $T.createListenableFuture($N, $L)",
+                "return $T.createListenableFuture($N, $L, $L)",
                 RoomGuavaTypeNames.GUAVA_ROOM,
                 dbField,
+                "true", // inTransaction
                 callableImpl
             )
         }
diff --git a/room/compiler/src/test/data/daoWriter/output/ComplexDao.java b/room/compiler/src/test/data/daoWriter/output/ComplexDao.java
index bb945a3..f047ecd4 100644
--- a/room/compiler/src/test/data/daoWriter/output/ComplexDao.java
+++ b/room/compiler/src/test/data/daoWriter/output/ComplexDao.java
@@ -281,7 +281,7 @@
         final RoomSQLiteQuery _statement = RoomSQLiteQuery.acquire(_sql, 1);
         int _argIndex = 1;
         _statement.bindLong(_argIndex, id);
-        return __db.getInvalidationTracker().createLiveData(new String[]{"user"}, new Callable<User>() {
+        return __db.getInvalidationTracker().createLiveData(new String[]{"user"}, false, new Callable<User>() {
             @Override
             public User call() throws Exception {
                 final Cursor _cursor = DBUtil.query(__db, _statement, false);
@@ -330,7 +330,7 @@
             _statement.bindLong(_argIndex, _item);
             _argIndex ++;
         }
-        return __db.getInvalidationTracker().createLiveData(new String[]{"user"}, new Callable<List<User>>() {
+        return __db.getInvalidationTracker().createLiveData(new String[]{"user"}, false, new Callable<List<User>>() {
             @Override
             public List<User> call() throws Exception {
                 final Cursor _cursor = DBUtil.query(__db, _statement, false);
diff --git a/room/compiler/src/test/kotlin/androidx/room/parser/SqlParserTest.kt b/room/compiler/src/test/kotlin/androidx/room/parser/SqlParserTest.kt
index 885f7bd..f939e26 100644
--- a/room/compiler/src/test/kotlin/androidx/room/parser/SqlParserTest.kt
+++ b/room/compiler/src/test/kotlin/androidx/room/parser/SqlParserTest.kt
@@ -66,6 +66,17 @@
     }
 
     @Test
+    fun upsertQuery() {
+        val parsed = SqlParser.parse(
+            "INSERT INTO notes (id, content) VALUES (:id, :content) " +
+                "ON CONFLICT (id) DO UPDATE SET content = excluded.content, " +
+                "revision = revision + 1, modifiedTime = strftime('%s','now')"
+        )
+        assertThat(parsed.errors, `is`(emptyList()))
+        assertThat(parsed.type, `is`(QueryType.INSERT))
+    }
+
+    @Test
     fun explain() {
         assertErrors("EXPLAIN QUERY PLAN SELECT * FROM users",
                 ParserErrors.invalidQueryType(QueryType.EXPLAIN))
@@ -140,6 +151,19 @@
     }
 
     @Test
+    fun unicodeInIdentifiers() {
+        val query = SqlParser.parse("SELECT 名, 色 FROM 猫")
+        assertThat(query.errors, `is`(emptyList()))
+        assertThat(query.tables, `is`(setOf(Table("猫", "猫"))))
+    }
+
+    @Test
+    fun rowValue_where() {
+        val query = SqlParser.parse("SELECT * FROM notes WHERE (id, content) > (:id, :content)")
+        assertThat(query.errors, `is`(emptyList()))
+    }
+
+    @Test
     fun findBindVariables() {
         assertVariables("select * from users")
         assertVariables("select * from users where name like ?", "?")
diff --git a/room/guava/src/main/java/androidx/room/guava/GuavaRoom.java b/room/guava/src/main/java/androidx/room/guava/GuavaRoom.java
index d4f6c2f..4e2f500 100644
--- a/room/guava/src/main/java/androidx/room/guava/GuavaRoom.java
+++ b/room/guava/src/main/java/androidx/room/guava/GuavaRoom.java
@@ -48,8 +48,8 @@
      * Returns a {@link ListenableFuture<T>} created by submitting the input {@code callable} to
      * {@link ArchTaskExecutor}'s background-threaded Executor.
      *
-     * @deprecated
-     *      Use {@link #createListenableFuture(RoomDatabase, Callable, RoomSQLiteQuery, boolean)}
+     * @deprecated Use {@link #createListenableFuture(RoomDatabase, boolean, Callable,
+     *             RoomSQLiteQuery, boolean)}
      */
     @Deprecated
     public static <T> ListenableFuture<T> createListenableFuture(
@@ -63,7 +63,11 @@
     /**
      * Returns a {@link ListenableFuture<T>} created by submitting the input {@code callable} to
      * {@link RoomDatabase}'s {@link java.util.concurrent.Executor}.
+     *
+     * @deprecated Use {@link #createListenableFuture(RoomDatabase, boolean, Callable,
+     *             RoomSQLiteQuery, boolean)}
      */
+    @Deprecated
     public static <T> ListenableFuture<T> createListenableFuture(
             final RoomDatabase roomDatabase,
             final Callable<T> callable,
@@ -73,6 +77,20 @@
                 roomDatabase.getQueryExecutor(), callable, query, releaseQuery);
     }
 
+    /**
+     * Returns a {@link ListenableFuture<T>} created by submitting the input {@code callable} to
+     * {@link RoomDatabase}'s {@link java.util.concurrent.Executor}.
+     */
+    public static <T> ListenableFuture<T> createListenableFuture(
+            final RoomDatabase roomDatabase,
+            final boolean inTransaction,
+            final Callable<T> callable,
+            final RoomSQLiteQuery query,
+            final boolean releaseQuery) {
+        return createListenableFuture(
+                getExecutor(roomDatabase, inTransaction), callable, query, releaseQuery);
+    }
+
     private static <T> ListenableFuture<T> createListenableFuture(
             final Executor executor,
             final Callable<T> callable,
@@ -104,12 +122,34 @@
     /**
      * Returns a {@link ListenableFuture<T>} created by submitting the input {@code callable} to
      * {@link RoomDatabase}'s {@link java.util.concurrent.Executor}.
+     *
+     * @deprecated Use {@link #createListenableFuture(RoomDatabase, boolean, Callable)}
      */
+    @Deprecated
     public static <T> ListenableFuture<T> createListenableFuture(
             final RoomDatabase roomDatabase,
             final Callable<T> callable) {
+        return createListenableFuture(roomDatabase, false, callable);
+    }
+
+    /**
+     * Returns a {@link ListenableFuture<T>} created by submitting the input {@code callable} to
+     * {@link RoomDatabase}'s {@link java.util.concurrent.Executor}.
+     */
+    public static <T> ListenableFuture<T> createListenableFuture(
+            final RoomDatabase roomDatabase,
+            final boolean inTransaction,
+            final Callable<T> callable) {
         ListenableFutureTask<T> listenableFutureTask = ListenableFutureTask.create(callable);
-        roomDatabase.getQueryExecutor().execute(listenableFutureTask);
+        getExecutor(roomDatabase, inTransaction).execute(listenableFutureTask);
         return listenableFutureTask;
     }
+
+    private static Executor getExecutor(RoomDatabase database, boolean inTransaction) {
+        if (inTransaction) {
+            return database.getTransactionExecutor();
+        } else {
+            return database.getQueryExecutor();
+        }
+    }
 }
diff --git a/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/test/SneakyThrowTest.kt b/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/test/SneakyThrowTest.kt
new file mode 100644
index 0000000..f462d00
--- /dev/null
+++ b/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/test/SneakyThrowTest.kt
@@ -0,0 +1,44 @@
+/*
+ * 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.room.integration.kotlintestapp.test
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import org.json.JSONException
+import org.json.JSONObject
+import org.junit.Assert.fail
+import org.junit.Test
+import org.junit.runner.RunWith
+import java.util.concurrent.Callable
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class SneakyThrowTest : TestDatabaseTest() {
+
+    @Test
+    fun testCheckedException() {
+        try {
+            database.runInTransaction(Callable<String> {
+                val json = JSONObject()
+                json.getString("key") // method declares that it throws JSONException
+            })
+            fail("runInTransaction should have thrown an exception")
+        } catch (ex: JSONException) {
+            // no-op on purpose
+        }
+    }
+}
\ No newline at end of file
diff --git a/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/test/SuspendingQueryTest.kt b/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/test/SuspendingQueryTest.kt
index 901f410..b8e81fa 100644
--- a/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/test/SuspendingQueryTest.kt
+++ b/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/test/SuspendingQueryTest.kt
@@ -17,6 +17,7 @@
 package androidx.room.integration.kotlintestapp.test
 
 import android.os.Build
+import androidx.arch.core.executor.ArchTaskExecutor
 import androidx.room.Room
 import androidx.room.RoomDatabase
 import androidx.room.integration.kotlintestapp.NewThreadDispatcher
@@ -45,8 +46,10 @@
 import org.junit.runner.RunWith
 import java.io.IOException
 import java.util.concurrent.CountDownLatch
+import java.util.concurrent.ExecutorService
 import java.util.concurrent.Executors
 import java.util.concurrent.TimeUnit
+import java.util.concurrent.atomic.AtomicInteger
 
 @LargeTest
 @RunWith(AndroidJUnit4::class)
@@ -633,6 +636,52 @@
 
     @Test
     @Suppress("DeferredResultUnused")
+    fun withTransaction_multipleTransactions_verifyThreadUsage() {
+        val busyThreadsCount = AtomicInteger()
+        // Executor wrapper that counts threads that are busy executing commands.
+        class WrappedService(val delegate: ExecutorService) : ExecutorService by delegate {
+            override fun execute(command: Runnable) {
+                delegate.execute {
+                    busyThreadsCount.incrementAndGet()
+                    try {
+                        command.run()
+                    } finally {
+                        busyThreadsCount.decrementAndGet()
+                    }
+                }
+            }
+        }
+        val wrappedExecutor = WrappedService(Executors.newCachedThreadPool())
+        val localDatabase = Room.inMemoryDatabaseBuilder(
+            ApplicationProvider.getApplicationContext(), TestDatabase::class.java)
+            .setQueryExecutor(ArchTaskExecutor.getIOThreadExecutor())
+            .setTransactionExecutor(wrappedExecutor)
+            .build()
+
+        // Run two parallel transactions but verify that only 1 thread is busy when the transactions
+        // execute, indicating that threads are not busy waiting on sql connections but are instead
+        // suspended.
+        runBlocking(Dispatchers.IO) {
+            async {
+                localDatabase.withTransaction {
+                    delay(200) // delay a bit to let the other transaction proceed
+                    assertThat(busyThreadsCount.get()).isEqualTo(1)
+                }
+            }
+
+            async {
+                localDatabase.withTransaction {
+                    delay(200) // delay a bit to let the other transaction proceed
+                    assertThat(busyThreadsCount.get()).isEqualTo(1)
+                }
+            }
+        }
+
+        wrappedExecutor.awaitTermination(1, TimeUnit.SECONDS)
+    }
+
+    @Test
+    @Suppress("DeferredResultUnused")
     fun withTransaction_leakTransactionContext_async() {
         runBlocking {
             val leakedContext = database.withTransaction {
@@ -700,7 +749,7 @@
             val executorService = Executors.newSingleThreadExecutor()
             val localDatabase = Room.inMemoryDatabaseBuilder(
                 ApplicationProvider.getApplicationContext(), TestDatabase::class.java)
-                .setQueryExecutor(executorService)
+                .setTransactionExecutor(executorService)
                 .build()
 
             // Simulate a busy executor, no thread to acquire for transaction.
@@ -735,7 +784,7 @@
             val executorService = Executors.newCachedThreadPool()
             val localDatabase = Room.inMemoryDatabaseBuilder(
                 ApplicationProvider.getApplicationContext(), TestDatabase::class.java)
-                .setQueryExecutor(executorService)
+                .setTransactionExecutor(executorService)
                 .build()
 
             executorService.shutdownNow()
diff --git a/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/test/InvalidationTrackerTest.java b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/test/InvalidationTrackerTest.java
index d49779a..6b88e33 100644
--- a/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/test/InvalidationTrackerTest.java
+++ b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/test/InvalidationTrackerTest.java
@@ -125,7 +125,7 @@
     public void createLiveData() throws ExecutionException, InterruptedException, TimeoutException {
         final LiveData<Item> liveData = mDb
                 .getInvalidationTracker()
-                .createLiveData(new String[]{"Item"}, () -> mDb.getItemDao().itemById(1));
+                .createLiveData(new String[]{"Item"}, false, () -> mDb.getItemDao().itemById(1));
 
         mDb.getItemDao().insert(new Item(1, "v1"));
 
@@ -147,7 +147,7 @@
             throws ExecutionException, InterruptedException, TimeoutException {
         LiveData<Item> liveData = mDb
                 .getInvalidationTracker()
-                .createLiveData(new String[]{"Item"}, () -> mDb.getItemDao().itemById(1));
+                .createLiveData(new String[]{"Item"}, false, () -> mDb.getItemDao().itemById(1));
 
         mDb.getItemDao().insert(new Item(1, "v1"));
 
diff --git a/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/test/SneakyThrowTest.java b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/test/SneakyThrowTest.java
new file mode 100644
index 0000000..ca96fdc
--- /dev/null
+++ b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/test/SneakyThrowTest.java
@@ -0,0 +1,91 @@
+/*
+ * 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.room.integration.testapp.test;
+
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import androidx.test.filters.SmallTest;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@SmallTest
+@RunWith(JUnit4.class)
+public class SneakyThrowTest extends TestDatabaseTest {
+
+    @Test
+    public void testRuntimeException_catchRuntimeException() {
+        try {
+            mDatabase.runInTransaction(() -> {
+                throw new IllegalStateException("Boom");
+            });
+            fail("runInTransaction should have thrown an exception");
+        } catch (IllegalStateException e) {
+            // no-op on purpose
+        }
+    }
+
+    @Test
+    public void testCheckedException_catchThrowable() {
+        try {
+            mDatabase.runInTransaction(() -> {
+                JSONObject json = new JSONObject();
+                return json.getString("key"); // method declares that it throws JSONException
+            });
+            fail("runInTransaction should have thrown an exception");
+        } catch (Throwable e) {
+            assertTrue(e instanceof JSONException);
+        }
+    }
+
+    @Test
+    public void testCheckedException_catchException() {
+        try {
+            mDatabase.runInTransaction(() -> {
+                JSONObject json = new JSONObject();
+                return json.getString("key"); // method declares that it throws JSONException
+            });
+            fail("runInTransaction should have thrown an exception");
+        } catch (Exception e) {
+            assertTrue(e instanceof JSONException);
+        }
+    }
+
+    @Test
+    public void testCheckedException_catchCheckedException() {
+        try {
+            // Must move the lambda to a method that declares throwing the checked exception,
+            // otherwise compiler complains about 'exception not thrown in corresponding block', a
+            // limitation of the sneaky throw technique.
+            doJsonWork();
+            fail("doJsonWork should have thrown an exception");
+        } catch (JSONException e) {
+            // no-op on purpose
+        }
+    }
+
+    private void doJsonWork() throws JSONException {
+        mDatabase.runInTransaction(() -> {
+            JSONObject json = new JSONObject();
+            return json.getString("key"); // method declares that it throws JSONException
+        });
+    }
+}
diff --git a/room/ktx/api/2.1.0-alpha06.txt b/room/ktx/api/2.1.0-alpha06.txt
index f5dcd16..851eb86 100644
--- a/room/ktx/api/2.1.0-alpha06.txt
+++ b/room/ktx/api/2.1.0-alpha06.txt
@@ -1,6 +1,10 @@
 // Signature format: 3.0
 package androidx.room {
 
+  public final class CoroutinesRoomKt {
+    ctor public CoroutinesRoomKt();
+  }
+
   public final class RoomDatabaseKt {
     ctor public RoomDatabaseKt();
     method public static suspend Object? acquireTransactionThread(java.util.concurrent.Executor, kotlinx.coroutines.Job controlJob, kotlin.coroutines.experimental.Continuation<? super kotlin.coroutines.ContinuationInterceptor> p);
diff --git a/room/ktx/api/current.txt b/room/ktx/api/current.txt
index f5dcd16..851eb86 100644
--- a/room/ktx/api/current.txt
+++ b/room/ktx/api/current.txt
@@ -1,6 +1,10 @@
 // Signature format: 3.0
 package androidx.room {
 
+  public final class CoroutinesRoomKt {
+    ctor public CoroutinesRoomKt();
+  }
+
   public final class RoomDatabaseKt {
     ctor public RoomDatabaseKt();
     method public static suspend Object? acquireTransactionThread(java.util.concurrent.Executor, kotlinx.coroutines.Job controlJob, kotlin.coroutines.experimental.Continuation<? super kotlin.coroutines.ContinuationInterceptor> p);
diff --git a/room/ktx/api/restricted_2.1.0-alpha06.txt b/room/ktx/api/restricted_2.1.0-alpha06.txt
index f31bf4d..866bb4d 100644
--- a/room/ktx/api/restricted_2.1.0-alpha06.txt
+++ b/room/ktx/api/restricted_2.1.0-alpha06.txt
@@ -2,12 +2,12 @@
 package androidx.room {
 
   @RestrictTo({RestrictTo.Scope.LIBRARY_GROUP_PREFIX}) public final class CoroutinesRoom {
-    method public static suspend <R> Object? execute(androidx.room.RoomDatabase p, java.util.concurrent.Callable<R> db, kotlin.coroutines.experimental.Continuation<? super R> callable);
+    method public static suspend <R> Object? execute(androidx.room.RoomDatabase p, boolean db, java.util.concurrent.Callable<R> inTransaction, kotlin.coroutines.experimental.Continuation<? super R> callable);
     field public static final androidx.room.CoroutinesRoom.Companion! Companion;
   }
 
   public static final class CoroutinesRoom.Companion {
-    method public suspend <R> Object? execute(androidx.room.RoomDatabase db, java.util.concurrent.Callable<R> callable, kotlin.coroutines.experimental.Continuation<? super R> p);
+    method public suspend <R> Object? execute(androidx.room.RoomDatabase db, boolean inTransaction, java.util.concurrent.Callable<R> callable, kotlin.coroutines.experimental.Continuation<? super R> p);
   }
 
 }
diff --git a/room/ktx/api/restricted_current.txt b/room/ktx/api/restricted_current.txt
index f31bf4d..866bb4d 100644
--- a/room/ktx/api/restricted_current.txt
+++ b/room/ktx/api/restricted_current.txt
@@ -2,12 +2,12 @@
 package androidx.room {
 
   @RestrictTo({RestrictTo.Scope.LIBRARY_GROUP_PREFIX}) public final class CoroutinesRoom {
-    method public static suspend <R> Object? execute(androidx.room.RoomDatabase p, java.util.concurrent.Callable<R> db, kotlin.coroutines.experimental.Continuation<? super R> callable);
+    method public static suspend <R> Object? execute(androidx.room.RoomDatabase p, boolean db, java.util.concurrent.Callable<R> inTransaction, kotlin.coroutines.experimental.Continuation<? super R> callable);
     field public static final androidx.room.CoroutinesRoom.Companion! Companion;
   }
 
   public static final class CoroutinesRoom.Companion {
-    method public suspend <R> Object? execute(androidx.room.RoomDatabase db, java.util.concurrent.Callable<R> callable, kotlin.coroutines.experimental.Continuation<? super R> p);
+    method public suspend <R> Object? execute(androidx.room.RoomDatabase db, boolean inTransaction, java.util.concurrent.Callable<R> callable, kotlin.coroutines.experimental.Continuation<? super R> p);
   }
 
 }
diff --git a/room/ktx/src/main/java/androidx/room/CoroutinesRoom.kt b/room/ktx/src/main/java/androidx/room/CoroutinesRoom.kt
index f4e3cc9..d38ed58 100644
--- a/room/ktx/src/main/java/androidx/room/CoroutinesRoom.kt
+++ b/room/ktx/src/main/java/androidx/room/CoroutinesRoom.kt
@@ -17,6 +17,7 @@
 package androidx.room
 
 import androidx.annotation.RestrictTo
+import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.asCoroutineDispatcher
 import kotlinx.coroutines.withContext
 import java.util.concurrent.Callable
@@ -33,18 +34,42 @@
     companion object {
 
         @JvmStatic
-        suspend fun <R> execute(db: RoomDatabase, callable: Callable<R>): R {
+        suspend fun <R> execute(
+            db: RoomDatabase,
+            inTransaction: Boolean,
+            callable: Callable<R>
+        ): R {
             if (db.isOpen && db.inTransaction()) {
                 return callable.call()
             }
 
             // Use the transaction dispatcher if we are on a transaction coroutine, otherwise
-            // use the query executor as dispatcher.
+            // use the database dispatchers.
             val context = coroutineContext[TransactionElement]?.transactionDispatcher
-                ?: db.queryExecutor.asCoroutineDispatcher()
+                ?: if (inTransaction) db.transactionDispatcher else db.queryDispatcher
             return withContext(context) {
                 callable.call()
             }
         }
     }
-}
\ No newline at end of file
+}
+
+/**
+ * Gets the query coroutine dispatcher.
+ *
+ * @hide
+ */
+internal val RoomDatabase.queryDispatcher: CoroutineDispatcher
+    get() = backingFieldMap.getOrPut("QueryDispatcher") {
+        queryExecutor.asCoroutineDispatcher()
+    } as CoroutineDispatcher
+
+/**
+ * Gets the transaction coroutine dispatcher.
+ *
+ * @hide
+ */
+internal val RoomDatabase.transactionDispatcher: CoroutineDispatcher
+    get() = backingFieldMap.getOrPut("TransactionDispatcher") {
+        queryExecutor.asCoroutineDispatcher()
+    } as CoroutineDispatcher
diff --git a/room/ktx/src/main/java/androidx/room/RoomDatabase.kt b/room/ktx/src/main/java/androidx/room/RoomDatabase.kt
index 86f1fde..49a972a 100644
--- a/room/ktx/src/main/java/androidx/room/RoomDatabase.kt
+++ b/room/ktx/src/main/java/androidx/room/RoomDatabase.kt
@@ -91,7 +91,7 @@
  */
 private suspend fun RoomDatabase.createTransactionContext(): CoroutineContext {
     val controlJob = Job()
-    val dispatcher = queryExecutor.acquireTransactionThread(controlJob)
+    val dispatcher = transactionExecutor.acquireTransactionThread(controlJob)
     val transactionElement = TransactionElement(controlJob, dispatcher)
     val threadLocalElement =
         suspendingTransactionId.asContextElement(System.identityHashCode(controlJob))
diff --git a/room/runtime/api/2.1.0-alpha06.txt b/room/runtime/api/2.1.0-alpha06.txt
index 00d448a..882b112 100644
--- a/room/runtime/api/2.1.0-alpha06.txt
+++ b/room/runtime/api/2.1.0-alpha06.txt
@@ -15,6 +15,7 @@
     field public final java.util.concurrent.Executor queryExecutor;
     field public final boolean requireMigration;
     field public final androidx.sqlite.db.SupportSQLiteOpenHelper.Factory sqliteOpenHelperFactory;
+    field public final java.util.concurrent.Executor transactionExecutor;
   }
 
   public class InvalidationTracker {
@@ -48,6 +49,7 @@
     method public androidx.room.InvalidationTracker getInvalidationTracker();
     method public androidx.sqlite.db.SupportSQLiteOpenHelper getOpenHelper();
     method public java.util.concurrent.Executor getQueryExecutor();
+    method public java.util.concurrent.Executor getTransactionExecutor();
     method public boolean inTransaction();
     method @CallSuper public void init(androidx.room.DatabaseConfiguration);
     method protected void internalInitInvalidationTracker(androidx.sqlite.db.SupportSQLiteDatabase);
@@ -73,6 +75,7 @@
     method public androidx.room.RoomDatabase.Builder<T> openHelperFactory(androidx.sqlite.db.SupportSQLiteOpenHelper.Factory?);
     method public androidx.room.RoomDatabase.Builder<T> setJournalMode(androidx.room.RoomDatabase.JournalMode);
     method public androidx.room.RoomDatabase.Builder<T> setQueryExecutor(java.util.concurrent.Executor);
+    method public androidx.room.RoomDatabase.Builder<T> setTransactionExecutor(java.util.concurrent.Executor);
   }
 
   public abstract static class RoomDatabase.Callback {
diff --git a/room/runtime/api/current.txt b/room/runtime/api/current.txt
index 00d448a..882b112 100644
--- a/room/runtime/api/current.txt
+++ b/room/runtime/api/current.txt
@@ -15,6 +15,7 @@
     field public final java.util.concurrent.Executor queryExecutor;
     field public final boolean requireMigration;
     field public final androidx.sqlite.db.SupportSQLiteOpenHelper.Factory sqliteOpenHelperFactory;
+    field public final java.util.concurrent.Executor transactionExecutor;
   }
 
   public class InvalidationTracker {
@@ -48,6 +49,7 @@
     method public androidx.room.InvalidationTracker getInvalidationTracker();
     method public androidx.sqlite.db.SupportSQLiteOpenHelper getOpenHelper();
     method public java.util.concurrent.Executor getQueryExecutor();
+    method public java.util.concurrent.Executor getTransactionExecutor();
     method public boolean inTransaction();
     method @CallSuper public void init(androidx.room.DatabaseConfiguration);
     method protected void internalInitInvalidationTracker(androidx.sqlite.db.SupportSQLiteDatabase);
@@ -73,6 +75,7 @@
     method public androidx.room.RoomDatabase.Builder<T> openHelperFactory(androidx.sqlite.db.SupportSQLiteOpenHelper.Factory?);
     method public androidx.room.RoomDatabase.Builder<T> setJournalMode(androidx.room.RoomDatabase.JournalMode);
     method public androidx.room.RoomDatabase.Builder<T> setQueryExecutor(java.util.concurrent.Executor);
+    method public androidx.room.RoomDatabase.Builder<T> setTransactionExecutor(java.util.concurrent.Executor);
   }
 
   public abstract static class RoomDatabase.Callback {
diff --git a/room/runtime/api/restricted_2.1.0-alpha06.txt b/room/runtime/api/restricted_2.1.0-alpha06.txt
index 10b8084..a792dc1 100644
--- a/room/runtime/api/restricted_2.1.0-alpha06.txt
+++ b/room/runtime/api/restricted_2.1.0-alpha06.txt
@@ -2,7 +2,7 @@
 package androidx.room {
 
   public class DatabaseConfiguration {
-    ctor @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public DatabaseConfiguration(android.content.Context, String?, androidx.sqlite.db.SupportSQLiteOpenHelper.Factory, androidx.room.RoomDatabase.MigrationContainer, java.util.List<androidx.room.RoomDatabase.Callback>?, boolean, androidx.room.RoomDatabase.JournalMode!, java.util.concurrent.Executor, boolean, boolean, boolean, java.util.Set<java.lang.Integer>?);
+    ctor @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public DatabaseConfiguration(android.content.Context, String?, androidx.sqlite.db.SupportSQLiteOpenHelper.Factory, androidx.room.RoomDatabase.MigrationContainer, java.util.List<androidx.room.RoomDatabase.Callback>?, boolean, androidx.room.RoomDatabase.JournalMode!, java.util.concurrent.Executor, java.util.concurrent.Executor, boolean, boolean, boolean, java.util.Set<java.lang.Integer>?);
   }
 
   @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public abstract class EntityDeletionOrUpdateAdapter<T> extends androidx.room.SharedSQLiteStatement {
@@ -32,7 +32,8 @@
     ctor @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public InvalidationTracker(androidx.room.RoomDatabase!, java.lang.String...!);
     ctor @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public InvalidationTracker(androidx.room.RoomDatabase!, java.util.Map<java.lang.String,java.lang.String>!, java.util.Map<java.lang.String,java.util.Set<java.lang.String>>!, java.lang.String...!);
     method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public void addWeakObserver(androidx.room.InvalidationTracker.Observer!);
-    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public <T> androidx.lifecycle.LiveData<T>! createLiveData(String[]!, java.util.concurrent.Callable<T>!);
+    method @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public <T> androidx.lifecycle.LiveData<T>! createLiveData(String[]!, java.util.concurrent.Callable<T>!);
+    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public <T> androidx.lifecycle.LiveData<T>! createLiveData(String[]!, boolean, java.util.concurrent.Callable<T>!);
     method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @WorkerThread public void refreshVersionsSync();
   }
 
diff --git a/room/runtime/api/restricted_current.txt b/room/runtime/api/restricted_current.txt
index 10b8084..a792dc1 100644
--- a/room/runtime/api/restricted_current.txt
+++ b/room/runtime/api/restricted_current.txt
@@ -2,7 +2,7 @@
 package androidx.room {
 
   public class DatabaseConfiguration {
-    ctor @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public DatabaseConfiguration(android.content.Context, String?, androidx.sqlite.db.SupportSQLiteOpenHelper.Factory, androidx.room.RoomDatabase.MigrationContainer, java.util.List<androidx.room.RoomDatabase.Callback>?, boolean, androidx.room.RoomDatabase.JournalMode!, java.util.concurrent.Executor, boolean, boolean, boolean, java.util.Set<java.lang.Integer>?);
+    ctor @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public DatabaseConfiguration(android.content.Context, String?, androidx.sqlite.db.SupportSQLiteOpenHelper.Factory, androidx.room.RoomDatabase.MigrationContainer, java.util.List<androidx.room.RoomDatabase.Callback>?, boolean, androidx.room.RoomDatabase.JournalMode!, java.util.concurrent.Executor, java.util.concurrent.Executor, boolean, boolean, boolean, java.util.Set<java.lang.Integer>?);
   }
 
   @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public abstract class EntityDeletionOrUpdateAdapter<T> extends androidx.room.SharedSQLiteStatement {
@@ -32,7 +32,8 @@
     ctor @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public InvalidationTracker(androidx.room.RoomDatabase!, java.lang.String...!);
     ctor @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public InvalidationTracker(androidx.room.RoomDatabase!, java.util.Map<java.lang.String,java.lang.String>!, java.util.Map<java.lang.String,java.util.Set<java.lang.String>>!, java.lang.String...!);
     method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public void addWeakObserver(androidx.room.InvalidationTracker.Observer!);
-    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public <T> androidx.lifecycle.LiveData<T>! createLiveData(String[]!, java.util.concurrent.Callable<T>!);
+    method @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public <T> androidx.lifecycle.LiveData<T>! createLiveData(String[]!, java.util.concurrent.Callable<T>!);
+    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public <T> androidx.lifecycle.LiveData<T>! createLiveData(String[]!, boolean, java.util.concurrent.Callable<T>!);
     method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @WorkerThread public void refreshVersionsSync();
   }
 
diff --git a/room/runtime/build.gradle b/room/runtime/build.gradle
index c7e7958..634aea6 100644
--- a/room/runtime/build.gradle
+++ b/room/runtime/build.gradle
@@ -32,6 +32,10 @@
 
 dependencies {
     api(project(":room:room-common"))
+    implementation fileTree(
+            dir: "${new File(project(":room:room-common-java8").buildDir, "libs")}",
+            include : "*.jar"
+    )
     api(ANDROIDX_SQLITE_FRAMEWORK)
     api(ANDROIDX_SQLITE)
     implementation(ARCH_CORE_RUNTIME)
@@ -46,6 +50,7 @@
     testImplementation(MOCKITO_CORE)
     testImplementation(ARCH_LIFECYCLE_EXTENSIONS)
     testImplementation(KOTLIN_STDLIB)
+    testImplementation(TRUTH)
 
     androidTestImplementation(JUNIT)
     androidTestImplementation(TEST_EXT_JUNIT)
@@ -56,15 +61,21 @@
     androidTestImplementation(DEXMAKER_MOCKITO, libs.exclude_bytebuddy) // DexMaker has it"s own MockMaker
 }
 
-// Used by testCompile in room-compiler
 android.libraryVariants.all { variant ->
     def name = variant.name
     def suffix = name.capitalize()
+
+    // Create jar<variant> task for testCompile in room-compiler.
     project.tasks.create(name: "jar${suffix}", type: Jar){
         dependsOn variant.javaCompileProvider.get()
         from variant.javaCompileProvider.get().destinationDir
         destinationDir new File(project.buildDir, "libJar")
     }
+
+    // Make javaCompile task depend on room-common-java8 jar task.
+    variant.javaCompileProvider.configure { task ->
+        task.dependsOn(":room:room-common-java8:jar")
+    }
 }
 
 supportLibrary {
diff --git a/room/runtime/src/main/java/androidx/room/DatabaseConfiguration.java b/room/runtime/src/main/java/androidx/room/DatabaseConfiguration.java
index bb893fd..dae3a09 100644
--- a/room/runtime/src/main/java/androidx/room/DatabaseConfiguration.java
+++ b/room/runtime/src/main/java/androidx/room/DatabaseConfiguration.java
@@ -74,6 +74,12 @@
     public final Executor queryExecutor;
 
     /**
+     * The Executor used to execute asynchronous transactions.
+     */
+    @NonNull
+    public final Executor transactionExecutor;
+
+    /**
      * If true, table invalidation in an instance of {@link RoomDatabase} is broadcast and
      * synchronized with other instances of the same {@link RoomDatabase} file, including those
      * in a separate process.
@@ -124,6 +130,7 @@
             boolean allowMainThreadQueries,
             RoomDatabase.JournalMode journalMode,
             @NonNull Executor queryExecutor,
+            @NonNull Executor transactionExecutor,
             boolean multiInstanceInvalidation,
             boolean requireMigration,
             boolean allowDestructiveMigrationOnDowngrade,
@@ -136,6 +143,7 @@
         this.allowMainThreadQueries = allowMainThreadQueries;
         this.journalMode = journalMode;
         this.queryExecutor = queryExecutor;
+        this.transactionExecutor = transactionExecutor;
         this.multiInstanceInvalidation = multiInstanceInvalidation;
         this.requireMigration = requireMigration;
         this.allowDestructiveMigrationOnDowngrade = allowDestructiveMigrationOnDowngrade;
diff --git a/room/runtime/src/main/java/androidx/room/InvalidationLiveDataContainer.java b/room/runtime/src/main/java/androidx/room/InvalidationLiveDataContainer.java
index 4168f47..e1e8155 100644
--- a/room/runtime/src/main/java/androidx/room/InvalidationLiveDataContainer.java
+++ b/room/runtime/src/main/java/androidx/room/InvalidationLiveDataContainer.java
@@ -43,8 +43,10 @@
         mDatabase = database;
     }
 
-    <T> LiveData<T> create(String[] tableNames, Callable<T> computeFunction) {
-        return new RoomTrackingLiveData<>(mDatabase, this, computeFunction, tableNames);
+    <T> LiveData<T> create(String[] tableNames, boolean inTransaction,
+            Callable<T> computeFunction) {
+        return new RoomTrackingLiveData<>(mDatabase, this, inTransaction, computeFunction,
+                tableNames);
     }
 
     void onActive(LiveData liveData) {
diff --git a/room/runtime/src/main/java/androidx/room/InvalidationTracker.java b/room/runtime/src/main/java/androidx/room/InvalidationTracker.java
index 4d3161a..9e7be31 100644
--- a/room/runtime/src/main/java/androidx/room/InvalidationTracker.java
+++ b/room/runtime/src/main/java/androidx/room/InvalidationTracker.java
@@ -559,6 +559,8 @@
      * <p>
      * Holds a strong reference to the created LiveData as long as it is active.
      *
+     * @deprecated Use {@link #createLiveData(String[], boolean, Callable)}
+     *
      * @param computeFunction The function that calculates the value
      * @param tableNames      The list of tables to observe
      * @param <T>             The return type
@@ -566,10 +568,32 @@
      * invalidates.
      * @hide
      */
+    @Deprecated
     @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
     public <T> LiveData<T> createLiveData(String[] tableNames, Callable<T> computeFunction) {
+        return createLiveData(tableNames, false, computeFunction);
+    }
+
+    /**
+     * Creates a LiveData that computes the given function once and for every other invalidation
+     * of the database.
+     * <p>
+     * Holds a strong reference to the created LiveData as long as it is active.
+     *
+     * @param tableNames      The list of tables to observe
+     * @param inTransaction   True if the computeFunction will be done in a transaction, false
+     *                        otherwise.
+     * @param computeFunction The function that calculates the value
+     * @param <T>             The return type
+     * @return A new LiveData that computes the given function when the given list of tables
+     * invalidates.
+     * @hide
+     */
+    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
+    public <T> LiveData<T> createLiveData(String[] tableNames, boolean inTransaction,
+            Callable<T> computeFunction) {
         return mInvalidationLiveDataContainer.create(
-                validateAndResolveTableNames(tableNames), computeFunction);
+                validateAndResolveTableNames(tableNames), inTransaction, computeFunction);
     }
 
     /**
diff --git a/room/runtime/src/main/java/androidx/room/RoomDatabase.java b/room/runtime/src/main/java/androidx/room/RoomDatabase.java
index a33383a..d89ac58 100644
--- a/room/runtime/src/main/java/androidx/room/RoomDatabase.java
+++ b/room/runtime/src/main/java/androidx/room/RoomDatabase.java
@@ -34,6 +34,7 @@
 import androidx.collection.SparseArrayCompat;
 import androidx.core.app.ActivityManagerCompat;
 import androidx.room.migration.Migration;
+import androidx.room.util.SneakyThrow;
 import androidx.sqlite.db.SimpleSQLiteQuery;
 import androidx.sqlite.db.SupportSQLiteDatabase;
 import androidx.sqlite.db.SupportSQLiteOpenHelper;
@@ -45,8 +46,10 @@
 import java.util.Collections;
 import java.util.HashSet;
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.Callable;
+import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.Executor;
 import java.util.concurrent.locks.Lock;
 import java.util.concurrent.locks.ReentrantReadWriteLock;
@@ -77,6 +80,7 @@
     @Deprecated
     protected volatile SupportSQLiteDatabase mDatabase;
     private Executor mQueryExecutor;
+    private Executor mTransactionExecutor;
     private SupportSQLiteOpenHelper mOpenHelper;
     private final InvalidationTracker mInvalidationTracker;
     private boolean mAllowMainThreadQueries;
@@ -121,6 +125,19 @@
         return mSuspendingTransactionId;
     }
 
+
+    private final Map<String, Object> mBackingFieldMap = new ConcurrentHashMap<>();
+
+    /**
+     * Gets the map for storing extension properties of Kotlin type.
+     *
+     * @hide
+     */
+    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+    Map<String, Object> getBackingFieldMap() {
+        return mBackingFieldMap;
+    }
+
     /**
      * Creates a RoomDatabase.
      * <p>
@@ -147,6 +164,7 @@
         }
         mCallbacks = configuration.callbacks;
         mQueryExecutor = configuration.queryExecutor;
+        mTransactionExecutor = new TransactionExecutor(configuration.transactionExecutor);
         mAllowMainThreadQueries = configuration.allowMainThreadQueries;
         mWriteAheadLoggingEnabled = wal;
         if (configuration.multiInstanceInvalidation) {
@@ -336,6 +354,14 @@
     }
 
     /**
+     * @return The Executor in use by this database for async transactions.
+     */
+    @NonNull
+    public Executor getTransactionExecutor() {
+        return mTransactionExecutor;
+    }
+
+    /**
      * Wrapper for {@link SupportSQLiteDatabase#setTransactionSuccessful()}.
      *
      * @deprecated Use {@link #runInTransaction(Runnable)}
@@ -380,7 +406,8 @@
         } catch (RuntimeException e) {
             throw e;
         } catch (Exception e) {
-            throw new RuntimeException("Exception in transaction", e);
+            SneakyThrow.reThrow(e);
+            return null; // Unreachable code, but compiler doesn't know it.
         } finally {
             endTransaction();
         }
@@ -481,6 +508,8 @@
 
         /** The Executor used to run database queries. This should be background-threaded. */
         private Executor mQueryExecutor;
+        /** The Executor used to run database transactions. This should be background-threaded. */
+        private Executor mTransactionExecutor;
         private SupportSQLiteOpenHelper.Factory mFactory;
         private boolean mAllowMainThreadQueries;
         private JournalMode mJournalMode;
@@ -598,12 +627,19 @@
          * queries and tasks, including {@code LiveData} invalidation, {@code Flowable} scheduling
          * and {@code ListenableFuture} tasks.
          * <p>
-         * When unset, a default {@code Executor} will be used. The default {@code Executor}
-         * allocates and shares threads amongst Architecture Components libraries.
+         * When both the query executor and transaction executor are unset, then a default
+         * {@code Executor} will be used. The default {@code Executor} allocates and shares threads
+         * amongst Architecture Components libraries. If the query executor is unset but a
+         * transaction executor was set, then the same {@code Executor} will be used for queries.
+         * <p>
+         * For best performance the given {@code Executor} should be bounded (max number of threads
+         * is limited).
          * <p>
          * The input {@code Executor} cannot run tasks on the UI thread.
-         *
+         **
          * @return this
+         *
+         * @see #setTransactionExecutor(Executor)
          */
         @NonNull
         public Builder<T> setQueryExecutor(@NonNull Executor executor) {
@@ -612,6 +648,32 @@
         }
 
         /**
+         * Sets the {@link Executor} that will be used to execute all non-blocking asynchronous
+         * transaction queries and tasks, including {@code LiveData} invalidation, {@code Flowable}
+         * scheduling and {@code ListenableFuture} tasks.
+         * <p>
+         * When both the transaction executor and query executor are unset, then a default
+         * {@code Executor} will be used. The default {@code Executor} allocates and shares threads
+         * amongst Architecture Components libraries. If the transaction executor is unset but a
+         * query executor was set, then the same {@code Executor} will be used for transactions.
+         * <p>
+         * If the given {@code Executor} is shared then it should be unbounded to avoid the
+         * possibility of a deadlock. Room will not use more than one thread at a time from this
+         * executor.
+         * <p>
+         * The input {@code Executor} cannot run tasks on the UI thread.
+         *
+         * @return this
+         *
+         * @see #setQueryExecutor(Executor)
+         */
+        @NonNull
+        public Builder<T> setTransactionExecutor(@NonNull Executor executor) {
+            mTransactionExecutor = executor;
+            return this;
+        }
+
+        /**
          * Sets whether table invalidation in this instance of {@link RoomDatabase} should be
          * broadcast and synchronized with other instances of the same {@link RoomDatabase},
          * including those in a separate process. In order to enable multi-instance invalidation,
@@ -741,8 +803,12 @@
                 throw new IllegalArgumentException("Must provide an abstract class that"
                         + " extends RoomDatabase");
             }
-            if (mQueryExecutor == null) {
-                mQueryExecutor = ArchTaskExecutor.getIOThreadExecutor();
+            if (mQueryExecutor == null && mTransactionExecutor == null) {
+                mQueryExecutor = mTransactionExecutor = ArchTaskExecutor.getIOThreadExecutor();
+            } else if (mQueryExecutor != null && mTransactionExecutor == null) {
+                mTransactionExecutor = mQueryExecutor;
+            } else if (mQueryExecutor == null && mTransactionExecutor != null) {
+                mQueryExecutor = mTransactionExecutor;
             }
 
             if (mMigrationStartAndEndVersions != null && mMigrationsNotRequiredFrom != null) {
@@ -763,12 +829,20 @@
                 mFactory = new FrameworkSQLiteOpenHelperFactory();
             }
             DatabaseConfiguration configuration =
-                    new DatabaseConfiguration(mContext, mName, mFactory, mMigrationContainer,
-                            mCallbacks, mAllowMainThreadQueries, mJournalMode.resolve(mContext),
+                    new DatabaseConfiguration(
+                            mContext,
+                            mName,
+                            mFactory,
+                            mMigrationContainer,
+                            mCallbacks,
+                            mAllowMainThreadQueries,
+                            mJournalMode.resolve(mContext),
                             mQueryExecutor,
+                            mTransactionExecutor,
                             mMultiInstanceInvalidation,
                             mRequireMigration,
-                            mAllowDestructiveMigrationOnDowngrade, mMigrationsNotRequiredFrom);
+                            mAllowDestructiveMigrationOnDowngrade,
+                            mMigrationsNotRequiredFrom);
             T db = Room.getGeneratedImplementation(mDatabaseClass, DB_IMPL_SUFFIX);
             db.init(configuration);
             return db;
diff --git a/room/runtime/src/main/java/androidx/room/RoomTrackingLiveData.java b/room/runtime/src/main/java/androidx/room/RoomTrackingLiveData.java
index 61ef38e..8df1014 100644
--- a/room/runtime/src/main/java/androidx/room/RoomTrackingLiveData.java
+++ b/room/runtime/src/main/java/androidx/room/RoomTrackingLiveData.java
@@ -27,6 +27,7 @@
 
 import java.util.Set;
 import java.util.concurrent.Callable;
+import java.util.concurrent.Executor;
 import java.util.concurrent.atomic.AtomicBoolean;
 
 /**
@@ -48,6 +49,9 @@
     final RoomDatabase mDatabase;
 
     @SuppressWarnings("WeakerAccess")
+    final boolean mInTransaction;
+
+    @SuppressWarnings("WeakerAccess")
     final Callable<T> mComputeFunction;
 
     private final InvalidationLiveDataContainer mContainer;
@@ -116,7 +120,7 @@
             boolean isActive = hasActiveObservers();
             if (mInvalid.compareAndSet(false, true)) {
                 if (isActive) {
-                    mDatabase.getQueryExecutor().execute(mRefreshRunnable);
+                    getQueryExecutor().execute(mRefreshRunnable);
                 }
             }
         }
@@ -125,9 +129,11 @@
     RoomTrackingLiveData(
             RoomDatabase database,
             InvalidationLiveDataContainer container,
+            boolean inTransaction,
             Callable<T> computeFunction,
             String[] tableNames) {
         mDatabase = database;
+        mInTransaction = inTransaction;
         mComputeFunction = computeFunction;
         mContainer = container;
         mObserver = new InvalidationTracker.Observer(tableNames) {
@@ -142,7 +148,7 @@
     protected void onActive() {
         super.onActive();
         mContainer.onActive(this);
-        mDatabase.getQueryExecutor().execute(mRefreshRunnable);
+        getQueryExecutor().execute(mRefreshRunnable);
     }
 
     @Override
@@ -150,4 +156,12 @@
         super.onInactive();
         mContainer.onInactive(this);
     }
+
+    Executor getQueryExecutor() {
+        if (mInTransaction) {
+            return mDatabase.getTransactionExecutor();
+        } else {
+            return mDatabase.getQueryExecutor();
+        }
+    }
 }
diff --git a/room/runtime/src/main/java/androidx/room/TransactionExecutor.java b/room/runtime/src/main/java/androidx/room/TransactionExecutor.java
new file mode 100644
index 0000000..6a6bc12
--- /dev/null
+++ b/room/runtime/src/main/java/androidx/room/TransactionExecutor.java
@@ -0,0 +1,62 @@
+/*
+ * 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.room;
+
+import androidx.annotation.NonNull;
+
+import java.util.ArrayDeque;
+import java.util.concurrent.Executor;
+
+/**
+ * Executor wrapper for performing database transactions serially.
+ * <p>
+ * Since database transactions are exclusive, this executor ensures that transactions are performed
+ * in-order and one at a time, preventing threads from blocking each other when multiple concurrent
+ * transactions are attempted.
+ */
+class TransactionExecutor implements Executor {
+
+    private final Executor mExecutor;
+    private final ArrayDeque<Runnable> mTasks = new ArrayDeque<>();
+    private Runnable mActive;
+
+    TransactionExecutor(@NonNull Executor executor) {
+        mExecutor = executor;
+    }
+
+    public synchronized void execute(final Runnable command) {
+        mTasks.offer(new Runnable() {
+            public void run() {
+                try {
+                    command.run();
+                } finally {
+                    scheduleNext();
+                }
+            }
+        });
+        if (mActive == null) {
+            scheduleNext();
+        }
+    }
+
+    @SuppressWarnings("WeakerAccess")
+    synchronized void scheduleNext() {
+        if ((mActive = mTasks.poll()) != null) {
+            mExecutor.execute(mActive);
+        }
+    }
+}
diff --git a/room/runtime/src/test/java/androidx/room/BuilderTest.java b/room/runtime/src/test/java/androidx/room/BuilderTest.java
index 0c29dd7..eabefdb 100644
--- a/room/runtime/src/test/java/androidx/room/BuilderTest.java
+++ b/room/runtime/src/test/java/androidx/room/BuilderTest.java
@@ -39,6 +39,7 @@
 import org.junit.runners.JUnit4;
 
 import java.util.List;
+import java.util.concurrent.Executor;
 
 @SuppressWarnings("ArraysAsListWithZeroOrOneArgument")
 @RunWith(JUnit4.class)
@@ -66,6 +67,41 @@
         Room.databaseBuilder(mock(Context.class), RoomDatabase.class, "  ").build();
     }
 
+    public void executors_setQueryExecutor() {
+        Executor executor = mock(Executor.class);
+
+        TestDatabase db = Room.databaseBuilder(mock(Context.class), TestDatabase.class, "foo")
+                .setQueryExecutor(executor)
+                .build();
+
+        assertThat(db.mDatabaseConfiguration.queryExecutor, is(executor));
+        assertThat(db.mDatabaseConfiguration.transactionExecutor, is(executor));
+    }
+
+    public void executors_setTransactionExecutor() {
+        Executor executor = mock(Executor.class);
+
+        TestDatabase db = Room.databaseBuilder(mock(Context.class), TestDatabase.class, "foo")
+                .setTransactionExecutor(executor)
+                .build();
+
+        assertThat(db.mDatabaseConfiguration.queryExecutor, is(executor));
+        assertThat(db.mDatabaseConfiguration.transactionExecutor, is(executor));
+    }
+
+    public void executors_setBothExecutors() {
+        Executor executor1 = mock(Executor.class);
+        Executor executor2 = mock(Executor.class);
+
+        TestDatabase db = Room.databaseBuilder(mock(Context.class), TestDatabase.class, "foo")
+                .setQueryExecutor(executor1)
+                .setTransactionExecutor(executor2)
+                .build();
+
+        assertThat(db.mDatabaseConfiguration.queryExecutor, is(executor1));
+        assertThat(db.mDatabaseConfiguration.transactionExecutor, is(executor2));
+    }
+
     @Test
     public void migration() {
         Migration m1 = new EmptyMigration(0, 1);
@@ -387,6 +423,14 @@
     }
 
     abstract static class TestDatabase extends RoomDatabase {
+
+        DatabaseConfiguration mDatabaseConfiguration;
+
+        @Override
+        public void init(@NonNull DatabaseConfiguration configuration) {
+            super.init(configuration);
+            mDatabaseConfiguration = configuration;
+        }
     }
 
     static class EmptyMigration extends Migration {
diff --git a/room/runtime/src/test/java/androidx/room/InvalidationLiveDataContainerTest.kt b/room/runtime/src/test/java/androidx/room/InvalidationLiveDataContainerTest.kt
index 97c22d1..6034b67 100644
--- a/room/runtime/src/test/java/androidx/room/InvalidationLiveDataContainerTest.kt
+++ b/room/runtime/src/test/java/androidx/room/InvalidationLiveDataContainerTest.kt
@@ -99,6 +99,7 @@
     private fun createLiveData(): LiveData<Any> {
         return container.create(
             arrayOf("a", "b"),
+            false,
             createComputeFunction<Any>()
         ) as LiveData
     }
diff --git a/room/runtime/src/test/java/androidx/room/TransactionExecutorTest.kt b/room/runtime/src/test/java/androidx/room/TransactionExecutorTest.kt
new file mode 100644
index 0000000..edda059
--- /dev/null
+++ b/room/runtime/src/test/java/androidx/room/TransactionExecutorTest.kt
@@ -0,0 +1,87 @@
+/*
+ * 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.room
+
+import com.google.common.truth.Truth.assertThat
+import org.junit.After
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import java.util.concurrent.CountDownLatch
+import java.util.concurrent.Executors
+import java.util.concurrent.TimeUnit
+
+@RunWith(JUnit4::class)
+class TransactionExecutorTest {
+
+    private val testExecutor = Executors.newCachedThreadPool()
+    private val transactionExecutor = TransactionExecutor(testExecutor)
+
+    @After
+    fun teardown() {
+        testExecutor.shutdownNow()
+    }
+
+    @Test
+    @Throws(InterruptedException::class)
+    fun testSerialExecution() {
+
+        val latch = CountDownLatch(3)
+        val runnableA = TimingRunnable(latch)
+        val runnableB = TimingRunnable(latch)
+        val runnableC = TimingRunnable(latch)
+
+        transactionExecutor.execute(runnableA)
+        transactionExecutor.execute(runnableB)
+        transactionExecutor.execute(runnableC)
+
+        // Await for the runnables to finish.
+        latch.await(1, TimeUnit.SECONDS)
+
+        // Assert that everything ran.
+        assertThat(runnableA.run).isTrue()
+        assertThat(runnableB.run).isTrue()
+        assertThat(runnableC.run).isTrue()
+
+        // Assert that runnables were run in order of submission.
+        assertThat(runnableA.start).isLessThan(runnableB.start)
+        assertThat(runnableB.start).isLessThan(runnableC.start)
+
+        // Assert that a runnable finishes before the runnable after it starts.
+        assertThat(runnableA.finish).isLessThan(runnableB.start)
+        assertThat(runnableB.finish).isLessThan(runnableC.start)
+    }
+
+    private class TimingRunnable(val latch: CountDownLatch) : Runnable {
+        var start: Long = 0
+        var finish: Long = 0
+        var run: Boolean = false
+
+        override fun run() {
+            run = true
+            start = System.nanoTime()
+            try {
+                // Sleep for a bit as if we were doing real work.
+                Thread.sleep(100)
+            } catch (e: InterruptedException) {
+                throw RuntimeException(e)
+            }
+            finish = System.nanoTime()
+            latch.countDown()
+        }
+    }
+}
\ No newline at end of file
diff --git a/room/rxjava2/api/restricted_2.1.0-alpha06.txt b/room/rxjava2/api/restricted_2.1.0-alpha06.txt
index 36290b8..6731dfa 100644
--- a/room/rxjava2/api/restricted_2.1.0-alpha06.txt
+++ b/room/rxjava2/api/restricted_2.1.0-alpha06.txt
@@ -2,8 +2,10 @@
 package androidx.room {
 
   public class RxRoom {
-    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static <T> io.reactivex.Flowable<T>! createFlowable(androidx.room.RoomDatabase!, String[]!, java.util.concurrent.Callable<T>!);
-    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static <T> io.reactivex.Observable<T>! createObservable(androidx.room.RoomDatabase!, String[]!, java.util.concurrent.Callable<T>!);
+    method @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static <T> io.reactivex.Flowable<T>! createFlowable(androidx.room.RoomDatabase!, String[]!, java.util.concurrent.Callable<T>!);
+    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static <T> io.reactivex.Flowable<T>! createFlowable(androidx.room.RoomDatabase!, boolean, String[]!, java.util.concurrent.Callable<T>!);
+    method @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static <T> io.reactivex.Observable<T>! createObservable(androidx.room.RoomDatabase!, String[]!, java.util.concurrent.Callable<T>!);
+    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static <T> io.reactivex.Observable<T>! createObservable(androidx.room.RoomDatabase!, boolean, String[]!, java.util.concurrent.Callable<T>!);
   }
 
 }
diff --git a/room/rxjava2/api/restricted_current.txt b/room/rxjava2/api/restricted_current.txt
index 36290b8..6731dfa 100644
--- a/room/rxjava2/api/restricted_current.txt
+++ b/room/rxjava2/api/restricted_current.txt
@@ -2,8 +2,10 @@
 package androidx.room {
 
   public class RxRoom {
-    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static <T> io.reactivex.Flowable<T>! createFlowable(androidx.room.RoomDatabase!, String[]!, java.util.concurrent.Callable<T>!);
-    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static <T> io.reactivex.Observable<T>! createObservable(androidx.room.RoomDatabase!, String[]!, java.util.concurrent.Callable<T>!);
+    method @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static <T> io.reactivex.Flowable<T>! createFlowable(androidx.room.RoomDatabase!, String[]!, java.util.concurrent.Callable<T>!);
+    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static <T> io.reactivex.Flowable<T>! createFlowable(androidx.room.RoomDatabase!, boolean, String[]!, java.util.concurrent.Callable<T>!);
+    method @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static <T> io.reactivex.Observable<T>! createObservable(androidx.room.RoomDatabase!, String[]!, java.util.concurrent.Callable<T>!);
+    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static <T> io.reactivex.Observable<T>! createObservable(androidx.room.RoomDatabase!, boolean, String[]!, java.util.concurrent.Callable<T>!);
   }
 
 }
diff --git a/room/rxjava2/src/main/java/androidx/room/RxRoom.java b/room/rxjava2/src/main/java/androidx/room/RxRoom.java
index 1e78572..3badde1 100644
--- a/room/rxjava2/src/main/java/androidx/room/RxRoom.java
+++ b/room/rxjava2/src/main/java/androidx/room/RxRoom.java
@@ -20,6 +20,7 @@
 
 import java.util.Set;
 import java.util.concurrent.Callable;
+import java.util.concurrent.Executor;
 
 import io.reactivex.BackpressureStrategy;
 import io.reactivex.Flowable;
@@ -97,12 +98,27 @@
      * Helper method used by generated code to bind a Callable such that it will be run in
      * our disk io thread and will automatically block null values since RxJava2 does not like null.
      *
+     * @deprecated Use {@link #createFlowable(RoomDatabase, boolean, String[], Callable)}
+     *
+     * @hide
+     */
+    @Deprecated
+    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
+    public static <T> Flowable<T> createFlowable(final RoomDatabase database,
+            final String[] tableNames, final Callable<T> callable) {
+        return createFlowable(database, false, tableNames, callable);
+    }
+
+    /**
+     * Helper method used by generated code to bind a Callable such that it will be run in
+     * our disk io thread and will automatically block null values since RxJava2 does not like null.
+     *
      * @hide
      */
     @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
     public static <T> Flowable<T> createFlowable(final RoomDatabase database,
-            final String[] tableNames, final Callable<T> callable) {
-        Scheduler scheduler = Schedulers.from(database.getQueryExecutor());
+            final boolean inTransaction, final String[] tableNames, final Callable<T> callable) {
+        Scheduler scheduler = Schedulers.from(getExecutor(database, inTransaction));
         final Maybe<T> maybe = Maybe.fromCallable(callable);
         return createFlowable(database, tableNames)
                 .subscribeOn(scheduler)
@@ -161,12 +177,27 @@
      * Helper method used by generated code to bind a Callable such that it will be run in
      * our disk io thread and will automatically block null values since RxJava2 does not like null.
      *
+     * @deprecated Use {@link #createObservable(RoomDatabase, boolean, String[], Callable)}
+     *
+     * @hide
+     */
+    @Deprecated
+    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
+    public static <T> Observable<T> createObservable(final RoomDatabase database,
+            final String[] tableNames, final Callable<T> callable) {
+        return createObservable(database, false, tableNames, callable);
+    }
+
+    /**
+     * Helper method used by generated code to bind a Callable such that it will be run in
+     * our disk io thread and will automatically block null values since RxJava2 does not like null.
+     *
      * @hide
      */
     @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
     public static <T> Observable<T> createObservable(final RoomDatabase database,
-            final String[] tableNames, final Callable<T> callable) {
-        Scheduler scheduler = Schedulers.from(database.getQueryExecutor());
+            final boolean inTransaction, final String[] tableNames, final Callable<T> callable) {
+        Scheduler scheduler = Schedulers.from(getExecutor(database, inTransaction));
         final Maybe<T> maybe = Maybe.fromCallable(callable);
         return createObservable(database, tableNames)
                 .subscribeOn(scheduler)
@@ -180,6 +211,14 @@
                 });
     }
 
+    private static Executor getExecutor(RoomDatabase database, boolean inTransaction) {
+        if (inTransaction) {
+            return database.getTransactionExecutor();
+        } else {
+            return database.getQueryExecutor();
+        }
+    }
+
     /** @deprecated This type should not be instantiated as it contains only static methods. */
     @Deprecated
     @SuppressWarnings("PrivateConstructorForUtilityClass")
diff --git a/room/rxjava2/src/test/java/androidx/room/RxRoomTest.java b/room/rxjava2/src/test/java/androidx/room/RxRoomTest.java
index cc96b50..dd5bebc 100644
--- a/room/rxjava2/src/test/java/androidx/room/RxRoomTest.java
+++ b/room/rxjava2/src/test/java/androidx/room/RxRoomTest.java
@@ -167,7 +167,7 @@
         final AtomicReference<String> value = new AtomicReference<>(null);
         String[] tables = {"a", "b"};
         Set<String> tableSet = new HashSet<>(Arrays.asList(tables));
-        final Flowable<String> flowable = RxRoom.createFlowable(mDatabase, tables,
+        final Flowable<String> flowable = RxRoom.createFlowable(mDatabase, false, tables,
                 new Callable<String>() {
                     @Override
                     public String call() throws Exception {
@@ -201,7 +201,7 @@
         final AtomicReference<String> value = new AtomicReference<>(null);
         String[] tables = {"a", "b"};
         Set<String> tableSet = new HashSet<>(Arrays.asList(tables));
-        final Observable<String> flowable = RxRoom.createObservable(mDatabase, tables,
+        final Observable<String> flowable = RxRoom.createObservable(mDatabase, false, tables,
                 new Callable<String>() {
                     @Override
                     public String call() throws Exception {
@@ -232,7 +232,7 @@
 
     @Test
     public void exception_Flowable() throws Exception {
-        final Flowable<String> flowable = RxRoom.createFlowable(mDatabase, new String[]{"a"},
+        final Flowable<String> flowable = RxRoom.createFlowable(mDatabase, false, new String[]{"a"},
                 new Callable<String>() {
                     @Override
                     public String call() throws Exception {
@@ -248,7 +248,8 @@
 
     @Test
     public void exception_Observable() throws Exception {
-        final Observable<String> flowable = RxRoom.createObservable(mDatabase, new String[]{"a"},
+        final Observable<String> flowable = RxRoom.createObservable(mDatabase, false,
+                new String[]{"a"},
                 new Callable<String>() {
                     @Override
                     public String call() throws Exception {
diff --git a/room/testing/src/main/java/androidx/room/testing/MigrationTestHelper.java b/room/testing/src/main/java/androidx/room/testing/MigrationTestHelper.java
index c0e1c4b..06b5a6b 100644
--- a/room/testing/src/main/java/androidx/room/testing/MigrationTestHelper.java
+++ b/room/testing/src/main/java/androidx/room/testing/MigrationTestHelper.java
@@ -158,6 +158,7 @@
                 true,
                 RoomDatabase.JournalMode.TRUNCATE,
                 ArchTaskExecutor.getIOThreadExecutor(),
+                ArchTaskExecutor.getIOThreadExecutor(),
                 false,
                 true,
                 false,
@@ -215,6 +216,7 @@
                 true,
                 RoomDatabase.JournalMode.TRUNCATE,
                 ArchTaskExecutor.getIOThreadExecutor(),
+                ArchTaskExecutor.getIOThreadExecutor(),
                 false,
                 true,
                 false,
diff --git a/samples/Support4Demos/src/main/AndroidManifest.xml b/samples/Support4Demos/src/main/AndroidManifest.xml
index 52d24be..295caf0 100644
--- a/samples/Support4Demos/src/main/AndroidManifest.xml
+++ b/samples/Support4Demos/src/main/AndroidManifest.xml
@@ -136,14 +136,6 @@
             </intent-filter>
         </activity>
 
-        <activity android:name=".app.FragmentNestingTabsSupport"
-                android:label="@string/fragment_nesting_tabs_support">
-            <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="com.example.android.supportv4.SUPPORT4_SAMPLE_CODE" />
-            </intent-filter>
-        </activity>
-
         <activity android:name=".app.FragmentRetainInstanceSupport"
                 android:label="@string/fragment_retain_instance_support">
             <intent-filter>
@@ -168,22 +160,6 @@
             </intent-filter>
         </activity>
 
-        <activity android:name=".app.FragmentTabs"
-                android:label="@string/fragment_tabs">
-            <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="com.example.android.supportv4.SUPPORT4_SAMPLE_CODE" />
-            </intent-filter>
-        </activity>
-
-        <activity android:name=".app.FragmentTabsPager"
-                android:label="@string/fragment_tabs_pager">
-            <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="com.example.android.supportv4.SUPPORT4_SAMPLE_CODE" />
-            </intent-filter>
-        </activity>
-
         <activity android:name=".app.FragmentPagerSupport"
                 android:label="@string/fragment_pager_support">
             <intent-filter>
diff --git a/samples/Support4Demos/src/main/java/com/example/android/supportv4/app/FragmentNestingTabsSupport.java b/samples/Support4Demos/src/main/java/com/example/android/supportv4/app/FragmentNestingTabsSupport.java
index 0991eb8..61b37b7 100644
--- a/samples/Support4Demos/src/main/java/com/example/android/supportv4/app/FragmentNestingTabsSupport.java
+++ b/samples/Support4Demos/src/main/java/com/example/android/supportv4/app/FragmentNestingTabsSupport.java
@@ -16,33 +16,4 @@
 package com.example.android.supportv4.app;
 
 //BEGIN_INCLUDE(complete)
-
-import android.os.Bundle;
-
-import androidx.fragment.app.FragmentActivity;
-import androidx.fragment.app.FragmentTabHost;
-
-import com.example.android.supportv4.R;
-
-public class FragmentNestingTabsSupport extends FragmentActivity {
-    private FragmentTabHost mTabHost;
-
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-
-        mTabHost = new FragmentTabHost(this);
-        setContentView(mTabHost);
-        mTabHost.setup(this, getSupportFragmentManager(), R.id.fragment1);
-
-        mTabHost.addTab(mTabHost.newTabSpec("menus").setIndicator("Menus"),
-                FragmentMenuFragmentSupport.class, null);
-        mTabHost.addTab(mTabHost.newTabSpec("contacts").setIndicator("Contacts"),
-                LoaderCursorSupport.CursorLoaderListFragment.class, null);
-        mTabHost.addTab(mTabHost.newTabSpec("stack").setIndicator("Stack"),
-                FragmentStackFragmentSupport.class, null);
-        mTabHost.addTab(mTabHost.newTabSpec("tabs").setIndicator("Tabs"),
-                FragmentTabsFragmentSupport.class, null);
-    }
-}
 //END_INCLUDE(complete)
diff --git a/samples/Support4Demos/src/main/java/com/example/android/supportv4/app/FragmentTabs.java b/samples/Support4Demos/src/main/java/com/example/android/supportv4/app/FragmentTabs.java
index c4bbd93..25cfea4 100644
--- a/samples/Support4Demos/src/main/java/com/example/android/supportv4/app/FragmentTabs.java
+++ b/samples/Support4Demos/src/main/java/com/example/android/supportv4/app/FragmentTabs.java
@@ -16,37 +16,4 @@
 package com.example.android.supportv4.app;
 
 //BEGIN_INCLUDE(complete)
-
-import android.os.Bundle;
-
-import androidx.fragment.app.FragmentActivity;
-import androidx.fragment.app.FragmentTabHost;
-
-import com.example.android.supportv4.R;
-
-/**
- * This demonstrates how you can implement switching between the tabs of a
- * TabHost through fragments, using FragmentTabHost.
- */
-public class FragmentTabs extends FragmentActivity {
-    private FragmentTabHost mTabHost;
-
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-
-        setContentView(R.layout.fragment_tabs);
-        mTabHost = (FragmentTabHost)findViewById(android.R.id.tabhost);
-        mTabHost.setup(this, getSupportFragmentManager(), R.id.realtabcontent);
-
-        mTabHost.addTab(mTabHost.newTabSpec("simple").setIndicator("Simple"),
-                FragmentStackSupport.CountingFragment.class, null);
-        mTabHost.addTab(mTabHost.newTabSpec("contacts").setIndicator("Contacts"),
-                LoaderCursorSupport.CursorLoaderListFragment.class, null);
-        mTabHost.addTab(mTabHost.newTabSpec("custom").setIndicator("Custom"),
-                LoaderCustomSupport.AppListFragment.class, null);
-        mTabHost.addTab(mTabHost.newTabSpec("throttle").setIndicator("Throttle"),
-                LoaderThrottleSupport.ThrottledLoaderListFragment.class, null);
-    }
-}
 //END_INCLUDE(complete)
diff --git a/samples/Support4Demos/src/main/java/com/example/android/supportv4/app/FragmentTabsFragmentSupport.java b/samples/Support4Demos/src/main/java/com/example/android/supportv4/app/FragmentTabsFragmentSupport.java
index 06a132b..61b37b7 100644
--- a/samples/Support4Demos/src/main/java/com/example/android/supportv4/app/FragmentTabsFragmentSupport.java
+++ b/samples/Support4Demos/src/main/java/com/example/android/supportv4/app/FragmentTabsFragmentSupport.java
@@ -16,42 +16,4 @@
 package com.example.android.supportv4.app;
 
 //BEGIN_INCLUDE(complete)
-
-import android.os.Bundle;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-
-import androidx.fragment.app.Fragment;
-import androidx.fragment.app.FragmentTabHost;
-
-import com.example.android.supportv4.R;
-
-public class FragmentTabsFragmentSupport extends Fragment {
-    private FragmentTabHost mTabHost;
-
-    @Override
-    public View onCreateView(LayoutInflater inflater, ViewGroup container,
-            Bundle savedInstanceState) {
-        mTabHost = new FragmentTabHost(getActivity());
-        mTabHost.setup(getActivity(), getChildFragmentManager(), R.id.fragment1);
-
-        mTabHost.addTab(mTabHost.newTabSpec("simple").setIndicator("Simple"),
-                FragmentStackSupport.CountingFragment.class, null);
-        mTabHost.addTab(mTabHost.newTabSpec("contacts").setIndicator("Contacts"),
-                LoaderCursorSupport.CursorLoaderListFragment.class, null);
-        mTabHost.addTab(mTabHost.newTabSpec("custom").setIndicator("Custom"),
-                LoaderCustomSupport.AppListFragment.class, null);
-        mTabHost.addTab(mTabHost.newTabSpec("throttle").setIndicator("Throttle"),
-                LoaderThrottleSupport.ThrottledLoaderListFragment.class, null);
-
-        return mTabHost;
-    }
-
-    @Override
-    public void onDestroyView() {
-        super.onDestroyView();
-        mTabHost = null;
-    }
-}
 //END_INCLUDE(complete)
diff --git a/samples/Support4Demos/src/main/res/layout/fragment_tabs.xml b/samples/Support4Demos/src/main/res/layout/fragment_tabs.xml
deleted file mode 100644
index faea9cc..0000000
--- a/samples/Support4Demos/src/main/res/layout/fragment_tabs.xml
+++ /dev/null
@@ -1,54 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/* //device/apps/common/assets/res/layout/tab_content.xml
-**
-** Copyright 2011, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<!-- BEGIN_INCLUDE(complete) -->
-<androidx.fragment.app.FragmentTabHost
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@android:id/tabhost"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent">
-
-    <LinearLayout
-        android:orientation="vertical"
-        android:layout_width="match_parent"
-        android:layout_height="match_parent">
-
-        <TabWidget
-            android:id="@android:id/tabs"
-            android:orientation="horizontal"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:layout_weight="0"/>
-
-        <FrameLayout
-            android:id="@android:id/tabcontent"
-            android:layout_width="0dp"
-            android:layout_height="0dp"
-            android:layout_weight="0"/>
-
-        <FrameLayout
-            android:id="@+id/realtabcontent"
-            android:layout_width="match_parent"
-            android:layout_height="0dp"
-            android:layout_weight="1"/>
-
-    </LinearLayout>
-</androidx.fragment.app.FragmentTabHost>
-<!-- END_INCLUDE(complete) -->
diff --git a/samples/SupportCarDemos/src/main/java/com/example/androidx/car/SwitchListItemActivity.java b/samples/SupportCarDemos/src/main/java/com/example/androidx/car/SwitchListItemActivity.java
index 7b9a95a..501b20e 100644
--- a/samples/SupportCarDemos/src/main/java/com/example/androidx/car/SwitchListItemActivity.java
+++ b/samples/SupportCarDemos/src/main/java/com/example/androidx/car/SwitchListItemActivity.java
@@ -18,6 +18,7 @@
 
 import android.app.Activity;
 import android.content.Context;
+import android.graphics.drawable.Icon;
 import android.os.Bundle;
 import android.widget.CompoundButton;
 import android.widget.Toast;
@@ -103,6 +104,24 @@
             mItems.add(item);
 
             item = new SwitchListItem(mContext);
+            item.setPrimaryActionIcon(
+                    Icon.createWithResource(mContext, android.R.drawable.sym_def_app_icon),
+                    SwitchListItem.PRIMARY_ACTION_ICON_SIZE_SMALL);
+            item.setTitle("Switch with Icon");
+            item.setBody(longText);
+            item.setSwitchOnCheckedChangeListener(mListener);
+            mItems.add(item);
+
+            item = new SwitchListItem(mContext);
+            item.setTitle("Switch with Drawable");
+            item.setPrimaryActionIcon(
+                    mContext.getDrawable(android.R.drawable.sym_def_app_icon),
+                    SwitchListItem.PRIMARY_ACTION_ICON_SIZE_SMALL);
+            item.setBody(longText);
+            item.setSwitchOnCheckedChangeListener(mListener);
+            mItems.add(item);
+
+            item = new SwitchListItem(mContext);
             item.setTitle("Clicking item toggles switch");
             item.setClickable(true);
             item.setSwitchOnCheckedChangeListener(mListener);
diff --git a/samples/SupportMediaDemos/src/main/java/com/example/androidx/media/VideoPlayerActivity.java b/samples/SupportMediaDemos/src/main/java/com/example/androidx/media/VideoPlayerActivity.java
index 11e0280..0d5594dd 100644
--- a/samples/SupportMediaDemos/src/main/java/com/example/androidx/media/VideoPlayerActivity.java
+++ b/samples/SupportMediaDemos/src/main/java/com/example/androidx/media/VideoPlayerActivity.java
@@ -128,7 +128,7 @@
         if (intent == null || (videoUri = intent.getData()) == null || !videoUri.isAbsolute()) {
             errorString = "Invalid intent";
         } else {
-            UriMediaItem mediaItem = new UriMediaItem.Builder(this, videoUri).build();
+            UriMediaItem mediaItem = new UriMediaItem.Builder(videoUri).build();
             mVideoView.setMediaItem(mediaItem);
 
             mMediaControlView = new MediaControlView(this);
diff --git a/settings.gradle b/settings.gradle
index 7976ae5..4e26b66 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -147,6 +147,7 @@
 includeProject(":room:integration-tests:room-testapp-kotlin", "room/integration-tests/kotlintestapp")
 includeProject(":room:room-benchmark", "room/benchmark")
 includeProject(":room:room-common", "room/common")
+includeProject(":room:room-common-java8", "room/common-java8")
 includeProject(":room:room-compiler", "room/compiler")
 includeProject(":room:room-guava", "room/guava")
 includeProject(":room:room-ktx", "room/ktx")
diff --git a/slices/view/src/main/java/androidx/slice/widget/RowView.java b/slices/view/src/main/java/androidx/slice/widget/RowView.java
index aa64268..76ea8c1 100644
--- a/slices/view/src/main/java/androidx/slice/widget/RowView.java
+++ b/slices/view/src/main/java/androidx/slice/widget/RowView.java
@@ -144,10 +144,15 @@
     Handler mHandler;
     @SuppressWarnings("WeakerAccess") /* synthetic access */
     long mLastSentRangeUpdate;
+    // TODO: mRangeValue is in 0..(mRangeMaxValue-mRangeMinValue) at initialization, and in
+    //       mRangeMinValue..mRangeMaxValue after user interaction. As far as I know, this doesn't
+    //       cause any incorrect behavior, but it is confusing and error-prone.
     @SuppressWarnings("WeakerAccess") /* synthetic access */
     int mRangeValue;
     @SuppressWarnings("WeakerAccess") /* synthetic access */
     int mRangeMinValue;
+    @SuppressWarnings("WeakerAccess") /* synthetic access */
+    int mRangeMaxValue;
     private SliceItem mRangeItem;
 
     private int mImageSize;
@@ -427,12 +432,10 @@
             if (mRowAction != null) {
                 setViewClickable(mRootView, true);
             }
+            mRangeItem = range;
             if (!skipSliderUpdate) {
-                determineRangeValues(range);
-                addRange(range);
-            } else {
-                // Even if we're skipping the update, we should still update the range item
-                mRangeItem = range;
+                setRangeBounds();
+                addRange();
             }
             return;
         }
@@ -602,14 +605,7 @@
         }
     }
 
-    private void determineRangeValues(SliceItem rangeItem) {
-        if (rangeItem == null) {
-            mRangeMinValue = 0;
-            mRangeValue = 0;
-            return;
-        }
-        mRangeItem = rangeItem;
-
+    private void setRangeBounds() {
         SliceItem min = SliceQuery.findSubtype(mRangeItem, FORMAT_INT, SUBTYPE_MIN);
         int minValue = 0;
         if (min != null) {
@@ -617,18 +613,27 @@
         }
         mRangeMinValue = minValue;
 
-        SliceItem progress = SliceQuery.findSubtype(mRangeItem, FORMAT_INT, SUBTYPE_VALUE);
-        if (progress != null) {
-            mRangeValue = progress.getInt() - minValue;
+        SliceItem max = SliceQuery.findSubtype(mRangeItem, FORMAT_INT, SUBTYPE_MAX);
+        int maxValue = 100;  // TODO: This default shouldn't be hardcoded here.
+        if (max != null) {
+            maxValue = max.getInt();
         }
+        mRangeMaxValue = maxValue;
+
+        SliceItem progress = SliceQuery.findSubtype(mRangeItem, FORMAT_INT, SUBTYPE_VALUE);
+        int progressValue = 0;
+        if (progress != null) {
+            progressValue = progress.getInt() - minValue;
+        }
+        mRangeValue = progressValue;
     }
 
-    private void addRange(final SliceItem range) {
+    private void addRange() {
         if (mHandler == null) {
             mHandler = new Handler();
         }
 
-        final boolean isSeekBar = FORMAT_ACTION.equals(range.getFormat());
+        final boolean isSeekBar = FORMAT_ACTION.equals(mRangeItem.getFormat());
         final ProgressBar progressBar = isSeekBar
                 ? new SeekBar(getContext())
                 : new ProgressBar(getContext(), null, android.R.attr.progressBarStyleHorizontal);
@@ -637,10 +642,9 @@
             DrawableCompat.setTint(progressDrawable, mTintColor);
             progressBar.setProgressDrawable(progressDrawable);
         }
-        SliceItem max = SliceQuery.findSubtype(range, FORMAT_INT, SUBTYPE_MAX);
-        if (max != null) {
-            progressBar.setMax(max.getInt() - mRangeMinValue);
-        }
+        // N.B. We don't use progressBar.setMin because it doesn't work properly in backcompat
+        //      and/or sliders.
+        progressBar.setMax(mRangeMaxValue - mRangeMinValue);
         progressBar.setProgress(mRangeValue);
         progressBar.setVisibility(View.VISIBLE);
         addView(progressBar);
@@ -664,21 +668,23 @@
     }
 
     void sendSliderValue() {
-        if (mRangeItem != null) {
-            try {
-                mLastSentRangeUpdate = System.currentTimeMillis();
-                mRangeItem.fireAction(getContext(),
-                        new Intent().addFlags(Intent.FLAG_RECEIVER_FOREGROUND)
-                                .putExtra(EXTRA_RANGE_VALUE, mRangeValue));
-                if (mObserver != null) {
-                    EventInfo info = new EventInfo(getMode(), ACTION_TYPE_SLIDER, ROW_TYPE_SLIDER,
-                            mRowIndex);
-                    info.state = mRangeValue;
-                    mObserver.onSliceAction(info, mRangeItem);
-                }
-            } catch (CanceledException e) {
-                Log.e(TAG, "PendingIntent for slice cannot be sent", e);
+        if (mRangeItem == null) {
+            return;
+        }
+
+        try {
+            mLastSentRangeUpdate = System.currentTimeMillis();
+            mRangeItem.fireAction(getContext(),
+                    new Intent().addFlags(Intent.FLAG_RECEIVER_FOREGROUND)
+                            .putExtra(EXTRA_RANGE_VALUE, mRangeValue));
+            if (mObserver != null) {
+                EventInfo info = new EventInfo(getMode(), ACTION_TYPE_SLIDER, ROW_TYPE_SLIDER,
+                        mRowIndex);
+                info.state = mRangeValue;
+                mObserver.onSliceAction(info, mRangeItem);
             }
+        } catch (CanceledException e) {
+            Log.e(TAG, "PendingIntent for slice cannot be sent", e);
         }
     }
 
@@ -904,6 +910,7 @@
         mRangeHasPendingUpdate = false;
         mRangeItem = null;
         mRangeMinValue = 0;
+        mRangeMaxValue = 0;
         mRangeValue = 0;
         mLastSentRangeUpdate = 0;
         mHandler = null;
diff --git a/testutils/src/main/java/androidx/testutils/FragmentActivityUtils.java b/testutils/src/main/java/androidx/testutils/FragmentActivityUtils.java
index 22d0edd..615d1a2 100644
--- a/testutils/src/main/java/androidx/testutils/FragmentActivityUtils.java
+++ b/testutils/src/main/java/androidx/testutils/FragmentActivityUtils.java
@@ -90,8 +90,8 @@
             ActivityTestRule<? extends RecreatedActivity> rule, final T activity)
             throws InterruptedException {
         // Now switch the orientation
-        RecreatedActivity.sResumed = new CountDownLatch(1);
-        RecreatedActivity.sDestroyed = new CountDownLatch(1);
+        RecreatedActivity.setResumedLatch(new CountDownLatch(1));
+        RecreatedActivity.setDestroyedLatch(new CountDownLatch(1));
 
         runOnUiThreadRethrow(rule, new Runnable() {
             @Override
@@ -99,9 +99,9 @@
                 activity.recreate();
             }
         });
-        assertTrue(RecreatedActivity.sResumed.await(1, TimeUnit.SECONDS));
-        assertTrue(RecreatedActivity.sDestroyed.await(1, TimeUnit.SECONDS));
-        T newActivity = (T) RecreatedActivity.sActivity;
+        assertTrue(RecreatedActivity.getResumedLatch().await(1, TimeUnit.SECONDS));
+        assertTrue(RecreatedActivity.getDestroyedLatch().await(1, TimeUnit.SECONDS));
+        T newActivity = (T) RecreatedActivity.getActivity();
 
         waitForExecution(rule);
 
diff --git a/testutils/src/main/java/androidx/testutils/RecreatedActivity.java b/testutils/src/main/java/androidx/testutils/RecreatedActivity.java
deleted file mode 100644
index 34326a48..0000000
--- a/testutils/src/main/java/androidx/testutils/RecreatedActivity.java
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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.testutils;
-
-import android.os.Bundle;
-
-import androidx.annotation.Nullable;
-import androidx.fragment.app.FragmentActivity;
-import androidx.test.rule.ActivityTestRule;
-
-import java.util.concurrent.CountDownLatch;
-
-/**
- * Extension of {@link FragmentActivity} that keeps track of when it is recreated.
- * In order to use this class, have your activity extend it and call
- * {@link FragmentActivityUtils#recreateActivity(ActivityTestRule, RecreatedActivity)} API.
- */
-public class RecreatedActivity extends FragmentActivity {
-    // These must be cleared after each test using clearState()
-    public static RecreatedActivity sActivity;
-    public static CountDownLatch sResumed;
-    public static CountDownLatch sDestroyed;
-
-    static void clearState() {
-        sActivity = null;
-        sResumed = null;
-        sDestroyed = null;
-    }
-
-    @Override
-    protected void onCreate(@Nullable Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        sActivity = this;
-    }
-
-    @Override
-    protected void onResume() {
-        super.onResume();
-        if (sResumed != null) {
-            sResumed.countDown();
-        }
-    }
-
-    @Override
-    protected void onDestroy() {
-        super.onDestroy();
-        if (sDestroyed != null) {
-            sDestroyed.countDown();
-        }
-    }
-}
diff --git a/testutils/src/main/java/androidx/testutils/RecreatedActivity.kt b/testutils/src/main/java/androidx/testutils/RecreatedActivity.kt
new file mode 100644
index 0000000..22b74aa
--- /dev/null
+++ b/testutils/src/main/java/androidx/testutils/RecreatedActivity.kt
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.testutils
+
+import android.os.Bundle
+import androidx.fragment.app.FragmentActivity
+import java.util.concurrent.CountDownLatch
+
+/**
+ * Extension of [FragmentActivity] that keeps track of when it is recreated.
+ * In order to use this class, have your activity extend it and call
+ * [FragmentActivityUtils.recreateActivity] API.
+ */
+open class RecreatedActivity : FragmentActivity() {
+
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+        activity = this
+    }
+
+    override fun onResume() {
+        super.onResume()
+        resumedLatch?.countDown()
+    }
+
+    override fun onDestroy() {
+        super.onDestroy()
+        destroyedLatch?.countDown()
+    }
+
+    companion object {
+        // These must be cleared after each test using clearState()
+        @JvmStatic
+        var activity: RecreatedActivity? = null
+        @JvmStatic
+        var resumedLatch: CountDownLatch? = null
+        @JvmStatic
+        var destroyedLatch: CountDownLatch? = null
+
+        @JvmStatic
+        fun clearState() {
+            activity = null
+            resumedLatch = null
+            destroyedLatch = null
+        }
+    }
+}
diff --git a/textclassifier/api/1.0.0-alpha03.txt b/textclassifier/api/1.0.0-alpha03.txt
index 043c858..c37c7d2 100644
--- a/textclassifier/api/1.0.0-alpha03.txt
+++ b/textclassifier/api/1.0.0-alpha03.txt
@@ -1,6 +1,10 @@
 // Signature format: 3.0
 package androidx.textclassifier {
 
+  public final class ExtrasUtils {
+    method public static java.util.Locale? getTopLanguage(android.content.Intent?);
+  }
+
   public final class TextClassification {
     method public static androidx.textclassifier.TextClassification createFromBundle(android.os.Bundle);
     method public java.util.List<androidx.core.app.RemoteActionCompat> getActions();
diff --git a/textclassifier/api/current.txt b/textclassifier/api/current.txt
index 043c858..c37c7d2 100644
--- a/textclassifier/api/current.txt
+++ b/textclassifier/api/current.txt
@@ -1,6 +1,10 @@
 // Signature format: 3.0
 package androidx.textclassifier {
 
+  public final class ExtrasUtils {
+    method public static java.util.Locale? getTopLanguage(android.content.Intent?);
+  }
+
   public final class TextClassification {
     method public static androidx.textclassifier.TextClassification createFromBundle(android.os.Bundle);
     method public java.util.List<androidx.core.app.RemoteActionCompat> getActions();
diff --git a/textclassifier/src/androidTest/java/androidx/textclassifier/ExtrasUtilsTest.java b/textclassifier/src/androidTest/java/androidx/textclassifier/ExtrasUtilsTest.java
new file mode 100644
index 0000000..17511e0
--- /dev/null
+++ b/textclassifier/src/androidTest/java/androidx/textclassifier/ExtrasUtilsTest.java
@@ -0,0 +1,57 @@
+/*
+ * 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.textclassifier;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Intent;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Tests for {@link ExtrasUtils}.
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class ExtrasUtilsTest {
+
+    @Test
+    public void testGetTopLanguage() {
+        final Intent intent = ExtrasUtils.buildFakeTextClassifierIntent("ja", "en");
+        assertThat(ExtrasUtils.getTopLanguage(intent).getLanguage()).isEqualTo("ja");
+    }
+
+    @Test
+    public void testGetTopLanguage_differentLanguage() {
+        final Intent intent = ExtrasUtils.buildFakeTextClassifierIntent("de");
+        assertThat(ExtrasUtils.getTopLanguage(intent).getLanguage()).isEqualTo("de");
+    }
+
+    @Test
+    public void testGetTopLanguage_nullLanguageBundle() {
+        assertThat(ExtrasUtils.getTopLanguage(new Intent())).isNull();
+    }
+
+    @Test
+    public void testGetTopLanguage_null() {
+        assertThat(ExtrasUtils.getTopLanguage(null)).isNull();
+    }
+}
diff --git a/textclassifier/src/main/java/androidx/textclassifier/BundleUtils.java b/textclassifier/src/main/java/androidx/textclassifier/BundleUtils.java
index 1906b4c..52704ec 100644
--- a/textclassifier/src/main/java/androidx/textclassifier/BundleUtils.java
+++ b/textclassifier/src/main/java/androidx/textclassifier/BundleUtils.java
@@ -25,6 +25,7 @@
 import androidx.collection.ArrayMap;
 import androidx.core.app.RemoteActionCompat;
 import androidx.core.os.LocaleListCompat;
+import androidx.versionedparcelable.ParcelUtils;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -80,30 +81,13 @@
     /** Serializes a list of actions to a bundle, or clears it if null is passed. */
     static void putRemoteActionList(
             @NonNull Bundle bundle, @NonNull String key,
-            @Nullable List<RemoteActionCompat> actions) {
-        if (actions == null) {
-            bundle.remove(key);
-            return;
-        }
-        final ArrayList<Bundle> actionBundles = new ArrayList<>(actions.size());
-        for (RemoteActionCompat action : actions) {
-            actionBundles.add(action.toBundle());
-        }
-        bundle.putParcelableArrayList(key, actionBundles);
+            @NonNull List<RemoteActionCompat> actions) {
+        ParcelUtils.putVersionedParcelableList(bundle, key, actions);
     }
 
-    /** @throws IllegalArgumentException if key can't be found in the bundle */
     static List<RemoteActionCompat> getRemoteActionListOrThrow(
             @NonNull Bundle bundle, @NonNull String key) {
-        final List<Bundle> actionBundles = bundle.getParcelableArrayList(key);
-        if (actionBundles == null) {
-            throw new IllegalArgumentException("Missing " + key);
-        }
-        final List<RemoteActionCompat> actions = new ArrayList<>(actionBundles.size());
-        for (Bundle actionBundle : actionBundles) {
-            actions.add(RemoteActionCompat.createFromBundle(actionBundle));
-        }
-        return actions;
+        return ParcelUtils.getVersionedParcelableList(bundle, key);
     }
 
     /** Serializes a list of TextLinks to a bundle, or clears it if null is passed. */
diff --git a/textclassifier/src/main/java/androidx/textclassifier/ExtrasUtils.java b/textclassifier/src/main/java/androidx/textclassifier/ExtrasUtils.java
new file mode 100644
index 0000000..26c45c6
--- /dev/null
+++ b/textclassifier/src/main/java/androidx/textclassifier/ExtrasUtils.java
@@ -0,0 +1,114 @@
+/*
+ * 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.textclassifier;
+
+import android.content.Intent;
+import android.os.Bundle;
+import android.util.Log;
+
+import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
+import androidx.core.os.LocaleListCompat;
+
+import java.util.Locale;
+
+/**
+ * Utilities for inserting/retrieving data into/from textclassifier related results and intents.
+ */
+public final class ExtrasUtils {
+
+    private static final String TAG = "ExtrasUtils";
+
+    private static final String EXTRA_FROM_TEXT_CLASSIFIER =
+            "android.view.textclassifier.extra.FROM_TEXT_CLASSIFIER";
+    private static final String ENTITY_TYPE = "entity-type";
+    private static final String SCORE = "score";
+    private static final String TEXT_LANGUAGES = "text-languages";
+
+    private ExtrasUtils() {}
+
+    /**
+     * Returns the highest scoring language found in the textclassifier extras in the intent.
+     * This may return null if the data could not be found.
+     *
+     * @param intent the intent used to start the activity.
+     * @see android.app.Activity#getIntent()
+     */
+    @Nullable
+    public static Locale getTopLanguage(@Nullable Intent intent) {
+        try {
+            // NOTE: This is (and should be) a copy of the related platform code.
+            // It is hard to test this code returns something on a given platform because we can't
+            // guarantee the TextClassifier implementation that will be used to send the intent.
+            // Depend on the platform tests instead and avoid this code running out of sync with
+            // what is expected of each platform. Note that the code may differ from platform to
+            // platform but that will be a bad idea as it will be hard to manage.
+            // TODO: Include a "put" counterpart of this method so that other TextClassifier
+            // implementations may use it to put language data into the generated intent in a way
+            // that this method can retrieve it.
+            if (intent == null) {
+                return null;
+            }
+            final Bundle tcBundle = intent.getBundleExtra(EXTRA_FROM_TEXT_CLASSIFIER);
+            if (tcBundle == null) {
+                return null;
+            }
+            final Bundle textLanguagesExtra = tcBundle.getBundle(TEXT_LANGUAGES);
+            if (textLanguagesExtra == null) {
+                return null;
+            }
+            final String[] languages = textLanguagesExtra.getStringArray(ENTITY_TYPE);
+            final float[] scores = textLanguagesExtra.getFloatArray(SCORE);
+            if (languages == null || scores == null
+                    || languages.length == 0 || languages.length != scores.length) {
+                return null;
+            }
+            int highestScoringIndex = 0;
+            for (int i = 1; i < languages.length; i++) {
+                if (scores[highestScoringIndex] < scores[i]) {
+                    highestScoringIndex = i;
+                }
+            }
+            final LocaleListCompat localeList =
+                    LocaleListCompat.forLanguageTags(languages[highestScoringIndex]);
+            return localeList.isEmpty() ? null : localeList.get(0);
+        } catch (Throwable t) {
+            // Prevent this method from crashing the process.
+            Log.e(TAG, "Error retrieving language information from textclassifier intent", t);
+            return null;
+        }
+    }
+
+    /**
+     * Returns a fake TextClassifier generated intent for testing purposes.
+     * @param languages ordered list of languages for the classified text
+     */
+    @VisibleForTesting
+    static Intent buildFakeTextClassifierIntent(String... languages) {
+        final float[] scores = new float[languages.length];
+        float scoresLeft = 1f;
+        for (int i = 0; i < scores.length; i++) {
+            scores[i] = scoresLeft /= 2;
+        }
+        final Bundle textLanguagesExtra = new Bundle();
+        textLanguagesExtra.putStringArray(ENTITY_TYPE, languages);
+        textLanguagesExtra.putFloatArray(SCORE, scores);
+        final Bundle tcBundle = new Bundle();
+        tcBundle.putBundle(TEXT_LANGUAGES, textLanguagesExtra);
+        return new Intent(Intent.ACTION_VIEW).putExtra(EXTRA_FROM_TEXT_CLASSIFIER, tcBundle);
+    }
+}
diff --git a/versionedparcelable/api/1.1.0-alpha02.txt b/versionedparcelable/api/1.1.0-alpha02.txt
index a53654a..0ca14c1 100644
--- a/versionedparcelable/api/1.1.0-alpha02.txt
+++ b/versionedparcelable/api/1.1.0-alpha02.txt
@@ -3,7 +3,9 @@
 
   public class ParcelUtils {
     method public static <T extends androidx.versionedparcelable.VersionedParcelable> T? getVersionedParcelable(android.os.Bundle!, String!);
+    method public static <T extends androidx.versionedparcelable.VersionedParcelable> java.util.List<T>? getVersionedParcelableList(android.os.Bundle!, String!);
     method public static void putVersionedParcelable(android.os.Bundle, String, androidx.versionedparcelable.VersionedParcelable);
+    method public static void putVersionedParcelableList(android.os.Bundle, String, java.util.List<? extends androidx.versionedparcelable.VersionedParcelable>);
   }
 
   public interface VersionedParcelable {
diff --git a/versionedparcelable/api/current.txt b/versionedparcelable/api/current.txt
index a53654a..0ca14c1 100644
--- a/versionedparcelable/api/current.txt
+++ b/versionedparcelable/api/current.txt
@@ -3,7 +3,9 @@
 
   public class ParcelUtils {
     method public static <T extends androidx.versionedparcelable.VersionedParcelable> T? getVersionedParcelable(android.os.Bundle!, String!);
+    method public static <T extends androidx.versionedparcelable.VersionedParcelable> java.util.List<T>? getVersionedParcelableList(android.os.Bundle!, String!);
     method public static void putVersionedParcelable(android.os.Bundle, String, androidx.versionedparcelable.VersionedParcelable);
+    method public static void putVersionedParcelableList(android.os.Bundle, String, java.util.List<? extends androidx.versionedparcelable.VersionedParcelable>);
   }
 
   public interface VersionedParcelable {
diff --git a/versionedparcelable/src/main/java/androidx/versionedparcelable/ParcelUtils.java b/versionedparcelable/src/main/java/androidx/versionedparcelable/ParcelUtils.java
index b9b3b04..c2d530b 100644
--- a/versionedparcelable/src/main/java/androidx/versionedparcelable/ParcelUtils.java
+++ b/versionedparcelable/src/main/java/androidx/versionedparcelable/ParcelUtils.java
@@ -27,6 +27,8 @@
 
 import java.io.InputStream;
 import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.List;
 
 /**
  * Utilities for managing {@link VersionedParcelable}s.
@@ -91,7 +93,6 @@
         b.putParcelable(key, innerBundle);
     }
 
-
     /**
      * Get a VersionedParcelable from a Bundle.
      *
@@ -109,4 +110,43 @@
             return null;
         }
     }
+
+    /**
+     * Add a list of VersionedParcelable to an existing Bundle.
+     */
+    public static void putVersionedParcelableList(@NonNull Bundle b, @NonNull String key,
+            @NonNull List<? extends VersionedParcelable> list) {
+        Bundle innerBundle = new Bundle();
+        ArrayList<Parcelable> toWrite = new ArrayList<>();
+        for (VersionedParcelable obj : list) {
+            toWrite.add(toParcelable(obj));
+        }
+        innerBundle.putParcelableArrayList(INNER_BUNDLE_KEY, toWrite);
+        b.putParcelable(key, innerBundle);
+    }
+
+    /**
+     * Get a list of VersionedParcelable from a Bundle.
+     *
+     * Returns null if the bundle isn't present or ClassLoader issues occur.
+     */
+    @SuppressWarnings("TypeParameterUnusedInFormals")
+    @Nullable
+    public static <T extends VersionedParcelable> List<T> getVersionedParcelableList(
+            Bundle bundle, String key) {
+        List<T> resultList = new ArrayList<>();
+        try {
+            Bundle innerBundle = bundle.getParcelable(key);
+            innerBundle.setClassLoader(ParcelUtils.class.getClassLoader());
+            ArrayList<Parcelable> parcelableArrayList =
+                    innerBundle.getParcelableArrayList(INNER_BUNDLE_KEY);
+            for (Parcelable parcelable : parcelableArrayList) {
+                resultList.add((T) fromParcelable(parcelable));
+            }
+            return resultList;
+        } catch (RuntimeException e) {
+            // There may be new classes or such in the bundle, make sure not to crash the caller.
+        }
+        return null;
+    }
 }
diff --git a/viewpager2/integration-tests/testapp/build.gradle b/viewpager2/integration-tests/testapp/build.gradle
index be2719a..3affaad 100644
--- a/viewpager2/integration-tests/testapp/build.gradle
+++ b/viewpager2/integration-tests/testapp/build.gradle
@@ -33,6 +33,7 @@
     implementation(project(":viewpager2"))
     implementation(ARCH_LIFECYCLE_EXTENSIONS)
     implementation(MATERIAL)
+    implementation(project(":coordinatorlayout"))
     implementation(project(":cardview"))
 
     androidTestImplementation(TEST_RULES)
diff --git a/viewpager2/src/androidTest/java/androidx/viewpager2/widget/BaseTest.kt b/viewpager2/src/androidTest/java/androidx/viewpager2/widget/BaseTest.kt
index c70085e..54f92c2 100644
--- a/viewpager2/src/androidTest/java/androidx/viewpager2/widget/BaseTest.kt
+++ b/viewpager2/src/androidTest/java/androidx/viewpager2/widget/BaseTest.kt
@@ -39,7 +39,7 @@
 import androidx.test.espresso.matcher.ViewMatchers.withParent
 import androidx.test.espresso.matcher.ViewMatchers.withText
 import androidx.test.rule.ActivityTestRule
-import androidx.testutils.FragmentActivityUtils
+import androidx.testutils.AppCompatActivityUtils
 import androidx.testutils.FragmentActivityUtils.waitForActivityDrawn
 import androidx.viewpager2.LocaleTestUtils
 import androidx.viewpager2.adapter.FragmentStateAdapter
@@ -120,7 +120,7 @@
                 viewPager.adapter = adapterProvider(activity)
                 onCreateCallback(viewPager)
             }
-            activity = FragmentActivityUtils.recreateActivity(activityTestRule, activity)
+            activity = AppCompatActivityUtils.recreateActivity(activityTestRule, activity)
             TestActivity.onCreateCallback = { }
             waitForActivityDrawn(activity)
         }
diff --git a/viewpager2/src/androidTest/java/androidx/viewpager2/widget/swipe/TestActivity.kt b/viewpager2/src/androidTest/java/androidx/viewpager2/widget/swipe/TestActivity.kt
index f4ed3c6..7715091 100644
--- a/viewpager2/src/androidTest/java/androidx/viewpager2/widget/swipe/TestActivity.kt
+++ b/viewpager2/src/androidTest/java/androidx/viewpager2/widget/swipe/TestActivity.kt
@@ -17,11 +17,11 @@
 package androidx.viewpager2.widget.swipe
 
 import android.os.Bundle
-import androidx.testutils.RecreatedActivity
+import androidx.testutils.RecreatedAppCompatActivity
 import androidx.viewpager2.LocaleTestUtils
 import androidx.viewpager2.test.R
 
-class TestActivity : RecreatedActivity() {
+class TestActivity : RecreatedAppCompatActivity() {
     public override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
         if (intent?.hasExtra(EXTRA_LANGUAGE) == true) {
diff --git a/viewpager2/src/androidTest/res/values/styles.xml b/viewpager2/src/androidTest/res/values/styles.xml
index 94e0a86..cb1f73d 100644
--- a/viewpager2/src/androidTest/res/values/styles.xml
+++ b/viewpager2/src/androidTest/res/values/styles.xml
@@ -15,7 +15,7 @@
 -->
 
 <resources>
-    <style name="TestActivityTheme" parent="android:Theme">
+    <style name="TestActivityTheme" parent="Theme.AppCompat">
         <item name="android:windowAnimationStyle">@empty</item>
     </style>
 </resources>
diff --git a/webkit/src/androidTest/java/androidx/webkit/WebViewAssetLoaderIntegrationTest.java b/webkit/src/androidTest/java/androidx/webkit/WebViewAssetLoaderIntegrationTest.java
index e1fe81f..2ec6943 100644
--- a/webkit/src/androidTest/java/androidx/webkit/WebViewAssetLoaderIntegrationTest.java
+++ b/webkit/src/androidTest/java/androidx/webkit/WebViewAssetLoaderIntegrationTest.java
@@ -17,25 +17,22 @@
 package androidx.webkit;
 
 import android.app.Activity;
-import android.net.Uri;
 import android.os.Bundle;
 import android.webkit.WebResourceRequest;
 import android.webkit.WebResourceResponse;
 import android.webkit.WebView;
-import android.webkit.WebViewClient;
 
 import androidx.test.filters.MediumTest;
 import androidx.test.rule.ActivityTestRule;
 import androidx.test.runner.AndroidJUnit4;
 
+import org.junit.After;
 import org.junit.Assert;
+import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-import java.util.concurrent.ArrayBlockingQueue;
-import java.util.concurrent.Callable;
-
 @RunWith(AndroidJUnit4.class)
 public class WebViewAssetLoaderIntegrationTest {
     private static final String TAG = "WebViewAssetLoaderIntegrationTest";
@@ -44,67 +41,58 @@
     public final ActivityTestRule<TestActivity> mActivityRule =
                                     new ActivityTestRule<>(TestActivity.class);
 
+    private WebViewOnUiThread mOnUiThread;
+    private WebViewAssetLoader mAssetLoader;
+
+    private static class AssetLoadingWebViewClient extends WebViewOnUiThread.WaitForLoadedClient {
+        private final WebViewAssetLoader mAssetLoader;
+        AssetLoadingWebViewClient(WebViewOnUiThread onUiThread,
+                WebViewAssetLoader assetLoader) {
+            super(onUiThread);
+            mAssetLoader = assetLoader;
+        }
+
+        @SuppressWarnings({"deprecated"})
+        @Override
+        public WebResourceResponse shouldInterceptRequest(WebView view, String url) {
+            return mAssetLoader.shouldInterceptRequest(url);
+        }
+
+        @Override
+        public WebResourceResponse shouldInterceptRequest(WebView view,
+                                            WebResourceRequest request) {
+            return mAssetLoader.shouldInterceptRequest(request);
+        }
+    }
+
     // An Activity for Integeration tests
     public static class TestActivity extends Activity {
-        private class MyWebViewClient extends WebViewClient {
-            @Override
-            public boolean shouldOverrideUrlLoading(WebView view, String url) {
-                return false;
-            }
-
-            @Override
-            public void onPageFinished(WebView view, String url) {
-                mOnPageFinishedUrl.add(url);
-            }
-
-            @SuppressWarnings({"deprecated"})
-            @Override
-            public WebResourceResponse shouldInterceptRequest(WebView view, String url) {
-                return mAssetLoader.shouldInterceptRequest(url);
-            }
-
-            @Override
-            public WebResourceResponse shouldInterceptRequest(WebView view,
-                                                WebResourceRequest request) {
-                return mAssetLoader.shouldInterceptRequest(request);
-            }
-        }
-
-        private WebViewAssetLoader mAssetLoader;
         private WebView mWebView;
-        private ArrayBlockingQueue<String> mOnPageFinishedUrl = new ArrayBlockingQueue<String>(5);
-
-        public WebViewAssetLoader getAssetLoader() {
-            return mAssetLoader;
-
-        }
 
         public WebView getWebView() {
             return mWebView;
         }
 
-        public ArrayBlockingQueue<String> getOnPageFinishedUrl() {
-            return mOnPageFinishedUrl;
-        }
-
-        private void setUpWebView(WebView view) {
-            view.setWebViewClient(new MyWebViewClient());
-        }
-
+        // Runs before test suite's @Before.
         @Override
         protected void onCreate(Bundle savedInstanceState) {
             super.onCreate(savedInstanceState);
-            mAssetLoader = (new WebViewAssetLoader.Builder(this)).build();
             mWebView = new WebView(this);
-            setUpWebView(mWebView);
             setContentView(mWebView);
         }
+    }
 
-        @Override
-        protected void onDestroy() {
-            super.onDestroy();
-            mWebView.destroy();
-            mWebView = null;
+    @Before
+    public void setUp() {
+        mAssetLoader = (new WebViewAssetLoader.Builder(mActivityRule.getActivity())).build();
+        mOnUiThread = new WebViewOnUiThread(mActivityRule.getActivity().getWebView());
+        mOnUiThread.setWebViewClient(new AssetLoadingWebViewClient(mOnUiThread, mAssetLoader));
+    }
+
+    @After
+    public void tearDown() {
+        if (mOnUiThread != null) {
+            mOnUiThread.cleanUp();
         }
     }
 
@@ -112,66 +100,34 @@
     @MediumTest
     public void testAssetHosting() throws Exception {
         final TestActivity activity = mActivityRule.getActivity();
-        final String test_with_title_path = "www/test_with_title.html";
+        final String testWithTitlePath = "www/test_with_title.html";
 
-        String url = WebkitUtils.onMainThreadSync(new Callable<String>() {
-            @Override
-            public String call() {
-                WebViewAssetLoader assetLoader = activity.getAssetLoader();
-                Uri.Builder testPath =
-                        assetLoader.getAssetsHttpsPrefix().buildUpon()
-                                .appendPath(test_with_title_path);
+        String url =
+                mAssetLoader.getAssetsHttpsPrefix().buildUpon()
+                        .appendPath(testWithTitlePath)
+                        .build()
+                        .toString();
 
-                String url = testPath.toString();
-                activity.getWebView().loadUrl(url);
+        mOnUiThread.loadUrlAndWaitForCompletion(url);
 
-                return url;
-            }
-        });
-
-        String onPageFinishedUrl = activity.getOnPageFinishedUrl().take();
-        Assert.assertEquals(url, onPageFinishedUrl);
-
-        String title = WebkitUtils.onMainThreadSync(new Callable<String>() {
-            @Override
-            public String call() {
-                return activity.getWebView().getTitle();
-            }
-        });
-        Assert.assertEquals("WebViewAssetLoaderTest", title);
+        Assert.assertEquals("WebViewAssetLoaderTest", mOnUiThread.getTitle());
     }
 
     @Test
     @MediumTest
     public void testResourcesHosting() throws Exception {
         final TestActivity activity = mActivityRule.getActivity();
-        final String test_with_title_path = "test_with_title.html";
+        final String testWithTitlePath = "test_with_title.html";
 
-        String url = WebkitUtils.onMainThreadSync(new Callable<String>() {
-            @Override
-            public String call() {
-                WebViewAssetLoader assetLoader = activity.getAssetLoader();
-                Uri.Builder testPath =
-                        assetLoader.getResourcesHttpsPrefix().buildUpon()
-                        .appendPath("raw")
-                        .appendPath(test_with_title_path);
+        String url =
+                mAssetLoader.getResourcesHttpsPrefix().buildUpon()
+                .appendPath("raw")
+                .appendPath(testWithTitlePath)
+                .build()
+                .toString();
 
-                String url = testPath.toString();
-                activity.getWebView().loadUrl(url);
+        mOnUiThread.loadUrlAndWaitForCompletion(url);
 
-                return url;
-            }
-        });
-
-        String onPageFinishedUrl = activity.getOnPageFinishedUrl().take();
-        Assert.assertEquals(url, onPageFinishedUrl);
-
-        String title = WebkitUtils.onMainThreadSync(new Callable<String>() {
-            @Override
-            public String call() {
-                return activity.getWebView().getTitle();
-            }
-        });
-        Assert.assertEquals("WebViewAssetLoaderTest", title);
+        Assert.assertEquals("WebViewAssetLoaderTest", mOnUiThread.getTitle());
     }
 }
diff --git a/webkit/src/androidTest/java/androidx/webkit/WebViewAssetLoaderTest.java b/webkit/src/androidTest/java/androidx/webkit/WebViewAssetLoaderTest.java
index ff62e4b..bf663db 100644
--- a/webkit/src/androidTest/java/androidx/webkit/WebViewAssetLoaderTest.java
+++ b/webkit/src/androidTest/java/androidx/webkit/WebViewAssetLoaderTest.java
@@ -18,7 +18,6 @@
 
 import android.content.ContextWrapper;
 import android.net.Uri;
-import android.util.Log;
 import android.webkit.WebResourceResponse;
 
 import androidx.test.core.app.ApplicationProvider;
@@ -95,9 +94,8 @@
                 try {
                     return new ByteArrayInputStream(contents.getBytes(encoding));
                 } catch (UnsupportedEncodingException e) {
-                    Log.e(TAG, "exception when creating response", e);
+                    throw new RuntimeException(e);
                 }
-                return null;
             }
         };
 
@@ -106,10 +104,11 @@
 
         WebResourceResponse response =
                 assetLoader.shouldInterceptRequest("http://appassets.androidplatform.net/test/");
-        Assert.assertNotNull(response);
+        Assert.assertNotNull("didn't match the exact registered URL", response);
 
         Assert.assertEquals(contents, readAsString(response.getData(), encoding));
-        Assert.assertNull(assetLoader.shouldInterceptRequest("http://foo.bar/"));
+        Assert.assertNull("opened a non-registered URL - should return null",
+                            assetLoader.shouldInterceptRequest("http://foo.bar/"));
     }
 
     @Test
@@ -125,21 +124,23 @@
                     try {
                         return new ByteArrayInputStream(testHtmlContents.getBytes("utf-8"));
                     } catch (IOException e) {
-                        Log.e(TAG, "Unable to open asset URL: " + url);
-                        return null;
+                        throw new RuntimeException(e);
                     }
                 }
                 return null;
             }
         });
 
-        Assert.assertNull(assetLoader.getAssetsHttpPrefix());
+        Assert.assertNull("HTTP is not allowed - getAssetsHttpPrefix should return null",
+                                assetLoader.getAssetsHttpPrefix());
         Assert.assertEquals(assetLoader.getAssetsHttpsPrefix(),
                                     Uri.parse("https://appassets.androidplatform.net/assets/"));
 
         WebResourceResponse response =
                 assetLoader.shouldInterceptRequest("https://appassets.androidplatform.net/assets/www/test.html");
-        Assert.assertNotNull(response);
+        Assert.assertNotNull("failed to match the URL and returned null response", response);
+        Assert.assertNotNull("matched the URL but not the file and returned a null InputStream",
+                                    response.getData());
         Assert.assertEquals(testHtmlContents, readAsString(response.getData(), "utf-8"));
     }
 
@@ -152,23 +153,27 @@
         WebViewAssetLoader assetLoader = builder.buildForTest(new MockAssetHelper() {
             @Override
             public InputStream openResource(Uri uri) {
-                try {
-                    if (uri.getPath().equals("raw/test.html")) {
+                if (uri.getPath().equals("raw/test.html")) {
+                    try {
                         return new ByteArrayInputStream(testHtmlContents.getBytes("utf-8"));
+                    } catch (IOException e) {
+                        throw new RuntimeException(e);
                     }
-                } catch (IOException e) {
-                    Log.e(TAG, "exception when creating response", e);
                 }
                 return null;
             }
         });
 
-        Assert.assertNull(assetLoader.getResourcesHttpPrefix());
-        Assert.assertEquals(assetLoader.getResourcesHttpsPrefix(), Uri.parse("https://appassets.androidplatform.net/res/"));
+        Assert.assertNull("HTTP is not allowed - getResourcesHttpPrefix should return null",
+                                assetLoader.getResourcesHttpPrefix());
+        Assert.assertEquals(assetLoader.getResourcesHttpsPrefix(),
+                                    Uri.parse("https://appassets.androidplatform.net/res/"));
 
         WebResourceResponse response =
                  assetLoader.shouldInterceptRequest("https://appassets.androidplatform.net/res/raw/test.html");
-        Assert.assertNotNull(response);
+        Assert.assertNotNull("failed to match the URL and returned null response", response);
+        Assert.assertNotNull("matched the prefix URL but not the file",
+                                    response.getData());
         Assert.assertEquals(testHtmlContents, readAsString(response.getData(), "utf-8"));
     }
 
@@ -188,8 +193,7 @@
                     try {
                         return new ByteArrayInputStream(testHtmlContents.getBytes("utf-8"));
                     } catch (IOException e) {
-                        Log.e(TAG, "Unable to open asset URL: " + url);
-                        return null;
+                        throw new RuntimeException(e);
                     }
                 }
                 return null;
@@ -203,7 +207,9 @@
 
         WebResourceResponse response =
                 assetLoader.shouldInterceptRequest("http://example.com/android_assets/www/test.html");
-        Assert.assertNotNull(response);
+        Assert.assertNotNull("failed to match the URL and returned null response", response);
+        Assert.assertNotNull("matched the prefix URL but not the file",
+                                    response.getData());
         Assert.assertEquals(testHtmlContents, readAsString(response.getData(), "utf-8"));
     }
 
@@ -219,12 +225,12 @@
         WebViewAssetLoader assetLoader = builder.buildForTest(new MockAssetHelper() {
             @Override
             public InputStream openResource(Uri uri) {
-                try {
-                    if (uri.getPath().equals("raw/test.html")) {
+                if (uri.getPath().equals("raw/test.html")) {
+                    try {
                         return new ByteArrayInputStream(testHtmlContents.getBytes("utf-8"));
+                    } catch (IOException e) {
+                        throw new RuntimeException(e);
                     }
-                } catch (IOException e) {
-                    Log.e(TAG, "exception when creating response", e);
                 }
                 return null;
             }
@@ -237,7 +243,9 @@
 
         WebResourceResponse response =
                 assetLoader.shouldInterceptRequest("http://example.com/android_res/raw/test.html");
-        Assert.assertNotNull(response);
+        Assert.assertNotNull("failed to match the URL and returned null response", response);
+        Assert.assertNotNull("matched the prefix URL but not the file",
+                                    response.getData());
         Assert.assertEquals(testHtmlContents, readAsString(response.getData(), "utf-8"));
     }
 }
diff --git a/webkit/src/androidTest/java/androidx/webkit/WebViewOnUiThread.java b/webkit/src/androidTest/java/androidx/webkit/WebViewOnUiThread.java
index 84f52f3..21e687c 100644
--- a/webkit/src/androidTest/java/androidx/webkit/WebViewOnUiThread.java
+++ b/webkit/src/androidTest/java/androidx/webkit/WebViewOnUiThread.java
@@ -30,6 +30,7 @@
 import android.webkit.WebView;
 import android.webkit.WebViewClient;
 
+import androidx.annotation.CallSuper;
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.test.core.app.ApplicationProvider;
@@ -45,7 +46,7 @@
  * Modifications to this class should be reflected in that class as necessary. See
  * http://go/modifying-webview-cts.
  */
-class WebViewOnUiThread {
+public class WebViewOnUiThread {
     /**
      * The maximum time, in milliseconds (10 seconds) to wait for a load
      * to be triggered.
@@ -69,10 +70,22 @@
     private WebView mWebView;
 
     public WebViewOnUiThread() {
+        this(WebkitUtils.onMainThreadSync(new Callable<WebView>() {
+            @Override
+            public WebView call() {
+                return new WebView(ApplicationProvider.getApplicationContext());
+            }
+        }));
+    }
+
+    /**
+     * Create a new WebViewOnUiThread wrapping the provided {@link WebView}.
+     */
+    public WebViewOnUiThread(final WebView webView) {
         WebkitUtils.onMainThreadSync(new Runnable() {
             @Override
             public void run() {
-                mWebView = new WebView(ApplicationProvider.getApplicationContext());
+                mWebView = webView;
                 mWebView.setWebViewClient(new WaitForLoadedClient(WebViewOnUiThread.this));
                 mWebView.setWebChromeClient(new WaitForProgressClient(WebViewOnUiThread.this));
             }
@@ -520,6 +533,7 @@
         }
 
         @Override
+        @CallSuper
         public void onProgressChanged(WebView view, int newProgress) {
             super.onProgressChanged(view, newProgress);
             mOnUiThread.onProgressChanged(newProgress);
@@ -541,12 +555,14 @@
         }
 
         @Override
+        @CallSuper
         public void onPageFinished(WebView view, String url) {
             super.onPageFinished(view, url);
             mOnUiThread.onPageFinished();
         }
 
         @Override
+        @CallSuper
         public void onPageStarted(WebView view, String url, Bitmap favicon) {
             super.onPageStarted(view, url, favicon);
             mOnUiThread.onPageStarted();
diff --git a/webkit/src/androidTest/java/androidx/webkit/WebViewRenderProcessTest.java b/webkit/src/androidTest/java/androidx/webkit/WebViewRenderProcessTest.java
index c2fc7c9..fda6677 100644
--- a/webkit/src/androidTest/java/androidx/webkit/WebViewRenderProcessTest.java
+++ b/webkit/src/androidTest/java/androidx/webkit/WebViewRenderProcessTest.java
@@ -31,6 +31,7 @@
 import com.google.common.util.concurrent.ListenableFuture;
 
 import org.junit.Assert;
+import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -102,11 +103,24 @@
         return future;
     }
 
+    @Before
+    public void setUp() {
+        WebkitUtils.checkFeature(WebViewFeature.GET_WEB_VIEW_RENDERER);
+
+        // Ensure that any existing renderer still alive after a previous test is terminated.
+        // TODO(tobiasjs): This assumes that WebView uses at most one renderer, which is true
+        // for now but may not remain so in future.
+        final WebView webView = WebViewOnUiThread.createWebView();
+        final WebViewRenderProcess renderProcess = getRenderProcessOnUiThread(webView);
+        WebViewOnUiThread.destroy(webView);
+        if (renderProcess != null) {
+            terminateRenderProcessOnUiThread(renderProcess);
+        }
+    }
+
     @Test
     @SdkSuppress(maxSdkVersion = Build.VERSION_CODES.N_MR1)
     public void testGetWebViewRenderProcessPreO() throws Throwable {
-        WebkitUtils.checkFeature(WebViewFeature.GET_WEB_VIEW_RENDERER);
-
         // It should not be possible to get a renderer pre-O
         WebView webView = WebViewOnUiThread.createWebView();
         final WebViewRenderProcess renderer = startAndGetRenderProcess(webView).get();
@@ -120,7 +134,6 @@
     @SuppressLint("NewApi")
     @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
     public void testGetWebViewRenderProcess() throws Throwable {
-        WebkitUtils.checkFeature(WebViewFeature.GET_WEB_VIEW_RENDERER);
         // TODO(tobiasjs) some O devices are not multiprocess, and multiprocess can also be disabled
         // manually. This test should handle those scenarios.
 
diff --git a/work/integration-tests/testapp/build.gradle b/work/integration-tests/testapp/build.gradle
index a03c39c..c85c010 100644
--- a/work/integration-tests/testapp/build.gradle
+++ b/work/integration-tests/testapp/build.gradle
@@ -47,7 +47,7 @@
 
     implementation(CONSTRAINT_LAYOUT, { transitive = true })
     implementation project(':work:work-runtime-ktx')
-    implementation(ARCH_CORE_RUNTIME)
+    implementation(WORK_ARCH_CORE_RUNTIME)
     implementation(ARCH_LIFECYCLE_EXTENSIONS)
     implementation(ANDROIDX_RECYCLERVIEW)
     implementation(MATERIAL)
diff --git a/work/workmanager-testing/build.gradle b/work/workmanager-testing/build.gradle
index 13c5338..2bbc54f 100644
--- a/work/workmanager-testing/build.gradle
+++ b/work/workmanager-testing/build.gradle
@@ -31,7 +31,7 @@
     implementation(ARCH_ROOM_RUNTIME)
     annotationProcessor(ARCH_ROOM_COMPILER)
 
-    androidTestImplementation(ARCH_CORE_TESTING)
+    androidTestImplementation(WORK_ARCH_CORE_TESTING)
     androidTestImplementation(TEST_EXT_JUNIT)
     androidTestImplementation(TEST_CORE)
     androidTestImplementation(TEST_RUNNER)
@@ -40,6 +40,10 @@
     androidTestImplementation(DEXMAKER_MOCKITO, libs.exclude_bytebuddy) // DexMaker has it"s own MockMaker
 
     testImplementation(JUNIT)
+    testImplementation(ROBOLECTRIC)
+    testImplementation(TEST_EXT_JUNIT)
+    testImplementation(TEST_CORE)
+    testImplementation(TEST_RUNNER)
 }
 
 supportLibrary {
diff --git a/work/workmanager-testing/src/main/java/androidx/work/testing/TestWorkManagerImpl.java b/work/workmanager-testing/src/main/java/androidx/work/testing/TestWorkManagerImpl.java
index 873ad26..0b39778 100644
--- a/work/workmanager-testing/src/main/java/androidx/work/testing/TestWorkManagerImpl.java
+++ b/work/workmanager-testing/src/main/java/androidx/work/testing/TestWorkManagerImpl.java
@@ -22,19 +22,26 @@
 import androidx.annotation.RestrictTo;
 import androidx.work.Configuration;
 import androidx.work.WorkManager;
+import androidx.work.impl.Scheduler;
 import androidx.work.impl.WorkManagerImpl;
 import androidx.work.impl.utils.taskexecutor.TaskExecutor;
 
+import java.util.Collections;
+import java.util.List;
+import java.util.UUID;
 import java.util.concurrent.Executor;
 
 /**
- * A concrete implementation of {@link WorkManager} which can be used for testing.
- * This implementation makes it easy to swap Schedulers.
+ * A concrete implementation of {@link WorkManager} which can be used for testing. This
+ * implementation makes it easy to swap Schedulers.
  *
  * @hide
  */
 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-abstract class TestWorkManagerImpl extends WorkManagerImpl implements TestDriver {
+class TestWorkManagerImpl extends WorkManagerImpl implements TestDriver {
+
+    private TestScheduler mScheduler;
+
     TestWorkManagerImpl(
             @NonNull Context context,
             @NonNull Configuration configuration) {
@@ -75,5 +82,29 @@
                     }
                 },
                 true);
+
+        // mScheduler is initialized in createSchedulers() called by super()
+        getProcessor().addExecutionListener(mScheduler);
+    }
+
+    @Override
+    public @NonNull List<Scheduler> createSchedulers(Context context) {
+        mScheduler = new TestScheduler();
+        return Collections.singletonList((Scheduler) mScheduler);
+    }
+
+    @Override
+    public void setAllConstraintsMet(@NonNull UUID workSpecId) {
+        mScheduler.setAllConstraintsMet(workSpecId);
+    }
+
+    @Override
+    public void setInitialDelayMet(@NonNull UUID workSpecId) {
+        mScheduler.setInitialDelayMet(workSpecId);
+    }
+
+    @Override
+    public void setPeriodDelayMet(@NonNull UUID workSpecId) {
+        mScheduler.setPeriodDelayMet(workSpecId);
     }
 }
diff --git a/work/workmanager-testing/src/main/java/androidx/work/testing/WorkManagerTestInitHelper.java b/work/workmanager-testing/src/main/java/androidx/work/testing/WorkManagerTestInitHelper.java
index 1e09b60..270e0d2 100644
--- a/work/workmanager-testing/src/main/java/androidx/work/testing/WorkManagerTestInitHelper.java
+++ b/work/workmanager-testing/src/main/java/androidx/work/testing/WorkManagerTestInitHelper.java
@@ -20,12 +20,8 @@
 
 import androidx.annotation.NonNull;
 import androidx.work.Configuration;
-import androidx.work.impl.Scheduler;
 import androidx.work.impl.WorkManagerImpl;
 
-import java.util.Collections;
-import java.util.List;
-import java.util.UUID;
 
 /**
  * Helps initialize {@link androidx.work.WorkManager} for testing.
@@ -54,32 +50,7 @@
     public static void initializeTestWorkManager(
             @NonNull Context context,
             @NonNull Configuration configuration) {
-
-        final TestScheduler scheduler = new TestScheduler();
-        WorkManagerImpl workManager = new TestWorkManagerImpl(context, configuration) {
-
-            @Override
-            public @NonNull List<Scheduler> createSchedulers(Context context) {
-                return Collections.singletonList((Scheduler) scheduler);
-            }
-
-            @Override
-            public void setAllConstraintsMet(@NonNull UUID workSpecId) {
-                scheduler.setAllConstraintsMet(workSpecId);
-            }
-
-            @Override
-            public void setInitialDelayMet(@NonNull UUID workSpecId) {
-                scheduler.setInitialDelayMet(workSpecId);
-            }
-
-            @Override
-            public void setPeriodDelayMet(@NonNull UUID workSpecId) {
-                scheduler.setPeriodDelayMet(workSpecId);
-            }
-        };
-        workManager.getProcessor().addExecutionListener(scheduler);
-        WorkManagerImpl.setDelegate(workManager);
+        WorkManagerImpl.setDelegate(new TestWorkManagerImpl(context, configuration));
     }
 
     /**
diff --git a/work/workmanager-testing/src/test/java/androidx/work/testing/RobolectricSmokeTest.java b/work/workmanager-testing/src/test/java/androidx/work/testing/RobolectricSmokeTest.java
new file mode 100644
index 0000000..4671314
--- /dev/null
+++ b/work/workmanager-testing/src/test/java/androidx/work/testing/RobolectricSmokeTest.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.work.testing;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import android.content.Context;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.filters.LargeTest;
+import androidx.work.OneTimeWorkRequest;
+import androidx.work.WorkInfo;
+import androidx.work.WorkRequest;
+import androidx.work.impl.WorkManagerImpl;
+import androidx.work.testing.workers.TestWorker;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.Config;
+import org.robolectric.annotation.internal.DoNotInstrument;
+
+import java.util.Collections;
+import java.util.concurrent.ExecutionException;
+
+@Config(manifest = Config.NONE)
+@RunWith(RobolectricTestRunner.class)
+@LargeTest
+@DoNotInstrument
+public class RobolectricSmokeTest {
+    @Before
+    public void setUp() {
+        Context context = ApplicationProvider.getApplicationContext();
+        WorkManagerTestInitHelper.initializeTestWorkManager(context);
+    }
+
+    @Test
+    public void testWorker_shouldSucceedSynchronously()
+            throws InterruptedException, ExecutionException {
+        WorkRequest request = new OneTimeWorkRequest.Builder(TestWorker.class).build();
+        // TestWorkManagerImpl is a subtype of WorkManagerImpl.
+        WorkManagerImpl workManagerImpl = WorkManagerImpl.getInstance();
+        workManagerImpl.enqueue(Collections.singletonList(request)).getResult().get();
+        WorkInfo status = workManagerImpl.getWorkInfoById(request.getId()).get();
+        assertThat(status.getState().isFinished(), is(true));
+    }
+}
diff --git a/work/workmanager-testing/src/test/java/androidx/work/testing/workers/TestWorker.java b/work/workmanager-testing/src/test/java/androidx/work/testing/workers/TestWorker.java
new file mode 100644
index 0000000..78b7ab0
--- /dev/null
+++ b/work/workmanager-testing/src/test/java/androidx/work/testing/workers/TestWorker.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.work.testing.workers;
+
+import android.content.Context;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+import androidx.work.Logger;
+import androidx.work.Worker;
+import androidx.work.WorkerParameters;
+
+/** A test {@link Worker} that prints a log and returns a successful result. */
+public class TestWorker extends Worker {
+    private static final String TAG = Logger.tagWithPrefix("TestWorker");
+
+    public TestWorker(@NonNull Context context, @NonNull WorkerParameters workerParams) {
+        super(context, workerParams);
+    }
+
+    @Override
+    public @NonNull Result doWork() {
+        Log.i(TAG, "Doing work.");
+        return Result.success();
+    }
+}
diff --git a/work/workmanager/build.gradle b/work/workmanager/build.gradle
index 4232d45..d424b64 100644
--- a/work/workmanager/build.gradle
+++ b/work/workmanager/build.gradle
@@ -58,7 +58,7 @@
     api(GUAVA_LISTENABLE_FUTURE)
     androidTestImplementation(TEST_EXT_JUNIT)
     androidTestImplementation(TEST_CORE)
-    androidTestImplementation(ARCH_CORE_TESTING)
+    androidTestImplementation(WORK_ARCH_CORE_TESTING)
     androidTestImplementation(TEST_RUNNER)
     androidTestImplementation(ESPRESSO_CORE)
     androidTestImplementation(MOCKITO_CORE, libs.exclude_bytebuddy) // DexMaker has its own MockMaker