Merge "Revert "Set Navigation to depend on exactly Fragment 1.1.0-alpha05"" into androidx-master-dev
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/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/PublishDocsRules.kt b/buildSrc/src/main/kotlin/androidx/build/PublishDocsRules.kt
index 60e0de9..aac2591 100644
--- a/buildSrc/src/main/kotlin/androidx/build/PublishDocsRules.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/PublishDocsRules.kt
@@ -65,6 +65,7 @@
     ignore(LibraryGroups.LIFECYCLE.group, "lifecycle-livedata-core-ktx")
     ignore(LibraryGroups.LIFECYCLE.group, "lifecycle-compiler")
     ignore(LibraryGroups.LIFECYCLE.group, "lifecycle-common-eap")
+    ignore(LibraryGroups.LIFECYCLE.group, "lifecycle-runtime-eap")
     prebuilts(LibraryGroups.LIFECYCLE, "lifecycle-viewmodel-savedstate", "1.0.0-alpha01")
     prebuilts(LibraryGroups.LIFECYCLE, "2.1.0-alpha03")
     prebuilts(LibraryGroups.LOADER, "1.1.0-beta01")
@@ -77,7 +78,7 @@
     prebuilts(LibraryGroups.MEDIA2, "1.0.0-alpha03")
     prebuilts(LibraryGroups.MEDIAROUTER, "1.1.0-alpha02")
     ignore(LibraryGroups.NAVIGATION.group, "navigation-testing")
-    prebuilts(LibraryGroups.NAVIGATION, "2.0.0-rc02")
+    prebuilts(LibraryGroups.NAVIGATION, "2.1.0-alpha01")
     prebuilts(LibraryGroups.PAGING, "2.1.0")
     prebuilts(LibraryGroups.PALETTE, "1.0.0")
     prebuilts(LibraryGroups.PERCENTLAYOUT, "1.0.0")
@@ -89,6 +90,7 @@
     prebuilts(LibraryGroups.RECYCLERVIEW, "recyclerview", "1.1.0-alpha03")
     prebuilts(LibraryGroups.RECYCLERVIEW, "recyclerview-selection", "1.1.0-alpha01")
     prebuilts(LibraryGroups.REMOTECALLBACK, "1.0.0-alpha01")
+    ignore(LibraryGroups.ROOM.group, "room-common-java8")
     prebuilts(LibraryGroups.ROOM, "2.1.0-alpha05")
     prebuilts(LibraryGroups.SAVEDSTATE, "1.0.0-alpha02")
     prebuilts(LibraryGroups.SHARETARGET, "1.0.0-alpha01")
@@ -112,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/car/core/api/1.0.0-alpha7.txt b/car/core/api/1.0.0-alpha7.txt
index a5d0f09..4e2813c 100644
--- a/car/core/api/1.0.0-alpha7.txt
+++ b/car/core/api/1.0.0-alpha7.txt
@@ -567,6 +567,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..4e2813c 100644
--- a/car/core/api/current.txt
+++ b/car/core/api/current.txt
@@ -567,6 +567,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/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/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.java b/fragment/src/androidTest/java/androidx/fragment/app/FragmentReorderingTest.java
deleted file mode 100644
index 1c47bc8..0000000
--- a/fragment/src/androidTest/java/androidx/fragment/app/FragmentReorderingTest.java
+++ /dev/null
@@ -1,683 +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.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-
-import android.app.Instrumentation;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.EditText;
-
-import androidx.fragment.app.test.FragmentTestActivity;
-import androidx.fragment.test.R;
-import androidx.test.annotation.UiThreadTest;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SmallTest;
-import androidx.test.platform.app.InstrumentationRegistry;
-import androidx.test.rule.ActivityTestRule;
-
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class FragmentReorderingTest {
-    @Rule
-    public ActivityTestRule<FragmentTestActivity> mActivityRule =
-            new ActivityTestRule<FragmentTestActivity>(FragmentTestActivity.class);
-
-    private ViewGroup mContainer;
-    private FragmentManager mFM;
-    private Instrumentation mInstrumentation;
-
-    @Before
-    public void setup() {
-        FragmentTestUtil.setContentView(mActivityRule, R.layout.simple_container);
-        mContainer = (ViewGroup) mActivityRule.getActivity().findViewById(R.id.fragmentContainer);
-        mFM = mActivityRule.getActivity().getSupportFragmentManager();
-        mInstrumentation = InstrumentationRegistry.getInstrumentation();
-    }
-
-    // Test that when you add and replace a fragment that only the replace's add
-    // actually creates a View.
-    @Test
-    public void addReplace() throws Throwable {
-        final CountCallsFragment fragment1 = new CountCallsFragment();
-        final StrictViewFragment fragment2 = new StrictViewFragment();
-        mInstrumentation.runOnMainSync(new Runnable() {
-            @Override
-            public void run() {
-                mFM.beginTransaction()
-                        .add(R.id.fragmentContainer, fragment1)
-                        .addToBackStack(null)
-                        .setReorderingAllowed(true)
-                        .commit();
-                mFM.beginTransaction()
-                        .replace(R.id.fragmentContainer, fragment2)
-                        .addToBackStack(null)
-                        .setReorderingAllowed(true)
-                        .commit();
-                mFM.executePendingTransactions();
-            }
-        });
-        assertEquals(0, fragment1.onCreateViewCount);
-        FragmentTestUtil.assertChildren(mContainer, fragment2);
-
-        mInstrumentation.runOnMainSync(new Runnable() {
-            @Override
-            public void run() {
-                mFM.popBackStack();
-                mFM.popBackStack();
-                mFM.executePendingTransactions();
-            }
-        });
-        FragmentTestUtil.assertChildren(mContainer);
-    }
-
-    // Test that it is possible to merge a transaction that starts with pop and adds
-    // the same view back again.
-    @Test
-    public void startWithPop() throws Throwable {
-        // Start with a single fragment on the back stack
-        final CountCallsFragment fragment1 = new CountCallsFragment();
-        mFM.beginTransaction()
-                .add(R.id.fragmentContainer, fragment1)
-                .addToBackStack(null)
-                .setReorderingAllowed(true)
-                .commit();
-
-        FragmentTestUtil.executePendingTransactions(mActivityRule);
-        FragmentTestUtil.assertChildren(mContainer, fragment1);
-
-        // Now pop and add
-        mInstrumentation.runOnMainSync(new Runnable() {
-            @Override
-            public void run() {
-                mFM.popBackStack();
-                mFM.beginTransaction()
-                        .add(R.id.fragmentContainer, fragment1)
-                        .addToBackStack(null)
-                        .setReorderingAllowed(true)
-                        .commit();
-                mFM.executePendingTransactions();
-            }
-        });
-        FragmentTestUtil.assertChildren(mContainer, fragment1);
-        assertEquals(1, fragment1.onCreateViewCount);
-
-        FragmentTestUtil.popBackStackImmediate(mActivityRule);
-        FragmentTestUtil.assertChildren(mContainer);
-        assertEquals(1, fragment1.onCreateViewCount);
-    }
-
-    // Popping the back stack in the middle of other operations doesn't fool it.
-    @Test
-    public void middlePop() throws Throwable {
-        final CountCallsFragment fragment1 = new CountCallsFragment();
-        final CountCallsFragment fragment2 = new CountCallsFragment();
-        mInstrumentation.runOnMainSync(new Runnable() {
-            @Override
-            public void run() {
-                mFM.beginTransaction()
-                        .add(R.id.fragmentContainer, fragment1)
-                        .addToBackStack(null)
-                        .setReorderingAllowed(true)
-                        .commit();
-                mFM.popBackStack();
-                mFM.beginTransaction()
-                        .add(R.id.fragmentContainer, fragment2)
-                        .addToBackStack(null)
-                        .setReorderingAllowed(true)
-                        .commit();
-                mFM.executePendingTransactions();
-            }
-        });
-        FragmentTestUtil.assertChildren(mContainer, fragment2);
-        assertEquals(0, fragment1.onAttachCount);
-        assertEquals(1, fragment2.onCreateViewCount);
-
-        FragmentTestUtil.popBackStackImmediate(mActivityRule);
-        FragmentTestUtil.assertChildren(mContainer);
-        assertEquals(1, fragment2.onDetachCount);
-    }
-
-    // ensure that removing a view after adding it is optimized into no
-    // View being created. Hide still gets notified.
-    @Test
-    public void removeRedundantRemove() throws Throwable {
-        final CountCallsFragment fragment1 = new CountCallsFragment();
-        final int[] id = new int[1];
-        mInstrumentation.runOnMainSync(new Runnable() {
-            @Override
-            public void run() {
-                id[0] = mFM.beginTransaction()
-                        .add(R.id.fragmentContainer, fragment1)
-                        .addToBackStack(null)
-                        .setReorderingAllowed(true)
-                        .commit();
-                mFM.beginTransaction()
-                        .hide(fragment1)
-                        .addToBackStack(null)
-                        .setReorderingAllowed(true)
-                        .commit();
-                mFM.beginTransaction()
-                        .remove(fragment1)
-                        .addToBackStack(null)
-                        .setReorderingAllowed(true)
-                        .commit();
-                mFM.executePendingTransactions();
-            }
-        });
-        FragmentTestUtil.assertChildren(mContainer);
-        assertEquals(0, fragment1.onCreateViewCount);
-        assertEquals(1, fragment1.onHideCount);
-        assertEquals(0, fragment1.onShowCount);
-        assertEquals(0, fragment1.onDetachCount);
-        assertEquals(1, fragment1.onAttachCount);
-
-        FragmentTestUtil.popBackStackImmediate(mActivityRule, id[0],
-                FragmentManager.POP_BACK_STACK_INCLUSIVE);
-        FragmentTestUtil.assertChildren(mContainer);
-        assertEquals(0, fragment1.onCreateViewCount);
-        assertEquals(1, fragment1.onHideCount);
-        assertEquals(1, fragment1.onShowCount);
-        assertEquals(1, fragment1.onDetachCount);
-        assertEquals(1, fragment1.onAttachCount);
-    }
-
-    // Ensure that removing and adding the same view results in no operation
-    @Test
-    public void removeRedundantAdd() throws Throwable {
-        final CountCallsFragment fragment1 = new CountCallsFragment();
-        int id = mFM.beginTransaction()
-                .add(R.id.fragmentContainer, fragment1)
-                .addToBackStack(null)
-                .setReorderingAllowed(true)
-                .commit();
-        FragmentTestUtil.executePendingTransactions(mActivityRule);
-        assertEquals(1, fragment1.onCreateViewCount);
-
-        mInstrumentation.runOnMainSync(new Runnable() {
-            @Override
-            public void run() {
-                mFM.beginTransaction()
-                        .remove(fragment1)
-                        .addToBackStack(null)
-                        .setReorderingAllowed(true)
-                        .commit();
-                mFM.beginTransaction()
-                        .add(R.id.fragmentContainer, fragment1)
-                        .addToBackStack(null)
-                        .setReorderingAllowed(true)
-                        .commit();
-                mFM.executePendingTransactions();
-            }
-        });
-
-        FragmentTestUtil.assertChildren(mContainer, fragment1);
-        // should be optimized out
-        assertEquals(1, fragment1.onCreateViewCount);
-
-        mFM.popBackStack(id, 0);
-        FragmentTestUtil.executePendingTransactions(mActivityRule);
-        FragmentTestUtil.assertChildren(mContainer, fragment1);
-        // optimize out going back, too
-        assertEquals(1, fragment1.onCreateViewCount);
-    }
-
-    // detaching, then attaching results in on change. Hide still functions
-    @Test
-    public void removeRedundantAttach() throws Throwable {
-        final CountCallsFragment fragment1 = new CountCallsFragment();
-        int id = mFM.beginTransaction()
-                .add(R.id.fragmentContainer, fragment1)
-                .addToBackStack(null)
-                .setReorderingAllowed(true)
-                .commit();
-        FragmentTestUtil.executePendingTransactions(mActivityRule);
-        assertEquals(1, fragment1.onAttachCount);
-        FragmentTestUtil.assertChildren(mContainer, fragment1);
-
-        mInstrumentation.runOnMainSync(new Runnable() {
-            @Override
-            public void run() {
-                mFM.beginTransaction()
-                        .detach(fragment1)
-                        .addToBackStack(null)
-                        .setReorderingAllowed(true)
-                        .commit();
-                mFM.beginTransaction()
-                        .hide(fragment1)
-                        .addToBackStack(null)
-                        .setReorderingAllowed(true)
-                        .commit();
-                mFM.beginTransaction()
-                        .attach(fragment1)
-                        .addToBackStack(null)
-                        .setReorderingAllowed(true)
-                        .commit();
-                mFM.executePendingTransactions();
-            }
-        });
-
-        FragmentTestUtil.assertChildren(mContainer, fragment1);
-        // can optimize out the detach/attach
-        assertEquals(0, fragment1.onDestroyViewCount);
-        assertEquals(1, fragment1.onHideCount);
-        assertEquals(0, fragment1.onShowCount);
-        assertEquals(1, fragment1.onCreateViewCount);
-        assertEquals(1, fragment1.onAttachCount);
-        assertEquals(0, fragment1.onDetachCount);
-
-        mFM.popBackStack(id, 0);
-        FragmentTestUtil.executePendingTransactions(mActivityRule);
-        FragmentTestUtil.assertChildren(mContainer, fragment1);
-
-        // optimized out again, but not the show
-        assertEquals(0, fragment1.onDestroyViewCount);
-        assertEquals(1, fragment1.onHideCount);
-        assertEquals(1, fragment1.onShowCount);
-        assertEquals(1, fragment1.onCreateViewCount);
-        assertEquals(1, fragment1.onAttachCount);
-        assertEquals(0, fragment1.onDetachCount);
-    }
-
-    // attaching, then detaching shouldn't result in a View being created
-    @Test
-    public void removeRedundantDetach() throws Throwable {
-        final CountCallsFragment fragment1 = new CountCallsFragment();
-        int id = mFM.beginTransaction()
-                .add(R.id.fragmentContainer, fragment1)
-                .detach(fragment1)
-                .addToBackStack(null)
-                .setReorderingAllowed(true)
-                .commit();
-        FragmentTestUtil.executePendingTransactions(mActivityRule);
-
-        // the add detach is not fully optimized out
-        assertEquals(1, fragment1.onAttachCount);
-        assertEquals(0, fragment1.onDetachCount);
-        assertTrue(fragment1.isDetached());
-        assertEquals(0, fragment1.onCreateViewCount);
-        FragmentTestUtil.assertChildren(mContainer);
-
-        mInstrumentation.runOnMainSync(new Runnable() {
-            @Override
-            public void run() {
-                mFM.beginTransaction()
-                        .attach(fragment1)
-                        .addToBackStack(null)
-                        .setReorderingAllowed(true)
-                        .commit();
-                mFM.beginTransaction()
-                        .hide(fragment1)
-                        .addToBackStack(null)
-                        .setReorderingAllowed(true)
-                        .commit();
-                mFM.beginTransaction()
-                        .detach(fragment1)
-                        .addToBackStack(null)
-                        .setReorderingAllowed(true)
-                        .commit();
-                mFM.executePendingTransactions();
-            }
-        });
-
-        FragmentTestUtil.assertChildren(mContainer);
-        // can optimize out the attach/detach, and the hide call
-        assertEquals(1, fragment1.onAttachCount);
-        assertEquals(0, fragment1.onDetachCount);
-        assertEquals(1, fragment1.onHideCount);
-        assertTrue(fragment1.isHidden());
-        assertEquals(0, fragment1.onShowCount);
-
-        mFM.popBackStack(id, 0);
-        FragmentTestUtil.executePendingTransactions(mActivityRule);
-        FragmentTestUtil.assertChildren(mContainer);
-
-        // we can optimize out the attach/detach on the way back
-        assertEquals(1, fragment1.onAttachCount);
-        assertEquals(0, fragment1.onDetachCount);
-        assertEquals(1, fragment1.onShowCount);
-        assertEquals(1, fragment1.onHideCount);
-        assertFalse(fragment1.isHidden());
-    }
-
-    // show, then hide should optimize out
-    @Test
-    public void removeRedundantHide() throws Throwable {
-        final CountCallsFragment fragment1 = new CountCallsFragment();
-        int id = mFM.beginTransaction()
-                .add(R.id.fragmentContainer, fragment1)
-                .hide(fragment1)
-                .addToBackStack(null)
-                .setReorderingAllowed(true)
-                .commit();
-        FragmentTestUtil.executePendingTransactions(mActivityRule);
-        assertEquals(0, fragment1.onShowCount);
-        assertEquals(1, fragment1.onHideCount);
-        FragmentTestUtil.assertChildren(mContainer, fragment1);
-
-        mInstrumentation.runOnMainSync(new Runnable() {
-            @Override
-            public void run() {
-                mFM.beginTransaction()
-                        .show(fragment1)
-                        .addToBackStack(null)
-                        .setReorderingAllowed(true)
-                        .commit();
-                mFM.beginTransaction()
-                        .remove(fragment1)
-                        .addToBackStack(null)
-                        .setReorderingAllowed(true)
-                        .commit();
-                mFM.beginTransaction()
-                        .add(R.id.fragmentContainer, fragment1)
-                        .addToBackStack(null)
-                        .setReorderingAllowed(true)
-                        .commit();
-                mFM.beginTransaction()
-                        .hide(fragment1)
-                        .addToBackStack(null)
-                        .setReorderingAllowed(true)
-                        .commit();
-                mFM.executePendingTransactions();
-            }
-        });
-
-        FragmentTestUtil.assertChildren(mContainer, fragment1);
-        // optimize out hide/show
-        assertEquals(0, fragment1.onShowCount);
-        assertEquals(1, fragment1.onHideCount);
-
-        FragmentTestUtil.popBackStackImmediate(mActivityRule, id, 0);
-        FragmentTestUtil.assertChildren(mContainer, fragment1);
-
-        // still optimized out
-        assertEquals(0, fragment1.onShowCount);
-        assertEquals(1, fragment1.onHideCount);
-
-        mInstrumentation.runOnMainSync(new Runnable() {
-            @Override
-            public void run() {
-                mFM.beginTransaction()
-                        .show(fragment1)
-                        .addToBackStack(null)
-                        .setReorderingAllowed(true)
-                        .commit();
-                mFM.beginTransaction()
-                        .hide(fragment1)
-                        .addToBackStack(null)
-                        .setReorderingAllowed(true)
-                        .commit();
-                mFM.executePendingTransactions();
-            }
-        });
-
-        // The show/hide can be optimized out and nothing should change.
-        FragmentTestUtil.assertChildren(mContainer, fragment1);
-        assertEquals(0, fragment1.onShowCount);
-        assertEquals(1, fragment1.onHideCount);
-
-        FragmentTestUtil.popBackStackImmediate(mActivityRule, id, 0);
-        FragmentTestUtil.assertChildren(mContainer, fragment1);
-
-        assertEquals(0, fragment1.onShowCount);
-        assertEquals(1, fragment1.onHideCount);
-
-        mInstrumentation.runOnMainSync(new Runnable() {
-            @Override
-            public void run() {
-                mFM.beginTransaction()
-                        .show(fragment1)
-                        .addToBackStack(null)
-                        .setReorderingAllowed(true)
-                        .commit();
-                mFM.beginTransaction()
-                        .detach(fragment1)
-                        .addToBackStack(null)
-                        .setReorderingAllowed(true)
-                        .commit();
-                mFM.beginTransaction()
-                        .attach(fragment1)
-                        .addToBackStack(null)
-                        .setReorderingAllowed(true)
-                        .commit();
-                mFM.beginTransaction()
-                        .hide(fragment1)
-                        .addToBackStack(null)
-                        .setReorderingAllowed(true)
-                        .commit();
-                mFM.executePendingTransactions();
-            }
-        });
-
-        // the detach/attach should not affect the show/hide, so show/hide should cancel each other
-        assertEquals(0, fragment1.onShowCount);
-        assertEquals(1, fragment1.onHideCount);
-
-        FragmentTestUtil.popBackStackImmediate(mActivityRule, id, 0);
-        FragmentTestUtil.assertChildren(mContainer, fragment1);
-
-        assertEquals(0, fragment1.onShowCount);
-        assertEquals(1, fragment1.onHideCount);
-    }
-
-    // hiding and showing the same view should optimize out
-    @Test
-    public void removeRedundantShow() throws Throwable {
-        final CountCallsFragment fragment1 = new CountCallsFragment();
-        int id = mFM.beginTransaction()
-                .add(R.id.fragmentContainer, fragment1)
-                .addToBackStack(null)
-                .setReorderingAllowed(true)
-                .commit();
-        FragmentTestUtil.executePendingTransactions(mActivityRule);
-        assertEquals(0, fragment1.onShowCount);
-        assertEquals(0, fragment1.onHideCount);
-        FragmentTestUtil.assertChildren(mContainer, fragment1);
-
-        mInstrumentation.runOnMainSync(new Runnable() {
-            @Override
-            public void run() {
-                mFM.beginTransaction()
-                        .hide(fragment1)
-                        .addToBackStack(null)
-                        .setReorderingAllowed(true)
-                        .commit();
-                mFM.beginTransaction()
-                        .detach(fragment1)
-                        .addToBackStack(null)
-                        .setReorderingAllowed(true)
-                        .commit();
-                mFM.beginTransaction()
-                        .attach(fragment1)
-                        .addToBackStack(null)
-                        .setReorderingAllowed(true)
-                        .commit();
-                mFM.beginTransaction()
-                        .show(fragment1)
-                        .addToBackStack(null)
-                        .setReorderingAllowed(true)
-                        .commit();
-                mFM.executePendingTransactions();
-            }
-        });
-
-        FragmentTestUtil.assertChildren(mContainer, fragment1);
-        // can optimize out the show/hide
-        assertEquals(0, fragment1.onShowCount);
-        assertEquals(0, fragment1.onHideCount);
-
-        FragmentTestUtil.popBackStackImmediate(mActivityRule, id,
-                FragmentManager.POP_BACK_STACK_INCLUSIVE);
-        assertEquals(0, fragment1.onShowCount);
-        assertEquals(0, fragment1.onHideCount);
-    }
-
-    // The View order shouldn't be messed up by reordering -- a view that
-    // is optimized to not remove/add should be in its correct position after
-    // the transaction completes.
-    @Test
-    public void viewOrder() throws Throwable {
-        final CountCallsFragment fragment1 = new CountCallsFragment();
-        int id = mFM.beginTransaction()
-                .add(R.id.fragmentContainer, fragment1)
-                .addToBackStack(null)
-                .setReorderingAllowed(true)
-                .commit();
-        FragmentTestUtil.executePendingTransactions(mActivityRule);
-        FragmentTestUtil.assertChildren(mContainer, fragment1);
-
-        final CountCallsFragment fragment2 = new CountCallsFragment();
-
-        mInstrumentation.runOnMainSync(new Runnable() {
-            @Override
-            public void run() {
-                mFM.beginTransaction()
-                        .replace(R.id.fragmentContainer, fragment2)
-                        .addToBackStack(null)
-                        .setReorderingAllowed(true)
-                        .commit();
-                mFM.beginTransaction()
-                        .add(R.id.fragmentContainer, fragment1)
-                        .addToBackStack(null)
-                        .setReorderingAllowed(true)
-                        .commit();
-
-                mFM.executePendingTransactions();
-            }
-        });
-        FragmentTestUtil.assertChildren(mContainer, fragment2, fragment1);
-
-        FragmentTestUtil.popBackStackImmediate(mActivityRule, id, 0);
-        FragmentTestUtil.assertChildren(mContainer, fragment1);
-    }
-
-    // Popping an added transaction results in no operation
-    @Test
-    public void addPopBackStack() throws Throwable {
-        final CountCallsFragment fragment1 = new CountCallsFragment();
-        mInstrumentation.runOnMainSync(new Runnable() {
-            @Override
-            public void run() {
-                mFM.beginTransaction()
-                        .add(R.id.fragmentContainer, fragment1)
-                        .addToBackStack(null)
-                        .setReorderingAllowed(true)
-                        .commit();
-                mFM.popBackStack();
-                mFM.executePendingTransactions();
-            }
-        });
-        FragmentTestUtil.assertChildren(mContainer);
-
-        // Was never instantiated because it was popped before anything could happen
-        assertEquals(0, fragment1.onCreateViewCount);
-    }
-
-    // A non-back-stack transaction doesn't interfere with back stack add/pop
-    // optimization.
-    @Test
-    public void popNonBackStack() throws Throwable {
-        final CountCallsFragment fragment1 = new CountCallsFragment();
-        final CountCallsFragment fragment2 = new CountCallsFragment();
-        mInstrumentation.runOnMainSync(new Runnable() {
-            @Override
-            public void run() {
-                mFM.beginTransaction()
-                        .add(R.id.fragmentContainer, fragment1)
-                        .addToBackStack(null)
-                        .setReorderingAllowed(true)
-                        .commit();
-                mFM.beginTransaction()
-                        .replace(R.id.fragmentContainer, fragment2)
-                        .setReorderingAllowed(true)
-                        .commit();
-                mFM.popBackStack();
-                mFM.executePendingTransactions();
-            }
-        });
-        FragmentTestUtil.assertChildren(mContainer, fragment2);
-
-        // It should be optimized with the replace, so no View creation
-        assertEquals(0, fragment1.onCreateViewCount);
-    }
-
-    // When reordering is disabled, the transaction prior to the disabled reordering
-    // transaction should all be run prior to running the ordered transaction.
-    @Test
-    public void noReordering() throws Throwable {
-        final CountCallsFragment fragment1 = new CountCallsFragment();
-        final CountCallsFragment fragment2 = new CountCallsFragment();
-        mInstrumentation.runOnMainSync(new Runnable() {
-            @Override
-            public void run() {
-                mFM.beginTransaction()
-                        .add(R.id.fragmentContainer, fragment1)
-                        .addToBackStack(null)
-                        .setReorderingAllowed(true)
-                        .commit();
-                mFM.beginTransaction()
-                        .replace(R.id.fragmentContainer, fragment2)
-                        .addToBackStack(null)
-                        .setReorderingAllowed(false)
-                        .commit();
-                mFM.executePendingTransactions();
-            }
-        });
-        FragmentTestUtil.assertChildren(mContainer, fragment2);
-
-        // No reordering, so fragment1 should have created its View
-        assertEquals(1, fragment1.onCreateViewCount);
-    }
-
-    // Test that a fragment view that is created with focus has focus after the transaction
-    // completes.
-    @UiThreadTest
-    @Test
-    public void focusedView() throws Throwable {
-        mActivityRule.getActivity().setContentView(R.layout.double_container);
-        mContainer = (ViewGroup) mActivityRule.getActivity().findViewById(R.id.fragmentContainer1);
-        EditText firstEditText = new EditText(mContainer.getContext());
-        mContainer.addView(firstEditText);
-        firstEditText.requestFocus();
-
-        assertTrue(firstEditText.isFocused());
-        final CountCallsFragment fragment1 = new CountCallsFragment();
-        final CountCallsFragment fragment2 = new CountCallsFragment();
-        fragment2.setLayoutId(R.layout.with_edit_text);
-        mFM.beginTransaction()
-                .add(R.id.fragmentContainer2, fragment1)
-                .addToBackStack(null)
-                .setReorderingAllowed(true)
-                .commit();
-        mFM.beginTransaction()
-                .replace(R.id.fragmentContainer2, fragment2)
-                .addToBackStack(null)
-                .setReorderingAllowed(true)
-                .commit();
-        mFM.executePendingTransactions();
-        final View editText = fragment2.requireView().findViewById(R.id.editText);
-        assertTrue(editText.isFocused());
-        assertFalse(firstEditText.isFocused());
-    }
-}
diff --git a/fragment/src/androidTest/java/androidx/fragment/app/FragmentReorderingTest.kt b/fragment/src/androidTest/java/androidx/fragment/app/FragmentReorderingTest.kt
new file mode 100644
index 0000000..6877430
--- /dev/null
+++ b/fragment/src/androidTest/java/androidx/fragment/app/FragmentReorderingTest.kt
@@ -0,0 +1,633 @@
+/*
+ * 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.app.Instrumentation
+import android.view.View
+import android.view.ViewGroup
+import android.widget.EditText
+import androidx.fragment.app.test.FragmentTestActivity
+import androidx.fragment.test.R
+import androidx.test.annotation.UiThreadTest
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.rule.ActivityTestRule
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class FragmentReorderingTest {
+    @get:Rule
+    var activityRule = ActivityTestRule(FragmentTestActivity::class.java)
+
+    private lateinit var container: ViewGroup
+    private lateinit var fm: FragmentManager
+    private lateinit var instrumentation: Instrumentation
+
+    @Before
+    fun setup() {
+        FragmentTestUtil.setContentView(activityRule, R.layout.simple_container)
+        container = activityRule.activity.findViewById<View>(R.id.fragmentContainer) as ViewGroup
+        fm = activityRule.activity.supportFragmentManager
+        instrumentation = InstrumentationRegistry.getInstrumentation()
+    }
+
+    // Test that when you add and replace a fragment that only the replace's add
+    // actually creates a View.
+    @Test
+    fun addReplace() {
+        val fragment1 = CountCallsFragment()
+        val fragment2 = StrictViewFragment()
+        instrumentation.runOnMainSync {
+            fm.beginTransaction()
+                .add(R.id.fragmentContainer, fragment1)
+                .addToBackStack(null)
+                .setReorderingAllowed(true)
+                .commit()
+            fm.beginTransaction()
+                .replace(R.id.fragmentContainer, fragment2)
+                .addToBackStack(null)
+                .setReorderingAllowed(true)
+                .commit()
+            fm.executePendingTransactions()
+        }
+        assertThat(fragment1.onCreateViewCount).isEqualTo(0)
+        FragmentTestUtil.assertChildren(container, fragment2)
+
+        instrumentation.runOnMainSync {
+            fm.popBackStack()
+            fm.popBackStack()
+            fm.executePendingTransactions()
+        }
+        FragmentTestUtil.assertChildren(container)
+    }
+
+    // Test that it is possible to merge a transaction that starts with pop and adds
+    // the same view back again.
+    @Test
+    fun startWithPop() {
+        // Start with a single fragment on the back stack
+        val fragment1 = CountCallsFragment()
+        fm.beginTransaction()
+            .add(R.id.fragmentContainer, fragment1)
+            .addToBackStack(null)
+            .setReorderingAllowed(true)
+            .commit()
+
+        FragmentTestUtil.executePendingTransactions(activityRule)
+        FragmentTestUtil.assertChildren(container, fragment1)
+
+        // Now pop and add
+        instrumentation.runOnMainSync {
+            fm.popBackStack()
+            fm.beginTransaction()
+                .add(R.id.fragmentContainer, fragment1)
+                .addToBackStack(null)
+                .setReorderingAllowed(true)
+                .commit()
+            fm.executePendingTransactions()
+        }
+        FragmentTestUtil.assertChildren(container, fragment1)
+        assertThat(fragment1.onCreateViewCount).isEqualTo(1)
+
+        FragmentTestUtil.popBackStackImmediate(activityRule)
+        FragmentTestUtil.assertChildren(container)
+        assertThat(fragment1.onCreateViewCount).isEqualTo(1)
+    }
+
+    // Popping the back stack in the middle of other operations doesn't fool it.
+    @Test
+    fun middlePop() {
+        val fragment1 = CountCallsFragment()
+        val fragment2 = CountCallsFragment()
+        instrumentation.runOnMainSync {
+            fm.beginTransaction()
+                .add(R.id.fragmentContainer, fragment1)
+                .addToBackStack(null)
+                .setReorderingAllowed(true)
+                .commit()
+            fm.popBackStack()
+            fm.beginTransaction()
+                .add(R.id.fragmentContainer, fragment2)
+                .addToBackStack(null)
+                .setReorderingAllowed(true)
+                .commit()
+            fm.executePendingTransactions()
+        }
+        FragmentTestUtil.assertChildren(container, fragment2)
+        assertThat(fragment1.onAttachCount).isEqualTo(0)
+        assertThat(fragment2.onCreateViewCount).isEqualTo(1)
+
+        FragmentTestUtil.popBackStackImmediate(activityRule)
+        FragmentTestUtil.assertChildren(container)
+        assertThat(fragment2.onDetachCount).isEqualTo(1)
+    }
+
+    // ensure that removing a view after adding it is optimized into no
+    // View being created. Hide still gets notified.
+    @Test
+    fun removeRedundantRemove() {
+        val fragment1 = CountCallsFragment()
+        var id = -1
+        instrumentation.runOnMainSync {
+            id = fm.beginTransaction()
+                .add(R.id.fragmentContainer, fragment1)
+                .addToBackStack(null)
+                .setReorderingAllowed(true)
+                .commit()
+            fm.beginTransaction()
+                .hide(fragment1)
+                .addToBackStack(null)
+                .setReorderingAllowed(true)
+                .commit()
+            fm.beginTransaction()
+                .remove(fragment1)
+                .addToBackStack(null)
+                .setReorderingAllowed(true)
+                .commit()
+            fm.executePendingTransactions()
+        }
+        FragmentTestUtil.assertChildren(container)
+        assertThat(fragment1.onCreateViewCount).isEqualTo(0)
+        assertThat(fragment1.onHideCount).isEqualTo(1)
+        assertThat(fragment1.onShowCount).isEqualTo(0)
+        assertThat(fragment1.onDetachCount).isEqualTo(0)
+        assertThat(fragment1.onAttachCount).isEqualTo(1)
+
+        FragmentTestUtil.popBackStackImmediate(
+            activityRule,
+            id,
+            FragmentManager.POP_BACK_STACK_INCLUSIVE
+        )
+        FragmentTestUtil.assertChildren(container)
+        assertThat(fragment1.onCreateViewCount).isEqualTo(0)
+        assertThat(fragment1.onHideCount).isEqualTo(1)
+        assertThat(fragment1.onShowCount).isEqualTo(1)
+        assertThat(fragment1.onDetachCount).isEqualTo(1)
+        assertThat(fragment1.onAttachCount).isEqualTo(1)
+    }
+
+    // Ensure that removing and adding the same view results in no operation
+    @Test
+    fun removeRedundantAdd() {
+        val fragment1 = CountCallsFragment()
+        val id = fm.beginTransaction()
+            .add(R.id.fragmentContainer, fragment1)
+            .addToBackStack(null)
+            .setReorderingAllowed(true)
+            .commit()
+        FragmentTestUtil.executePendingTransactions(activityRule)
+        assertThat(fragment1.onCreateViewCount).isEqualTo(1)
+
+        instrumentation.runOnMainSync {
+            fm.beginTransaction()
+                .remove(fragment1)
+                .addToBackStack(null)
+                .setReorderingAllowed(true)
+                .commit()
+            fm.beginTransaction()
+                .add(R.id.fragmentContainer, fragment1)
+                .addToBackStack(null)
+                .setReorderingAllowed(true)
+                .commit()
+            fm.executePendingTransactions()
+        }
+
+        FragmentTestUtil.assertChildren(container, fragment1)
+        // should be optimized out
+        assertThat(fragment1.onCreateViewCount).isEqualTo(1)
+
+        fm.popBackStack(id, 0)
+        FragmentTestUtil.executePendingTransactions(activityRule)
+        FragmentTestUtil.assertChildren(container, fragment1)
+        // optimize out going back, too
+        assertThat(fragment1.onCreateViewCount).isEqualTo(1)
+    }
+
+    // detaching, then attaching results in on change. Hide still functions
+    @Test
+    fun removeRedundantAttach() {
+        val fragment1 = CountCallsFragment()
+        val id = fm.beginTransaction()
+            .add(R.id.fragmentContainer, fragment1)
+            .addToBackStack(null)
+            .setReorderingAllowed(true)
+            .commit()
+        FragmentTestUtil.executePendingTransactions(activityRule)
+        assertThat(fragment1.onAttachCount).isEqualTo(1)
+        FragmentTestUtil.assertChildren(container, fragment1)
+
+        instrumentation.runOnMainSync {
+            fm.beginTransaction()
+                .detach(fragment1)
+                .addToBackStack(null)
+                .setReorderingAllowed(true)
+                .commit()
+            fm.beginTransaction()
+                .hide(fragment1)
+                .addToBackStack(null)
+                .setReorderingAllowed(true)
+                .commit()
+            fm.beginTransaction()
+                .attach(fragment1)
+                .addToBackStack(null)
+                .setReorderingAllowed(true)
+                .commit()
+            fm.executePendingTransactions()
+        }
+
+        FragmentTestUtil.assertChildren(container, fragment1)
+        // can optimize out the detach/attach
+        assertThat(fragment1.onDestroyViewCount).isEqualTo(0)
+        assertThat(fragment1.onHideCount).isEqualTo(1)
+        assertThat(fragment1.onShowCount).isEqualTo(0)
+        assertThat(fragment1.onCreateViewCount).isEqualTo(1)
+        assertThat(fragment1.onAttachCount).isEqualTo(1)
+        assertThat(fragment1.onDetachCount).isEqualTo(0)
+
+        fm.popBackStack(id, 0)
+        FragmentTestUtil.executePendingTransactions(activityRule)
+        FragmentTestUtil.assertChildren(container, fragment1)
+
+        // optimized out again, but not the show
+        assertThat(fragment1.onDestroyViewCount).isEqualTo(0)
+        assertThat(fragment1.onHideCount).isEqualTo(1)
+        assertThat(fragment1.onShowCount).isEqualTo(1)
+        assertThat(fragment1.onCreateViewCount).isEqualTo(1)
+        assertThat(fragment1.onAttachCount).isEqualTo(1)
+        assertThat(fragment1.onDetachCount).isEqualTo(0)
+    }
+
+    // attaching, then detaching shouldn't result in a View being created
+    @Test
+    fun removeRedundantDetach() {
+        val fragment1 = CountCallsFragment()
+        val id = fm.beginTransaction()
+            .add(R.id.fragmentContainer, fragment1)
+            .detach(fragment1)
+            .addToBackStack(null)
+            .setReorderingAllowed(true)
+            .commit()
+        FragmentTestUtil.executePendingTransactions(activityRule)
+
+        // the add detach is not fully optimized out
+        assertThat(fragment1.onAttachCount).isEqualTo(1)
+        assertThat(fragment1.onDetachCount).isEqualTo(0)
+        assertThat(fragment1.isDetached).isTrue()
+        assertThat(fragment1.onCreateViewCount).isEqualTo(0)
+        FragmentTestUtil.assertChildren(container)
+
+        instrumentation.runOnMainSync {
+            fm.beginTransaction()
+                .attach(fragment1)
+                .addToBackStack(null)
+                .setReorderingAllowed(true)
+                .commit()
+            fm.beginTransaction()
+                .hide(fragment1)
+                .addToBackStack(null)
+                .setReorderingAllowed(true)
+                .commit()
+            fm.beginTransaction()
+                .detach(fragment1)
+                .addToBackStack(null)
+                .setReorderingAllowed(true)
+                .commit()
+            fm.executePendingTransactions()
+        }
+
+        FragmentTestUtil.assertChildren(container)
+        // can optimize out the attach/detach, and the hide call
+        assertThat(fragment1.onAttachCount).isEqualTo(1)
+        assertThat(fragment1.onDetachCount).isEqualTo(0)
+        assertThat(fragment1.onHideCount).isEqualTo(1)
+        assertThat(fragment1.isHidden).isTrue()
+        assertThat(fragment1.onShowCount).isEqualTo(0)
+
+        fm.popBackStack(id, 0)
+        FragmentTestUtil.executePendingTransactions(activityRule)
+        FragmentTestUtil.assertChildren(container)
+
+        // we can optimize out the attach/detach on the way back
+        assertThat(fragment1.onAttachCount).isEqualTo(1)
+        assertThat(fragment1.onDetachCount).isEqualTo(0)
+        assertThat(fragment1.onShowCount).isEqualTo(1)
+        assertThat(fragment1.onHideCount).isEqualTo(1)
+        assertThat(fragment1.isHidden).isFalse()
+    }
+
+    // show, then hide should optimize out
+    @Test
+    fun removeRedundantHide() {
+        val fragment1 = CountCallsFragment()
+        val id = fm.beginTransaction()
+            .add(R.id.fragmentContainer, fragment1)
+            .hide(fragment1)
+            .addToBackStack(null)
+            .setReorderingAllowed(true)
+            .commit()
+        FragmentTestUtil.executePendingTransactions(activityRule)
+        assertThat(fragment1.onShowCount).isEqualTo(0)
+        assertThat(fragment1.onHideCount).isEqualTo(1)
+        FragmentTestUtil.assertChildren(container, fragment1)
+
+        instrumentation.runOnMainSync {
+            fm.beginTransaction()
+                .show(fragment1)
+                .addToBackStack(null)
+                .setReorderingAllowed(true)
+                .commit()
+            fm.beginTransaction()
+                .remove(fragment1)
+                .addToBackStack(null)
+                .setReorderingAllowed(true)
+                .commit()
+            fm.beginTransaction()
+                .add(R.id.fragmentContainer, fragment1)
+                .addToBackStack(null)
+                .setReorderingAllowed(true)
+                .commit()
+            fm.beginTransaction()
+                .hide(fragment1)
+                .addToBackStack(null)
+                .setReorderingAllowed(true)
+                .commit()
+            fm.executePendingTransactions()
+        }
+
+        FragmentTestUtil.assertChildren(container, fragment1)
+        // optimize out hide/show
+        assertThat(fragment1.onShowCount).isEqualTo(0)
+        assertThat(fragment1.onHideCount).isEqualTo(1)
+
+        FragmentTestUtil.popBackStackImmediate(activityRule, id, 0)
+        FragmentTestUtil.assertChildren(container, fragment1)
+
+        // still optimized out
+        assertThat(fragment1.onShowCount).isEqualTo(0)
+        assertThat(fragment1.onHideCount).isEqualTo(1)
+
+        instrumentation.runOnMainSync {
+            fm.beginTransaction()
+                .show(fragment1)
+                .addToBackStack(null)
+                .setReorderingAllowed(true)
+                .commit()
+            fm.beginTransaction()
+                .hide(fragment1)
+                .addToBackStack(null)
+                .setReorderingAllowed(true)
+                .commit()
+            fm.executePendingTransactions()
+        }
+
+        // The show/hide can be optimized out and nothing should change.
+        FragmentTestUtil.assertChildren(container, fragment1)
+        assertThat(fragment1.onShowCount).isEqualTo(0)
+        assertThat(fragment1.onHideCount).isEqualTo(1)
+
+        FragmentTestUtil.popBackStackImmediate(activityRule, id, 0)
+        FragmentTestUtil.assertChildren(container, fragment1)
+
+        assertThat(fragment1.onShowCount).isEqualTo(0)
+        assertThat(fragment1.onHideCount).isEqualTo(1)
+
+        instrumentation.runOnMainSync {
+            fm.beginTransaction()
+                .show(fragment1)
+                .addToBackStack(null)
+                .setReorderingAllowed(true)
+                .commit()
+            fm.beginTransaction()
+                .detach(fragment1)
+                .addToBackStack(null)
+                .setReorderingAllowed(true)
+                .commit()
+            fm.beginTransaction()
+                .attach(fragment1)
+                .addToBackStack(null)
+                .setReorderingAllowed(true)
+                .commit()
+            fm.beginTransaction()
+                .hide(fragment1)
+                .addToBackStack(null)
+                .setReorderingAllowed(true)
+                .commit()
+            fm.executePendingTransactions()
+        }
+
+        // the detach/attach should not affect the show/hide, so show/hide should cancel each other
+        assertThat(fragment1.onShowCount).isEqualTo(0)
+        assertThat(fragment1.onHideCount).isEqualTo(1)
+
+        FragmentTestUtil.popBackStackImmediate(activityRule, id, 0)
+        FragmentTestUtil.assertChildren(container, fragment1)
+
+        assertThat(fragment1.onShowCount).isEqualTo(0)
+        assertThat(fragment1.onHideCount).isEqualTo(1)
+    }
+
+    // hiding and showing the same view should optimize out
+    @Test
+    fun removeRedundantShow() {
+        val fragment1 = CountCallsFragment()
+        val id = fm.beginTransaction()
+            .add(R.id.fragmentContainer, fragment1)
+            .addToBackStack(null)
+            .setReorderingAllowed(true)
+            .commit()
+        FragmentTestUtil.executePendingTransactions(activityRule)
+        assertThat(fragment1.onShowCount).isEqualTo(0)
+        assertThat(fragment1.onHideCount).isEqualTo(0)
+        FragmentTestUtil.assertChildren(container, fragment1)
+
+        instrumentation.runOnMainSync {
+            fm.beginTransaction()
+                .hide(fragment1)
+                .addToBackStack(null)
+                .setReorderingAllowed(true)
+                .commit()
+            fm.beginTransaction()
+                .detach(fragment1)
+                .addToBackStack(null)
+                .setReorderingAllowed(true)
+                .commit()
+            fm.beginTransaction()
+                .attach(fragment1)
+                .addToBackStack(null)
+                .setReorderingAllowed(true)
+                .commit()
+            fm.beginTransaction()
+                .show(fragment1)
+                .addToBackStack(null)
+                .setReorderingAllowed(true)
+                .commit()
+            fm.executePendingTransactions()
+        }
+
+        FragmentTestUtil.assertChildren(container, fragment1)
+        // can optimize out the show/hide
+        assertThat(fragment1.onShowCount).isEqualTo(0)
+        assertThat(fragment1.onHideCount).isEqualTo(0)
+
+        FragmentTestUtil.popBackStackImmediate(
+            activityRule, id,
+            FragmentManager.POP_BACK_STACK_INCLUSIVE
+        )
+        assertThat(fragment1.onShowCount).isEqualTo(0)
+        assertThat(fragment1.onHideCount).isEqualTo(0)
+    }
+
+    // The View order shouldn't be messed up by reordering -- a view that
+    // is optimized to not remove/add should be in its correct position after
+    // the transaction completes.
+    @Test
+    fun viewOrder() {
+        val fragment1 = CountCallsFragment()
+        val id = fm.beginTransaction()
+            .add(R.id.fragmentContainer, fragment1)
+            .addToBackStack(null)
+            .setReorderingAllowed(true)
+            .commit()
+        FragmentTestUtil.executePendingTransactions(activityRule)
+        FragmentTestUtil.assertChildren(container, fragment1)
+
+        val fragment2 = CountCallsFragment()
+
+        instrumentation.runOnMainSync {
+            fm.beginTransaction()
+                .replace(R.id.fragmentContainer, fragment2)
+                .addToBackStack(null)
+                .setReorderingAllowed(true)
+                .commit()
+            fm.beginTransaction()
+                .add(R.id.fragmentContainer, fragment1)
+                .addToBackStack(null)
+                .setReorderingAllowed(true)
+                .commit()
+
+            fm.executePendingTransactions()
+        }
+        FragmentTestUtil.assertChildren(container, fragment2, fragment1)
+
+        FragmentTestUtil.popBackStackImmediate(activityRule, id, 0)
+        FragmentTestUtil.assertChildren(container, fragment1)
+    }
+
+    // Popping an added transaction results in no operation
+    @Test
+    fun addPopBackStack() {
+        val fragment1 = CountCallsFragment()
+        instrumentation.runOnMainSync {
+            fm.beginTransaction()
+                .add(R.id.fragmentContainer, fragment1)
+                .addToBackStack(null)
+                .setReorderingAllowed(true)
+                .commit()
+            fm.popBackStack()
+            fm.executePendingTransactions()
+        }
+        FragmentTestUtil.assertChildren(container)
+
+        // Was never instantiated because it was popped before anything could happen
+        assertThat(fragment1.onCreateViewCount).isEqualTo(0)
+    }
+
+    // A non-back-stack transaction doesn't interfere with back stack add/pop
+    // optimization.
+    @Test
+    fun popNonBackStack() {
+        val fragment1 = CountCallsFragment()
+        val fragment2 = CountCallsFragment()
+        instrumentation.runOnMainSync {
+            fm.beginTransaction()
+                .add(R.id.fragmentContainer, fragment1)
+                .addToBackStack(null)
+                .setReorderingAllowed(true)
+                .commit()
+            fm.beginTransaction()
+                .replace(R.id.fragmentContainer, fragment2)
+                .setReorderingAllowed(true)
+                .commit()
+            fm.popBackStack()
+            fm.executePendingTransactions()
+        }
+        FragmentTestUtil.assertChildren(container, fragment2)
+
+        // It should be optimized with the replace, so no View creation
+        assertThat(fragment1.onCreateViewCount).isEqualTo(0)
+    }
+
+    // When reordering is disabled, the transaction prior to the disabled reordering
+    // transaction should all be run prior to running the ordered transaction.
+    @Test
+    fun noReordering() {
+        val fragment1 = CountCallsFragment()
+        val fragment2 = CountCallsFragment()
+        instrumentation.runOnMainSync {
+            fm.beginTransaction()
+                .add(R.id.fragmentContainer, fragment1)
+                .addToBackStack(null)
+                .setReorderingAllowed(true)
+                .commit()
+            fm.beginTransaction()
+                .replace(R.id.fragmentContainer, fragment2)
+                .addToBackStack(null)
+                .setReorderingAllowed(false)
+                .commit()
+            fm.executePendingTransactions()
+        }
+        FragmentTestUtil.assertChildren(container, fragment2)
+
+        // No reordering, so fragment1 should have created its View
+        assertThat(fragment1.onCreateViewCount).isEqualTo(1)
+    }
+
+    // Test that a fragment view that is created with focus has focus after the transaction
+    // completes.
+    @UiThreadTest
+    @Test
+    fun focusedView() {
+        activityRule.activity.setContentView(R.layout.double_container)
+        container = activityRule.activity.findViewById<View>(R.id.fragmentContainer1) as ViewGroup
+        val firstEditText = EditText(container.context)
+        container.addView(firstEditText)
+        firstEditText.requestFocus()
+
+        assertThat(firstEditText.isFocused).isTrue()
+        val fragment1 = CountCallsFragment()
+        val fragment2 = CountCallsFragment(R.layout.with_edit_text)
+        fm.beginTransaction()
+            .add(R.id.fragmentContainer2, fragment1)
+            .addToBackStack(null)
+            .setReorderingAllowed(true)
+            .commit()
+        fm.beginTransaction()
+            .replace(R.id.fragmentContainer2, fragment2)
+            .addToBackStack(null)
+            .setReorderingAllowed(true)
+            .commit()
+        fm.executePendingTransactions()
+        val editText = fragment2.requireView().findViewById<View>(R.id.editText)
+        assertThat(editText.isFocused).isTrue()
+        assertThat(firstEditText.isFocused).isFalse()
+    }
+}
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/FragmentTransactionTest.java b/fragment/src/androidTest/java/androidx/fragment/app/FragmentTransactionTest.java
deleted file mode 100644
index bae9332..0000000
--- a/fragment/src/androidTest/java/androidx/fragment/app/FragmentTransactionTest.java
+++ /dev/null
@@ -1,460 +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.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotSame;
-import static org.junit.Assert.assertSame;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-
-import android.app.Activity;
-import android.app.Instrumentation;
-import android.content.Intent;
-import android.os.Bundle;
-import android.os.SystemClock;
-import android.view.LayoutInflater;
-
-import androidx.annotation.ContentView;
-import androidx.annotation.NonNull;
-import androidx.fragment.app.test.FragmentTestActivity;
-import androidx.fragment.app.test.NewIntentActivity;
-import androidx.fragment.test.R;
-import androidx.test.annotation.UiThreadTest;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.MediumTest;
-import androidx.test.platform.app.InstrumentationRegistry;
-import androidx.test.rule.ActivityTestRule;
-
-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.Collection;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Tests usage of the {@link FragmentTransaction} class.
- */
-@MediumTest
-@RunWith(AndroidJUnit4.class)
-public class FragmentTransactionTest {
-
-    @Rule
-    public ActivityTestRule<FragmentTestActivity> mActivityRule =
-            new ActivityTestRule<>(FragmentTestActivity.class);
-
-    private FragmentTestActivity mActivity;
-    private int mOnBackStackChangedTimes;
-    private FragmentManager.OnBackStackChangedListener mOnBackStackChangedListener;
-
-    @Before
-    public void setUp() {
-        mActivity = mActivityRule.getActivity();
-        mOnBackStackChangedTimes = 0;
-        mOnBackStackChangedListener = new FragmentManager.OnBackStackChangedListener() {
-            @Override
-            public void onBackStackChanged() {
-                mOnBackStackChangedTimes++;
-            }
-        };
-        mActivity.getSupportFragmentManager()
-                .addOnBackStackChangedListener(mOnBackStackChangedListener);
-    }
-
-    @After
-    public void tearDown() {
-        mActivity.getSupportFragmentManager()
-                .removeOnBackStackChangedListener(mOnBackStackChangedListener);
-        mOnBackStackChangedListener = null;
-    }
-
-    @Test
-    @UiThreadTest
-    public void testAddTransactionWithValidFragment() {
-        final Fragment fragment = new CorrectFragment();
-        mActivity.getSupportFragmentManager().beginTransaction()
-                .add(R.id.content, fragment)
-                .addToBackStack(null)
-                .commit();
-        mActivity.getSupportFragmentManager().executePendingTransactions();
-        assertEquals(1, mOnBackStackChangedTimes);
-        assertTrue(fragment.isAdded());
-    }
-
-    @Test
-    @UiThreadTest
-    public void testAddTransactionWithPrivateFragment() {
-        final Fragment fragment = new PrivateFragment();
-        boolean exceptionThrown = false;
-        try {
-            mActivity.getSupportFragmentManager().beginTransaction()
-                    .add(R.id.content, fragment)
-                    .addToBackStack(null)
-                    .commit();
-            mActivity.getSupportFragmentManager().executePendingTransactions();
-            assertEquals(1, mOnBackStackChangedTimes);
-        } catch (IllegalStateException e) {
-            exceptionThrown = true;
-        } finally {
-            assertTrue("Exception should be thrown", exceptionThrown);
-            assertFalse("Fragment shouldn't be added", fragment.isAdded());
-        }
-    }
-
-    @Test
-    @UiThreadTest
-    public void testAddTransactionWithPackagePrivateFragment() {
-        final Fragment fragment = new PackagePrivateFragment();
-        boolean exceptionThrown = false;
-        try {
-            mActivity.getSupportFragmentManager().beginTransaction()
-                    .add(R.id.content, fragment)
-                    .addToBackStack(null)
-                    .commit();
-            mActivity.getSupportFragmentManager().executePendingTransactions();
-            assertEquals(1, mOnBackStackChangedTimes);
-        } catch (IllegalStateException e) {
-            exceptionThrown = true;
-        } finally {
-            assertTrue("Exception should be thrown", exceptionThrown);
-            assertFalse("Fragment shouldn't be added", fragment.isAdded());
-        }
-    }
-
-    @Test
-    @UiThreadTest
-    public void testAddTransactionWithAnonymousFragment() {
-        final Fragment fragment = new Fragment() {};
-        boolean exceptionThrown = false;
-        try {
-            mActivity.getSupportFragmentManager().beginTransaction()
-                    .add(R.id.content, fragment)
-                    .addToBackStack(null)
-                    .commit();
-            mActivity.getSupportFragmentManager().executePendingTransactions();
-            assertEquals(1, mOnBackStackChangedTimes);
-        } catch (IllegalStateException e) {
-            exceptionThrown = true;
-        } finally {
-            assertTrue("Exception should be thrown", exceptionThrown);
-            assertFalse("Fragment shouldn't be added", fragment.isAdded());
-        }
-    }
-
-    @Test
-    @UiThreadTest
-    public void testGetLayoutInflater() {
-        final OnGetLayoutInflaterFragment fragment1 = new OnGetLayoutInflaterFragment();
-        assertEquals(0, fragment1.onGetLayoutInflaterCalls);
-        mActivity.getSupportFragmentManager().beginTransaction()
-                .add(R.id.content, fragment1)
-                .addToBackStack(null)
-                .commit();
-        mActivity.getSupportFragmentManager().executePendingTransactions();
-        assertEquals(1, fragment1.onGetLayoutInflaterCalls);
-        assertEquals(fragment1.layoutInflater, fragment1.getLayoutInflater());
-        // getLayoutInflater() didn't force onGetLayoutInflater()
-        assertEquals(1, fragment1.onGetLayoutInflaterCalls);
-
-        LayoutInflater layoutInflater = fragment1.layoutInflater;
-        // Replacing fragment1 won't detach it, so the value won't be cleared
-        final OnGetLayoutInflaterFragment fragment2 = new OnGetLayoutInflaterFragment();
-        mActivity.getSupportFragmentManager().beginTransaction()
-                .replace(R.id.content, fragment2)
-                .addToBackStack(null)
-                .commit();
-        mActivity.getSupportFragmentManager().executePendingTransactions();
-
-        assertSame(layoutInflater, fragment1.getLayoutInflater());
-        assertEquals(1, fragment1.onGetLayoutInflaterCalls);
-
-        // Popping it should cause onCreateView again, so a new LayoutInflater...
-        mActivity.getSupportFragmentManager().popBackStackImmediate();
-        assertNotSame(layoutInflater, fragment1.getLayoutInflater());
-        assertEquals(2, fragment1.onGetLayoutInflaterCalls);
-        layoutInflater = fragment1.layoutInflater;
-        assertSame(layoutInflater, fragment1.getLayoutInflater());
-
-        // Popping it should detach it, clearing the cached value again
-        mActivity.getSupportFragmentManager().popBackStackImmediate();
-
-        // once it is detached, the getLayoutInflater() will default to throw
-        // an exception, but we've made it return null instead.
-        assertEquals(2, fragment1.onGetLayoutInflaterCalls);
-        try {
-            fragment1.getLayoutInflater();
-            fail("getLayoutInflater should throw when the Fragment is detached");
-        } catch (IllegalStateException e) {
-            // Expected
-        }
-        assertEquals(3, fragment1.onGetLayoutInflaterCalls);
-    }
-
-    @Test
-    @UiThreadTest
-    public void testAddTransactionWithNonStaticFragment() {
-        final Fragment fragment = new NonStaticFragment();
-        boolean exceptionThrown = false;
-        try {
-            mActivity.getSupportFragmentManager().beginTransaction()
-                    .add(R.id.content, fragment)
-                    .addToBackStack(null)
-                    .commit();
-            mActivity.getSupportFragmentManager().executePendingTransactions();
-            assertEquals(1, mOnBackStackChangedTimes);
-        } catch (IllegalStateException e) {
-            exceptionThrown = true;
-        } finally {
-            assertTrue("Exception should be thrown", exceptionThrown);
-            assertFalse("Fragment shouldn't be added", fragment.isAdded());
-        }
-    }
-
-    @Test
-    @UiThreadTest
-    public void testPostOnCommit() {
-        final boolean[] ran = new boolean[1];
-        FragmentManager fm = mActivityRule.getActivity().getSupportFragmentManager();
-        fm.beginTransaction().runOnCommit(new Runnable() {
-            @Override
-            public void run() {
-                ran[0] = true;
-            }
-        }).commit();
-        fm.executePendingTransactions();
-
-        assertTrue("runOnCommit runnable never ran", ran[0]);
-
-        ran[0] = false;
-
-        boolean threw = false;
-        try {
-            fm.beginTransaction().runOnCommit(new Runnable() {
-                @Override
-                public void run() {
-                    ran[0] = true;
-                }
-            }).addToBackStack(null).commit();
-        } catch (IllegalStateException ise) {
-            threw = true;
-        }
-
-        fm.executePendingTransactions();
-
-        assertTrue("runOnCommit was allowed to be called for back stack transaction",
-                threw);
-        assertFalse("runOnCommit runnable for back stack transaction was run", ran[0]);
-    }
-
-    // Ensure that getFragments() works during transactions, even if it is run off thread
-    @Test
-    public void getFragmentsOffThread() throws Throwable {
-        final FragmentManager fm = mActivity.getSupportFragmentManager();
-
-        // Make sure that adding a fragment works
-        Fragment fragment = new CorrectFragment();
-        fm.beginTransaction()
-                .add(R.id.content, fragment)
-                .addToBackStack(null)
-                .commit();
-
-        FragmentTestUtil.executePendingTransactions(mActivityRule);
-        Collection<Fragment> fragments = fm.getFragments();
-        assertEquals(1, fragments.size());
-        assertTrue(fragments.contains(fragment));
-
-        // Removed fragments shouldn't show
-        fm.beginTransaction()
-                .remove(fragment)
-                .addToBackStack(null)
-                .commit();
-        FragmentTestUtil.executePendingTransactions(mActivityRule);
-        assertTrue(fm.getFragments().isEmpty());
-
-        // Now try detached fragments
-        FragmentTestUtil.popBackStackImmediate(mActivityRule);
-        fm.beginTransaction()
-                .detach(fragment)
-                .addToBackStack(null)
-                .commit();
-        FragmentTestUtil.executePendingTransactions(mActivityRule);
-        assertTrue(fm.getFragments().isEmpty());
-
-        // Now try hidden fragments
-        FragmentTestUtil.popBackStackImmediate(mActivityRule);
-        fm.beginTransaction()
-                .hide(fragment)
-                .addToBackStack(null)
-                .commit();
-        FragmentTestUtil.executePendingTransactions(mActivityRule);
-        fragments = fm.getFragments();
-        assertEquals(1, fragments.size());
-        assertTrue(fragments.contains(fragment));
-
-        // And showing it again shouldn't change anything:
-        FragmentTestUtil.popBackStackImmediate(mActivityRule);
-        fragments = fm.getFragments();
-        assertEquals(1, fragments.size());
-        assertTrue(fragments.contains(fragment));
-
-        // Now pop back to the start state
-        FragmentTestUtil.popBackStackImmediate(mActivityRule);
-
-        // We can't force concurrency, but we can do it lots of times and hope that
-        // we hit it.
-        // Reset count here to verify afterwards
-
-        // Wait until we receive a OnBackStackChange callback for the total number of times
-        // specified by transactionCount times 2 (1 for adding, 1 for removal)
-        final int transactionCount = 100;
-        final CountDownLatch backStackLatch = new CountDownLatch(transactionCount * 2);
-        final FragmentManager.OnBackStackChangedListener countDownListener =
-                new FragmentManager.OnBackStackChangedListener() {
-
-            @Override
-            public void onBackStackChanged() {
-                backStackLatch.countDown();
-            }
-        };
-
-        fm.addOnBackStackChangedListener(countDownListener);
-
-        for (int i = 0; i < transactionCount; i++) {
-            Fragment fragment2 = new CorrectFragment();
-            fm.beginTransaction()
-                    .add(R.id.content, fragment2)
-                    .addToBackStack(null)
-                    .commit();
-            getFragmentsUntilSize(1);
-
-            fm.popBackStack();
-            getFragmentsUntilSize(0);
-        }
-
-        backStackLatch.await();
-
-        fm.removeOnBackStackChangedListener(countDownListener);
-    }
-
-    /**
-     * When a FragmentManager is detached, it should allow commitAllowingStateLoss()
-     * and commitNowAllowingStateLoss() by just dropping the transaction.
-     */
-    @Test
-    public void commitAllowStateLossDetached() throws Throwable {
-        Fragment fragment1 = new CorrectFragment();
-        mActivity.getSupportFragmentManager()
-                .beginTransaction()
-                .add(fragment1, "1")
-                .commit();
-        FragmentTestUtil.executePendingTransactions(mActivityRule);
-        final FragmentManager fm = fragment1.getChildFragmentManager();
-        mActivity.getSupportFragmentManager()
-                .beginTransaction()
-                .remove(fragment1)
-                .commit();
-        FragmentTestUtil.executePendingTransactions(mActivityRule);
-        Assert.assertEquals(0, mActivity.getSupportFragmentManager().getFragments().size());
-        assertEquals(0, fm.getFragments().size());
-
-        // Now the fragment1's fragment manager should allow commitAllowingStateLoss
-        // by doing nothing since it has been detached.
-        Fragment fragment2 = new CorrectFragment();
-        fm.beginTransaction()
-                .add(fragment2, "2")
-                .commitAllowingStateLoss();
-        FragmentTestUtil.executePendingTransactions(mActivityRule);
-        assertEquals(0, fm.getFragments().size());
-
-        // It should also allow commitNowAllowingStateLoss by doing nothing
-        mActivityRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                Fragment fragment3 = new CorrectFragment();
-                fm.beginTransaction()
-                        .add(fragment3, "3")
-                        .commitNowAllowingStateLoss();
-                assertEquals(0, fm.getFragments().size());
-            }
-        });
-    }
-
-    /**
-     * onNewIntent() should note that the state is not saved so that child fragment
-     * managers can execute transactions.
-     */
-    @Test
-    public void newIntentUnlocks() throws Throwable {
-        Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
-        Intent intent1 = new Intent(mActivity, NewIntentActivity.class)
-                .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-        NewIntentActivity newIntentActivity =
-                (NewIntentActivity) instrumentation.startActivitySync(intent1);
-        FragmentTestUtil.waitForExecution(mActivityRule);
-
-        Intent intent2 = new Intent(mActivity, FragmentTestActivity.class);
-        intent2.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-        Activity coveringActivity = instrumentation.startActivitySync(intent2);
-        FragmentTestUtil.waitForExecution(mActivityRule);
-
-        Intent intent3 = new Intent(mActivity, NewIntentActivity.class)
-                .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-        mActivity.startActivity(intent3);
-        assertTrue(newIntentActivity.newIntent.await(1, TimeUnit.SECONDS));
-        FragmentTestUtil.waitForExecution(mActivityRule);
-
-        for (Fragment fragment : newIntentActivity.getSupportFragmentManager().getFragments()) {
-            // There really should only be one fragment in newIntentActivity.
-            assertEquals(1, fragment.getChildFragmentManager().getFragments().size());
-        }
-    }
-
-    private void getFragmentsUntilSize(int expectedSize) {
-        final long endTime = SystemClock.uptimeMillis() + 3000;
-
-        do {
-            assertTrue(SystemClock.uptimeMillis() < endTime);
-        } while (mActivity.getSupportFragmentManager().getFragments().size() != expectedSize);
-    }
-
-    public static class CorrectFragment extends Fragment {}
-
-    private static class PrivateFragment extends Fragment {}
-
-    static class PackagePrivateFragment extends Fragment {}
-
-    private class NonStaticFragment extends Fragment {}
-
-    @ContentView(R.layout.fragment_a)
-    public static class OnGetLayoutInflaterFragment extends Fragment {
-        public int onGetLayoutInflaterCalls = 0;
-        public LayoutInflater layoutInflater;
-
-        @NonNull
-        @Override
-        public LayoutInflater onGetLayoutInflater(Bundle savedInstanceState) {
-            onGetLayoutInflaterCalls++;
-            layoutInflater = super.onGetLayoutInflater(savedInstanceState);
-            return layoutInflater;
-        }
-    }
-}
diff --git a/fragment/src/androidTest/java/androidx/fragment/app/FragmentTransactionTest.kt b/fragment/src/androidTest/java/androidx/fragment/app/FragmentTransactionTest.kt
new file mode 100644
index 0000000..0c1e3fb
--- /dev/null
+++ b/fragment/src/androidTest/java/androidx/fragment/app/FragmentTransactionTest.kt
@@ -0,0 +1,421 @@
+/*
+ * 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 org.junit.Assert.fail
+
+import android.content.Intent
+import android.os.Bundle
+import android.os.SystemClock
+import android.view.LayoutInflater
+
+import androidx.annotation.ContentView
+import androidx.fragment.app.Fragment
+import androidx.fragment.app.FragmentManager
+import androidx.fragment.app.FragmentTestUtil
+import androidx.fragment.test.R
+import androidx.test.annotation.UiThreadTest
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.MediumTest
+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.After
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import java.util.concurrent.CountDownLatch
+import java.util.concurrent.TimeUnit
+
+/**
+ * Tests usage of the [FragmentTransaction] class.
+ */
+@MediumTest
+@RunWith(AndroidJUnit4::class)
+class FragmentTransactionTest {
+
+    @get:Rule
+    var activityRule = ActivityTestRule(FragmentTestActivity::class.java)
+
+    private lateinit var activity: FragmentTestActivity
+    private var onBackStackChangedTimes: Int = 0
+    private lateinit var onBackStackChangedListener: FragmentManager.OnBackStackChangedListener
+
+    @Before
+    fun setUp() {
+        activity = activityRule.activity
+        onBackStackChangedTimes = 0
+        onBackStackChangedListener =
+                FragmentManager.OnBackStackChangedListener { onBackStackChangedTimes++ }
+        activity.supportFragmentManager.addOnBackStackChangedListener(onBackStackChangedListener)
+    }
+
+    @After
+    fun tearDown() {
+        activity.supportFragmentManager.removeOnBackStackChangedListener(onBackStackChangedListener)
+    }
+
+    @Test
+    @UiThreadTest
+    fun testAddTransactionWithValidFragment() {
+        val fragment = CorrectFragment()
+        activity.supportFragmentManager.beginTransaction()
+            .add(R.id.content, fragment)
+            .addToBackStack(null)
+            .commit()
+        activity.supportFragmentManager.executePendingTransactions()
+        assertThat(onBackStackChangedTimes).isEqualTo(1)
+        assertThat(fragment.isAdded).isTrue()
+    }
+
+    @Test
+    @UiThreadTest
+    fun testAddTransactionWithPrivateFragment() {
+        val fragment = PrivateFragment()
+        try {
+            activity.supportFragmentManager.beginTransaction()
+                .add(R.id.content, fragment)
+                .addToBackStack(null)
+                .commit()
+            activity.supportFragmentManager.executePendingTransactions()
+            assertThat(onBackStackChangedTimes).isEqualTo(1)
+        } catch (e: IllegalStateException) {
+            assertThat(e)
+                .hasMessageThat().contains("Fragment " + fragment.javaClass.canonicalName +
+                        " must be a public static class to be  properly recreated from instance " +
+                        "state.")
+        } finally {
+            assertWithMessage("Fragment shouldn't be added").that(fragment.isAdded).isFalse()
+        }
+    }
+
+    @Test
+    @UiThreadTest
+    fun testAddTransactionWithPackagePrivateFragment() {
+        val fragment = OuterPackagePrivateFragment.PackagePrivateFragment()
+        try {
+            activity.supportFragmentManager.beginTransaction()
+                .add(R.id.content, fragment)
+                .addToBackStack(null)
+                .commit()
+            activity.supportFragmentManager.executePendingTransactions()
+            assertThat(onBackStackChangedTimes).isEqualTo(1)
+        } catch (e: IllegalStateException) {
+            assertThat(e).hasMessageThat().contains("Fragment " + fragment.javaClass.canonicalName +
+                    " must be a public static class to be  properly recreated from instance " +
+                    "state.")
+        } finally {
+            assertWithMessage("Fragment shouldn't be added").that(fragment.isAdded).isFalse()
+        }
+    }
+
+    @Test
+    @UiThreadTest
+    fun testAddTransactionWithAnonymousFragment() {
+        val fragment = object : Fragment() {}
+        try {
+            activity.supportFragmentManager.beginTransaction()
+                .add(R.id.content, fragment)
+                .addToBackStack(null)
+                .commit()
+            activity.supportFragmentManager.executePendingTransactions()
+            assertThat(onBackStackChangedTimes).isEqualTo(1)
+        } catch (e: IllegalStateException) {
+            assertThat(e).hasMessageThat().contains("Fragment " + fragment.javaClass.canonicalName +
+                    " must be a public static class to be  properly recreated from instance state.")
+        } finally {
+            assertWithMessage("Fragment shouldn't be added").that(fragment.isAdded).isFalse()
+        }
+    }
+
+    @Test
+    @UiThreadTest
+    fun testGetLayoutInflater() {
+        val fragment1 = OnGetLayoutInflaterFragment()
+        assertThat(fragment1.onGetLayoutInflaterCalls).isEqualTo(0)
+        activity.supportFragmentManager.beginTransaction()
+            .add(R.id.content, fragment1)
+            .addToBackStack(null)
+            .commit()
+        activity.supportFragmentManager.executePendingTransactions()
+        assertThat(fragment1.onGetLayoutInflaterCalls).isEqualTo(1)
+        assertThat(fragment1.layoutInflater).isEqualTo(fragment1.baseLayoutInflater)
+        // getBaseLayoutInflater() didn't force onGetLayoutInflater()
+        assertThat(fragment1.onGetLayoutInflaterCalls).isEqualTo(1)
+
+        var layoutInflater = fragment1.baseLayoutInflater
+        // Replacing fragment1 won't detach it, so the value won't be cleared
+        val fragment2 = OnGetLayoutInflaterFragment()
+        activity.supportFragmentManager.beginTransaction()
+            .replace(R.id.content, fragment2)
+            .addToBackStack(null)
+            .commit()
+        activity.supportFragmentManager.executePendingTransactions()
+
+        assertThat(fragment1.layoutInflater).isSameAs(layoutInflater)
+        assertThat(fragment1.onGetLayoutInflaterCalls).isEqualTo(1)
+
+        // Popping it should cause onCreateView again, so a new LayoutInflater...
+        activity.supportFragmentManager.popBackStackImmediate()
+        assertThat(fragment1.layoutInflater).isNotSameAs(layoutInflater)
+        assertThat(fragment1.onGetLayoutInflaterCalls).isEqualTo(2)
+        layoutInflater = fragment1.baseLayoutInflater
+        assertThat(fragment1.layoutInflater).isSameAs(layoutInflater)
+
+        // Popping it should detach it, clearing the cached value again
+        activity.supportFragmentManager.popBackStackImmediate()
+
+        // once it is detached, the getBaseLayoutInflater() will default to throw
+        // an exception, but we've made it return null instead.
+        assertThat(fragment1.onGetLayoutInflaterCalls).isEqualTo(2)
+        try {
+            fragment1.layoutInflater
+            fail("getLayoutInflater should throw when the Fragment is detached")
+        } catch (e: IllegalStateException) {
+            assertThat(e).hasMessageThat().contains("onGetLayoutInflater() cannot be executed " +
+                    "until the Fragment is attached to the FragmentManager.")
+        }
+
+        assertThat(fragment1.onGetLayoutInflaterCalls).isEqualTo(3)
+    }
+
+    @Test
+    @UiThreadTest
+    fun testAddTransactionWithNonStaticFragment() {
+        val fragment = NonStaticFragment()
+        try {
+            activity.supportFragmentManager.beginTransaction()
+                .add(R.id.content, fragment)
+                .addToBackStack(null)
+                .commit()
+            activity.supportFragmentManager.executePendingTransactions()
+            assertThat(onBackStackChangedTimes).isEqualTo(1)
+        } catch (e: IllegalStateException) {
+            assertThat(e).hasMessageThat().contains("Fragment " + fragment.javaClass.canonicalName +
+                    " must be a public static class to be  properly recreated from instance state.")
+        } finally {
+            assertWithMessage("Fragment shouldn't be added").that(fragment.isAdded).isFalse()
+        }
+    }
+
+    @Test
+    @UiThreadTest
+    fun testPostOnCommit() {
+        var ran = false
+        val fm = activityRule.activity.supportFragmentManager
+        fm.beginTransaction().runOnCommit { ran = true }.commit()
+        fm.executePendingTransactions()
+
+        assertWithMessage("runOnCommit runnable never ran").that(ran).isTrue()
+
+        ran = false
+
+        try {
+            fm.beginTransaction().runOnCommit { ran = true }.addToBackStack(null).commit()
+        } catch (e: IllegalStateException) {
+            assertThat(e).hasMessageThat().contains("This FragmentTransaction is not allowed to" +
+                    " be added to the back stack.")
+        }
+
+        fm.executePendingTransactions()
+
+        assertWithMessage("runOnCommit runnable for back stack transaction was run")
+            .that(ran)
+            .isFalse()
+    }
+
+    // Ensure that getFragments() works during transactions, even if it is run off thread
+    @Test
+    fun getFragmentsOffThread() {
+        val fm = activity.supportFragmentManager
+
+        // Make sure that adding a fragment works
+        val fragment = CorrectFragment()
+        fm.beginTransaction()
+            .add(R.id.content, fragment)
+            .addToBackStack(null)
+            .commit()
+
+        FragmentTestUtil.executePendingTransactions(activityRule)
+        var fragments: Collection<Fragment> = fm.fragments
+        assertThat(fragments.size).isEqualTo(1)
+        assertThat(fragments.contains(fragment)).isTrue()
+
+        // Removed fragments shouldn't show
+        fm.beginTransaction()
+            .remove(fragment)
+            .addToBackStack(null)
+            .commit()
+        FragmentTestUtil.executePendingTransactions(activityRule)
+        assertThat(fm.fragments.isEmpty()).isTrue()
+
+        // Now try detached fragments
+        FragmentTestUtil.popBackStackImmediate(activityRule)
+        fm.beginTransaction()
+            .detach(fragment)
+            .addToBackStack(null)
+            .commit()
+        FragmentTestUtil.executePendingTransactions(activityRule)
+        assertThat(fm.fragments.isEmpty()).isTrue()
+
+        // Now try hidden fragments
+        FragmentTestUtil.popBackStackImmediate(activityRule)
+        fm.beginTransaction()
+            .hide(fragment)
+            .addToBackStack(null)
+            .commit()
+        FragmentTestUtil.executePendingTransactions(activityRule)
+        fragments = fm.fragments
+        assertThat(fragments.size).isEqualTo(1)
+        assertThat(fragments.contains(fragment)).isTrue()
+
+        // And showing it again shouldn't change anything:
+        FragmentTestUtil.popBackStackImmediate(activityRule)
+        fragments = fm.fragments
+        assertThat(fragments.size).isEqualTo(1)
+        assertThat(fragments.contains(fragment)).isTrue()
+
+        // Now pop back to the start state
+        FragmentTestUtil.popBackStackImmediate(activityRule)
+
+        // We can't force concurrency, but we can do it lots of times and hope that
+        // we hit it.
+        // Reset count here to verify afterwards
+
+        // Wait until we receive a OnBackStackChange callback for the total number of times
+        // specified by transactionCount times 2 (1 for adding, 1 for removal)
+        val transactionCount = 100
+        val backStackLatch = CountDownLatch(transactionCount * 2)
+        val countDownListener =
+            FragmentManager.OnBackStackChangedListener { backStackLatch.countDown() }
+
+        fm.addOnBackStackChangedListener(countDownListener)
+
+        for (i in 0 until transactionCount) {
+            val fragment2 = CorrectFragment()
+            fm.beginTransaction()
+                .add(R.id.content, fragment2)
+                .addToBackStack(null)
+                .commit()
+            getFragmentsUntilSize(1)
+
+            fm.popBackStack()
+            getFragmentsUntilSize(0)
+        }
+
+        backStackLatch.await()
+
+        fm.removeOnBackStackChangedListener(countDownListener)
+    }
+
+    /**
+     * When a FragmentManager is detached, it should allow commitAllowingStateLoss()
+     * and commitNowAllowingStateLoss() by just dropping the transaction.
+     */
+    @Test
+    fun commitAllowStateLossDetached() {
+        val fragment1 = CorrectFragment()
+        activity.supportFragmentManager
+            .beginTransaction()
+            .add(fragment1, "1")
+            .commit()
+        FragmentTestUtil.executePendingTransactions(activityRule)
+        val fm = fragment1.childFragmentManager
+        activity.supportFragmentManager
+            .beginTransaction()
+            .remove(fragment1)
+            .commit()
+        FragmentTestUtil.executePendingTransactions(activityRule)
+        assertThat(activity.supportFragmentManager.fragments.size).isEqualTo(0)
+        assertThat(fm.fragments.size).isEqualTo(0)
+
+        // Now the fragment1's fragment manager should allow commitAllowingStateLoss
+        // by doing nothing since it has been detached.
+        val fragment2 = CorrectFragment()
+        fm.beginTransaction()
+            .add(fragment2, "2")
+            .commitAllowingStateLoss()
+        FragmentTestUtil.executePendingTransactions(activityRule)
+        assertThat(fm.fragments.size).isEqualTo(0)
+
+        // It should also allow commitNowAllowingStateLoss by doing nothing
+        activityRule.runOnUiThread {
+            val fragment3 = CorrectFragment()
+            fm.beginTransaction()
+                .add(fragment3, "3")
+                .commitNowAllowingStateLoss()
+            assertThat(fm.fragments.size).isEqualTo(0)
+        }
+    }
+
+    /**
+     * onNewIntent() should note that the state is not saved so that child fragment
+     * managers can execute transactions.
+     */
+    @Test
+    fun newIntentUnlocks() {
+        val instrumentation = InstrumentationRegistry.getInstrumentation()
+        val intent1 = Intent(activity, NewIntentActivity::class.java)
+            .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+        val newIntentActivity = instrumentation.startActivitySync(intent1) as NewIntentActivity
+        FragmentTestUtil.waitForExecution(activityRule)
+
+        val intent2 = Intent(activity, FragmentTestActivity::class.java)
+        intent2.flags = Intent.FLAG_ACTIVITY_NEW_TASK
+        instrumentation.startActivitySync(intent2)
+        FragmentTestUtil.waitForExecution(activityRule)
+
+        val intent3 = Intent(activity, NewIntentActivity::class.java)
+            .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+        activity.startActivity(intent3)
+        assertThat(newIntentActivity.newIntent.await(1, TimeUnit.SECONDS)).isTrue()
+        FragmentTestUtil.waitForExecution(activityRule)
+
+        for (fragment in newIntentActivity.supportFragmentManager.fragments) {
+            // There really should only be one fragment in newIntentActivity.
+            assertThat(fragment.childFragmentManager.fragments.size).isEqualTo(1)
+        }
+    }
+
+    private fun getFragmentsUntilSize(expectedSize: Int) {
+        val endTime = SystemClock.uptimeMillis() + 3000
+
+        do {
+            assertThat(SystemClock.uptimeMillis() < endTime).isTrue()
+        } while (activity.supportFragmentManager.fragments.size != expectedSize)
+    }
+
+    class CorrectFragment : Fragment()
+
+    private class PrivateFragment : Fragment()
+
+    private inner class NonStaticFragment : Fragment()
+
+    @ContentView(R.layout.fragment_a)
+    class OnGetLayoutInflaterFragment : Fragment() {
+        var onGetLayoutInflaterCalls = 0
+        lateinit var baseLayoutInflater: LayoutInflater
+
+        override fun onGetLayoutInflater(savedInstanceState: Bundle?): LayoutInflater {
+            onGetLayoutInflaterCalls++
+            baseLayoutInflater = super.onGetLayoutInflater(savedInstanceState)
+            return baseLayoutInflater
+        }
+    }
+}
diff --git a/fragment/src/androidTest/java/androidx/fragment/app/FragmentTransitionTest.java b/fragment/src/androidTest/java/androidx/fragment/app/FragmentTransitionTest.java
deleted file mode 100644
index 04cb401..0000000
--- a/fragment/src/androidTest/java/androidx/fragment/app/FragmentTransitionTest.java
+++ /dev/null
@@ -1,1155 +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.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.reset;
-import static org.mockito.Mockito.verify;
-
-import android.app.Instrumentation;
-import android.graphics.Rect;
-import android.os.Build;
-import android.os.Bundle;
-import android.transition.TransitionSet;
-import android.view.View;
-
-import androidx.core.app.SharedElementCallback;
-import androidx.fragment.app.test.FragmentTestActivity;
-import androidx.fragment.test.R;
-import androidx.test.filters.MediumTest;
-import androidx.test.filters.SdkSuppress;
-import androidx.test.platform.app.InstrumentationRegistry;
-import androidx.test.rule.ActivityTestRule;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-import org.mockito.ArgumentCaptor;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
-
-@MediumTest
-@RunWith(Parameterized.class)
-@SdkSuppress(minSdkVersion = Build.VERSION_CODES.LOLLIPOP)
-public class FragmentTransitionTest {
-    private final boolean mReorderingAllowed;
-
-    @Parameterized.Parameters
-    public static Object[] data() {
-        return new Boolean[] {
-                false, true
-        };
-    }
-
-    @Rule
-    public ActivityTestRule<FragmentTestActivity> mActivityRule =
-            new ActivityTestRule<FragmentTestActivity>(FragmentTestActivity.class);
-
-    private Instrumentation mInstrumentation;
-    private FragmentManager mFragmentManager;
-    private int mOnBackStackChangedTimes;
-    private FragmentManager.OnBackStackChangedListener mOnBackStackChangedListener;
-
-    public FragmentTransitionTest(final boolean reordering) {
-        mReorderingAllowed = reordering;
-    }
-
-    @Before
-    public void setup() throws Throwable {
-        mInstrumentation = InstrumentationRegistry.getInstrumentation();
-        mFragmentManager = mActivityRule.getActivity().getSupportFragmentManager();
-        FragmentTestUtil.setContentView(mActivityRule, R.layout.simple_container);
-        mOnBackStackChangedTimes = 0;
-        mOnBackStackChangedListener = new FragmentManager.OnBackStackChangedListener() {
-            @Override
-            public void onBackStackChanged() {
-                mOnBackStackChangedTimes++;
-            }
-        };
-        mFragmentManager.addOnBackStackChangedListener(mOnBackStackChangedListener);
-    }
-
-    @After
-    public void teardown() throws Throwable {
-        mFragmentManager.removeOnBackStackChangedListener(mOnBackStackChangedListener);
-        mOnBackStackChangedListener = null;
-    }
-
-    // Test that normal view transitions (enter, exit, reenter, return) run with
-    // a single fragment.
-    @Test
-    public void enterExitTransitions() throws Throwable {
-        // enter transition
-        TransitionFragment fragment = setupInitialFragment();
-        final View blue = findBlue();
-        final View green = findBlue();
-
-        // exit transition
-        mFragmentManager.beginTransaction()
-                .setReorderingAllowed(mReorderingAllowed)
-                .remove(fragment)
-                .addToBackStack(null)
-                .commit();
-
-        fragment.waitForTransition();
-        verifyAndClearTransition(fragment.exitTransition, null, green, blue);
-        verifyNoOtherTransitions(fragment);
-        assertEquals(2, mOnBackStackChangedTimes);
-
-        // reenter transition
-        FragmentTestUtil.popBackStackImmediate(mActivityRule);
-        fragment.waitForTransition();
-        final View green2 = findGreen();
-        final View blue2 = findBlue();
-        verifyAndClearTransition(fragment.reenterTransition, null, green2, blue2);
-        verifyNoOtherTransitions(fragment);
-        assertEquals(3, mOnBackStackChangedTimes);
-
-        // return transition
-        FragmentTestUtil.popBackStackImmediate(mActivityRule);
-        fragment.waitForTransition();
-        verifyAndClearTransition(fragment.returnTransition, null, green2, blue2);
-        verifyNoOtherTransitions(fragment);
-        assertEquals(4, mOnBackStackChangedTimes);
-    }
-
-    // Test that shared elements transition from one fragment to the next
-    // and back during pop.
-    @Test
-    public void sharedElement() throws Throwable {
-        TransitionFragment fragment1 = setupInitialFragment();
-
-        // Now do a transition to scene2
-        TransitionFragment fragment2 = new TransitionFragment();
-        fragment2.setLayoutId(R.layout.scene2);
-
-        verifyTransition(fragment1, fragment2, "blueSquare");
-
-        // Now pop the back stack
-        verifyPopTransition(1, fragment2, fragment1);
-    }
-
-    // Test that shared element transitions through multiple fragments work together
-    @Test
-    public void intermediateFragment() throws Throwable {
-        TransitionFragment fragment1 = setupInitialFragment();
-
-        final TransitionFragment fragment2 = new TransitionFragment();
-        fragment2.setLayoutId(R.layout.scene3);
-
-        verifyTransition(fragment1, fragment2, "shared");
-
-        final TransitionFragment fragment3 = new TransitionFragment();
-        fragment3.setLayoutId(R.layout.scene2);
-
-        verifyTransition(fragment2, fragment3, "blueSquare");
-
-        // Should transfer backwards when popping multiple:
-        verifyPopTransition(2, fragment3, fragment1, fragment2);
-    }
-
-    // Adding/removing the same fragment multiple times shouldn't mess anything up
-    @Test
-    public void removeAdded() throws Throwable {
-        final TransitionFragment fragment1 = setupInitialFragment();
-
-        final View startBlue = findBlue();
-        final View startGreen = findGreen();
-
-        final TransitionFragment fragment2 = new TransitionFragment();
-        fragment2.setLayoutId(R.layout.scene2);
-
-        mInstrumentation.runOnMainSync(new Runnable() {
-            @Override
-            public void run() {
-                mFragmentManager.beginTransaction()
-                        .setReorderingAllowed(mReorderingAllowed)
-                        .replace(R.id.fragmentContainer, fragment2)
-                        .replace(R.id.fragmentContainer, fragment1)
-                        .replace(R.id.fragmentContainer, fragment2)
-                        .addToBackStack(null)
-                        .commit();
-            }
-        });
-        FragmentTestUtil.waitForExecution(mActivityRule);
-        assertEquals(2, mOnBackStackChangedTimes);
-
-        // should be a normal transition from fragment1 to fragment2
-        fragment2.waitForTransition();
-        final View endBlue = findBlue();
-        final View endGreen = findGreen();
-        verifyAndClearTransition(fragment1.exitTransition, null, startBlue, startGreen);
-        verifyAndClearTransition(fragment2.enterTransition, null, endBlue, endGreen);
-        verifyNoOtherTransitions(fragment1);
-        verifyNoOtherTransitions(fragment2);
-
-        // Pop should also do the same thing
-        FragmentTestUtil.popBackStackImmediate(mActivityRule);
-        assertEquals(3, mOnBackStackChangedTimes);
-
-        fragment1.waitForTransition();
-        final View popBlue = findBlue();
-        final View popGreen = findGreen();
-        verifyAndClearTransition(fragment1.reenterTransition, null, popBlue, popGreen);
-        verifyAndClearTransition(fragment2.returnTransition, null, endBlue, endGreen);
-        verifyNoOtherTransitions(fragment1);
-        verifyNoOtherTransitions(fragment2);
-    }
-
-    // Make sure that shared elements on two different fragment containers don't interact
-    @Test
-    public void crossContainer() throws Throwable {
-        FragmentTestUtil.setContentView(mActivityRule, R.layout.double_container);
-        TransitionFragment fragment1 = new TransitionFragment();
-        fragment1.setLayoutId(R.layout.scene1);
-        TransitionFragment fragment2 = new TransitionFragment();
-        fragment2.setLayoutId(R.layout.scene1);
-        mFragmentManager.beginTransaction()
-                .setReorderingAllowed(mReorderingAllowed)
-                .add(R.id.fragmentContainer1, fragment1)
-                .add(R.id.fragmentContainer2, fragment2)
-                .addToBackStack(null)
-                .commit();
-        FragmentTestUtil.waitForExecution(mActivityRule);
-        assertEquals(1, mOnBackStackChangedTimes);
-
-        fragment1.waitForTransition();
-        final View greenSquare1 = findViewById(fragment1, R.id.greenSquare);
-        final View blueSquare1 = findViewById(fragment1, R.id.blueSquare);
-        verifyAndClearTransition(fragment1.enterTransition, null, greenSquare1, blueSquare1);
-        verifyNoOtherTransitions(fragment1);
-        fragment2.waitForTransition();
-        final View greenSquare2 = findViewById(fragment2, R.id.greenSquare);
-        final View blueSquare2 = findViewById(fragment2, R.id.blueSquare);
-        verifyAndClearTransition(fragment2.enterTransition, null, greenSquare2, blueSquare2);
-        verifyNoOtherTransitions(fragment2);
-
-        // Make sure the correct transitions are run when the target names
-        // are different in both shared elements. We may fool the system.
-        verifyCrossTransition(false, fragment1, fragment2);
-
-        // Make sure the correct transitions are run when the source names
-        // are different in both shared elements. We may fool the system.
-        verifyCrossTransition(true, fragment1, fragment2);
-    }
-
-    // Make sure that onSharedElementStart and onSharedElementEnd are called
-    @Test
-    public void callStartEndWithSharedElements() throws Throwable {
-        TransitionFragment fragment1 = setupInitialFragment();
-
-        // Now do a transition to scene2
-        TransitionFragment fragment2 = new TransitionFragment();
-        fragment2.setLayoutId(R.layout.scene2);
-
-        SharedElementCallback enterCallback = mock(SharedElementCallback.class);
-        fragment2.setEnterSharedElementCallback(enterCallback);
-
-        final View startBlue = findBlue();
-
-        verifyTransition(fragment1, fragment2, "blueSquare");
-
-        ArgumentCaptor<List> names = ArgumentCaptor.forClass(List.class);
-        ArgumentCaptor<List> views = ArgumentCaptor.forClass(List.class);
-        ArgumentCaptor<List> snapshots = ArgumentCaptor.forClass(List.class);
-        verify(enterCallback).onSharedElementStart(names.capture(), views.capture(),
-                snapshots.capture());
-        assertEquals(1, names.getValue().size());
-        assertEquals(1, views.getValue().size());
-        assertNull(snapshots.getValue());
-        assertEquals("blueSquare", names.getValue().get(0));
-        assertEquals(startBlue, views.getValue().get(0));
-
-        final View endBlue = findBlue();
-
-        verify(enterCallback).onSharedElementEnd(names.capture(), views.capture(),
-                snapshots.capture());
-        assertEquals(1, names.getValue().size());
-        assertEquals(1, views.getValue().size());
-        assertNull(snapshots.getValue());
-        assertEquals("blueSquare", names.getValue().get(0));
-        assertEquals(endBlue, views.getValue().get(0));
-
-        // Now pop the back stack
-        reset(enterCallback);
-        verifyPopTransition(1, fragment2, fragment1);
-
-        verify(enterCallback).onSharedElementStart(names.capture(), views.capture(),
-                snapshots.capture());
-        assertEquals(1, names.getValue().size());
-        assertEquals(1, views.getValue().size());
-        assertNull(snapshots.getValue());
-        assertEquals("blueSquare", names.getValue().get(0));
-        assertEquals(endBlue, views.getValue().get(0));
-
-        final View reenterBlue = findBlue();
-
-        verify(enterCallback).onSharedElementEnd(names.capture(), views.capture(),
-                snapshots.capture());
-        assertEquals(1, names.getValue().size());
-        assertEquals(1, views.getValue().size());
-        assertNull(snapshots.getValue());
-        assertEquals("blueSquare", names.getValue().get(0));
-        assertEquals(reenterBlue, views.getValue().get(0));
-    }
-
-    // Make sure that onMapSharedElement works to change the shared element going out
-    @Test
-    public void onMapSharedElementOut() throws Throwable {
-        final TransitionFragment fragment1 = setupInitialFragment();
-
-        // Now do a transition to scene2
-        TransitionFragment fragment2 = new TransitionFragment();
-        fragment2.setLayoutId(R.layout.scene2);
-
-        final View startBlue = findBlue();
-        final View startGreen = findGreen();
-
-        final Rect startGreenBounds = getBoundsOnScreen(startGreen);
-
-        SharedElementCallback mapOut = new SharedElementCallback() {
-            @Override
-            public void onMapSharedElements(List<String> names, Map<String, View> sharedElements) {
-                assertEquals(1, names.size());
-                assertEquals("blueSquare", names.get(0));
-                assertEquals(1, sharedElements.size());
-                assertEquals(startBlue, sharedElements.get("blueSquare"));
-                sharedElements.put("blueSquare", startGreen);
-            }
-        };
-        fragment1.setExitSharedElementCallback(mapOut);
-
-        mFragmentManager.beginTransaction()
-                .addSharedElement(startBlue, "blueSquare")
-                .replace(R.id.fragmentContainer, fragment2)
-                .setReorderingAllowed(mReorderingAllowed)
-                .addToBackStack(null)
-                .commit();
-        FragmentTestUtil.waitForExecution(mActivityRule);
-
-        fragment1.waitForTransition();
-        fragment2.waitForTransition();
-
-        final View endBlue = findBlue();
-        final Rect endBlueBounds = getBoundsOnScreen(endBlue);
-
-        verifyAndClearTransition(fragment2.sharedElementEnter, startGreenBounds, startGreen,
-                endBlue);
-
-        SharedElementCallback mapBack = new SharedElementCallback() {
-            @Override
-            public void onMapSharedElements(List<String> names, Map<String, View> sharedElements) {
-                assertEquals(1, names.size());
-                assertEquals("blueSquare", names.get(0));
-                assertEquals(1, sharedElements.size());
-                final View expectedBlue = findViewById(fragment1, R.id.blueSquare);
-                assertEquals(expectedBlue, sharedElements.get("blueSquare"));
-                final View greenSquare = findViewById(fragment1, R.id.greenSquare);
-                sharedElements.put("blueSquare", greenSquare);
-            }
-        };
-        fragment1.setExitSharedElementCallback(mapBack);
-
-        FragmentTestUtil.popBackStackImmediate(mActivityRule);
-
-        fragment1.waitForTransition();
-        fragment2.waitForTransition();
-
-        final View reenterGreen = findGreen();
-        verifyAndClearTransition(fragment2.sharedElementReturn, endBlueBounds, endBlue,
-                reenterGreen);
-    }
-
-    // Make sure that onMapSharedElement works to change the shared element target
-    @Test
-    public void onMapSharedElementIn() throws Throwable {
-        TransitionFragment fragment1 = setupInitialFragment();
-
-        // Now do a transition to scene2
-        final TransitionFragment fragment2 = new TransitionFragment();
-        fragment2.setLayoutId(R.layout.scene2);
-
-        final View startBlue = findBlue();
-        final Rect startBlueBounds = getBoundsOnScreen(startBlue);
-
-        SharedElementCallback mapIn = new SharedElementCallback() {
-            @Override
-            public void onMapSharedElements(List<String> names, Map<String, View> sharedElements) {
-                assertEquals(1, names.size());
-                assertEquals("blueSquare", names.get(0));
-                assertEquals(1, sharedElements.size());
-                final View blueSquare = findViewById(fragment2, R.id.blueSquare);
-                assertEquals(blueSquare, sharedElements.get("blueSquare"));
-                final View greenSquare = findViewById(fragment2, R.id.greenSquare);
-                sharedElements.put("blueSquare", greenSquare);
-            }
-        };
-        fragment2.setEnterSharedElementCallback(mapIn);
-
-        mFragmentManager.beginTransaction()
-                .addSharedElement(startBlue, "blueSquare")
-                .replace(R.id.fragmentContainer, fragment2)
-                .setReorderingAllowed(mReorderingAllowed)
-                .addToBackStack(null)
-                .commit();
-        FragmentTestUtil.waitForExecution(mActivityRule);
-
-        fragment1.waitForTransition();
-        fragment2.waitForTransition();
-
-        final View endGreen = findGreen();
-        final View endBlue = findBlue();
-        final Rect endGreenBounds = getBoundsOnScreen(endGreen);
-
-        verifyAndClearTransition(fragment2.sharedElementEnter, startBlueBounds, startBlue,
-                endGreen);
-
-        SharedElementCallback mapBack = new SharedElementCallback() {
-            @Override
-            public void onMapSharedElements(List<String> names, Map<String, View> sharedElements) {
-                assertEquals(1, names.size());
-                assertEquals("blueSquare", names.get(0));
-                assertEquals(1, sharedElements.size());
-                assertEquals(endBlue, sharedElements.get("blueSquare"));
-                sharedElements.put("blueSquare", endGreen);
-            }
-        };
-        fragment2.setEnterSharedElementCallback(mapBack);
-
-        FragmentTestUtil.popBackStackImmediate(mActivityRule);
-
-        fragment1.waitForTransition();
-        fragment2.waitForTransition();
-
-        final View reenterBlue = findBlue();
-        verifyAndClearTransition(fragment2.sharedElementReturn, endGreenBounds, endGreen,
-                reenterBlue);
-    }
-
-    // Ensure that shared element transitions that have targets properly target the views
-    @Test
-    public void complexSharedElementTransition() throws Throwable {
-        TransitionFragment fragment1 = setupInitialFragment();
-
-        // Now do a transition to scene2
-        ComplexTransitionFragment fragment2 = new ComplexTransitionFragment();
-        fragment2.setLayoutId(R.layout.scene2);
-
-        final View startBlue = findBlue();
-        final View startGreen = findGreen();
-        final Rect startBlueBounds = getBoundsOnScreen(startBlue);
-
-        mFragmentManager.beginTransaction()
-                .addSharedElement(startBlue, "blueSquare")
-                .addSharedElement(startGreen, "greenSquare")
-                .replace(R.id.fragmentContainer, fragment2)
-                .addToBackStack(null)
-                .setReorderingAllowed(true)
-                .commit();
-        FragmentTestUtil.waitForExecution(mActivityRule);
-        assertEquals(2, mOnBackStackChangedTimes);
-
-        fragment1.waitForTransition();
-        fragment2.waitForTransition();
-
-        final View endBlue = findBlue();
-        final View endGreen = findGreen();
-        final Rect endBlueBounds = getBoundsOnScreen(endBlue);
-
-        verifyAndClearTransition(fragment2.sharedElementEnterTransition1, startBlueBounds,
-                startBlue, endBlue);
-        verifyAndClearTransition(fragment2.sharedElementEnterTransition2, startBlueBounds,
-                startGreen, endGreen);
-
-        // Now see if it works when popped
-        FragmentTestUtil.popBackStackImmediate(mActivityRule);
-        assertEquals(3, mOnBackStackChangedTimes);
-
-        fragment1.waitForTransition();
-        fragment2.waitForTransition();
-
-        final View reenterBlue = findBlue();
-        final View reenterGreen = findGreen();
-
-        verifyAndClearTransition(fragment2.sharedElementReturnTransition1, endBlueBounds,
-                endBlue, reenterBlue);
-        verifyAndClearTransition(fragment2.sharedElementReturnTransition2, endBlueBounds,
-                endGreen, reenterGreen);
-    }
-
-    // Ensure that after transitions have executed that they don't have any targets or other
-    // unfortunate modifications.
-    @Test
-    public void transitionsEndUnchanged() throws Throwable {
-        TransitionFragment fragment1 = setupInitialFragment();
-
-        // Now do a transition to scene2
-        TransitionFragment fragment2 = new TransitionFragment();
-        fragment2.setLayoutId(R.layout.scene2);
-
-        verifyTransition(fragment1, fragment2, "blueSquare");
-        assertEquals(0, fragment1.exitTransition.getTargets().size());
-        assertEquals(0, fragment2.sharedElementEnter.getTargets().size());
-        assertEquals(0, fragment2.enterTransition.getTargets().size());
-        assertNull(fragment1.exitTransition.getEpicenterCallback());
-        assertNull(fragment2.enterTransition.getEpicenterCallback());
-        assertNull(fragment2.sharedElementEnter.getEpicenterCallback());
-
-        // Now pop the back stack
-        verifyPopTransition(1, fragment2, fragment1);
-
-        assertEquals(0, fragment2.returnTransition.getTargets().size());
-        assertEquals(0, fragment2.sharedElementReturn.getTargets().size());
-        assertEquals(0, fragment1.reenterTransition.getTargets().size());
-        assertNull(fragment2.returnTransition.getEpicenterCallback());
-        assertNull(fragment2.sharedElementReturn.getEpicenterCallback());
-        assertNull(fragment2.reenterTransition.getEpicenterCallback());
-    }
-
-    // Ensure that transitions are done when a fragment is shown and hidden
-    @Test
-    public void showHideTransition() throws Throwable {
-        TransitionFragment fragment1 = setupInitialFragment();
-        TransitionFragment fragment2 = new TransitionFragment();
-        fragment2.setLayoutId(R.layout.scene2);
-
-        final View startBlue = findBlue();
-        final View startGreen = findGreen();
-
-        mFragmentManager.beginTransaction()
-                .setReorderingAllowed(mReorderingAllowed)
-                .add(R.id.fragmentContainer, fragment2)
-                .hide(fragment1)
-                .addToBackStack(null)
-                .commit();
-
-        FragmentTestUtil.waitForExecution(mActivityRule);
-        fragment1.waitForTransition();
-        fragment2.waitForTransition();
-
-        final View endGreen = findViewById(fragment2, R.id.greenSquare);
-        final View endBlue = findViewById(fragment2, R.id.blueSquare);
-
-        assertEquals(View.GONE, fragment1.requireView().getVisibility());
-        assertEquals(View.VISIBLE, startGreen.getVisibility());
-        assertEquals(View.VISIBLE, startBlue.getVisibility());
-
-        verifyAndClearTransition(fragment1.exitTransition, null, startGreen, startBlue);
-        verifyNoOtherTransitions(fragment1);
-
-        verifyAndClearTransition(fragment2.enterTransition, null, endGreen, endBlue);
-        verifyNoOtherTransitions(fragment2);
-
-        FragmentTestUtil.popBackStackImmediate(mActivityRule);
-
-        FragmentTestUtil.waitForExecution(mActivityRule);
-        fragment1.waitForTransition();
-        fragment2.waitForTransition();
-
-        verifyAndClearTransition(fragment1.reenterTransition, null, startGreen, startBlue);
-        verifyNoOtherTransitions(fragment1);
-
-        assertEquals(View.VISIBLE, fragment1.requireView().getVisibility());
-        assertEquals(View.VISIBLE, startGreen.getVisibility());
-        assertEquals(View.VISIBLE, startBlue.getVisibility());
-
-        verifyAndClearTransition(fragment2.returnTransition, null, endGreen, endBlue);
-        verifyNoOtherTransitions(fragment2);
-    }
-
-    // Ensure that transitions are done when a fragment is attached and detached
-    @Test
-    public void attachDetachTransition() throws Throwable {
-        TransitionFragment fragment1 = setupInitialFragment();
-        TransitionFragment fragment2 = new TransitionFragment();
-        fragment2.setLayoutId(R.layout.scene2);
-
-        final View startBlue = findBlue();
-        final View startGreen = findGreen();
-
-        mFragmentManager.beginTransaction()
-                .setReorderingAllowed(mReorderingAllowed)
-                .add(R.id.fragmentContainer, fragment2)
-                .detach(fragment1)
-                .addToBackStack(null)
-                .commit();
-
-        FragmentTestUtil.waitForExecution(mActivityRule);
-
-        final View endGreen = findViewById(fragment2, R.id.greenSquare);
-        final View endBlue = findViewById(fragment2, R.id.blueSquare);
-
-        verifyAndClearTransition(fragment1.exitTransition, null, startGreen, startBlue);
-        verifyNoOtherTransitions(fragment1);
-
-        verifyAndClearTransition(fragment2.enterTransition, null, endGreen, endBlue);
-        verifyNoOtherTransitions(fragment2);
-
-        FragmentTestUtil.popBackStackImmediate(mActivityRule);
-
-        FragmentTestUtil.waitForExecution(mActivityRule);
-
-        final View reenterBlue = findBlue();
-        final View reenterGreen = findGreen();
-
-        verifyAndClearTransition(fragment1.reenterTransition, null, reenterGreen, reenterBlue);
-        verifyNoOtherTransitions(fragment1);
-
-        verifyAndClearTransition(fragment2.returnTransition, null, endGreen, endBlue);
-        verifyNoOtherTransitions(fragment2);
-    }
-
-    // Ensure that shared element without matching transition name doesn't error out
-    @Test
-    public void sharedElementMismatch() throws Throwable {
-        final TransitionFragment fragment1 = setupInitialFragment();
-
-        // Now do a transition to scene2
-        TransitionFragment fragment2 = new TransitionFragment();
-        fragment2.setLayoutId(R.layout.scene2);
-
-        final View startBlue = findBlue();
-        final View startGreen = findGreen();
-        final Rect startBlueBounds = getBoundsOnScreen(startBlue);
-
-        mFragmentManager.beginTransaction()
-                .addSharedElement(startBlue, "fooSquare")
-                .replace(R.id.fragmentContainer, fragment2)
-                .setReorderingAllowed(mReorderingAllowed)
-                .addToBackStack(null)
-                .commit();
-        FragmentTestUtil.waitForExecution(mActivityRule);
-
-        fragment1.waitForTransition();
-        fragment2.waitForTransition();
-
-        final View endBlue = findBlue();
-        final View endGreen = findGreen();
-
-        if (mReorderingAllowed) {
-            verifyAndClearTransition(fragment1.exitTransition, null, startGreen, startBlue);
-        } else {
-            verifyAndClearTransition(fragment1.exitTransition, startBlueBounds, startGreen);
-            verifyAndClearTransition(fragment2.sharedElementEnter, startBlueBounds, startBlue);
-        }
-        verifyNoOtherTransitions(fragment1);
-
-        verifyAndClearTransition(fragment2.enterTransition, null, endGreen, endBlue);
-        verifyNoOtherTransitions(fragment2);
-    }
-
-    // Ensure that using the same source or target shared element results in an exception.
-    @Test
-    public void sharedDuplicateTargetNames() throws Throwable {
-        setupInitialFragment();
-
-        final View startBlue = findBlue();
-        final View startGreen = findGreen();
-
-        FragmentTransaction ft = mFragmentManager.beginTransaction();
-        ft.addSharedElement(startBlue, "blueSquare");
-        try {
-            ft.addSharedElement(startGreen, "blueSquare");
-            fail("Expected IllegalArgumentException");
-        } catch (IllegalArgumentException e) {
-            // expected
-        }
-
-        try {
-            ft.addSharedElement(startBlue, "greenSquare");
-            fail("Expected IllegalArgumentException");
-        } catch (IllegalArgumentException e) {
-            // expected
-        }
-    }
-
-    // Test that invisible fragment views don't participate in transitions
-    @Test
-    public void invisibleNoTransitions() throws Throwable {
-        if (!mReorderingAllowed) {
-            return; // only reordered transitions can avoid interaction
-        }
-        // enter transition
-        TransitionFragment fragment = new InvisibleFragment();
-        fragment.setLayoutId(R.layout.scene1);
-        mFragmentManager.beginTransaction()
-                .setReorderingAllowed(mReorderingAllowed)
-                .add(R.id.fragmentContainer, fragment)
-                .addToBackStack(null)
-                .commit();
-        FragmentTestUtil.waitForExecution(mActivityRule);
-        fragment.waitForNoTransition();
-        verifyNoOtherTransitions(fragment);
-
-        // exit transition
-        mFragmentManager.beginTransaction()
-                .setReorderingAllowed(mReorderingAllowed)
-                .remove(fragment)
-                .addToBackStack(null)
-                .commit();
-
-        fragment.waitForNoTransition();
-        verifyNoOtherTransitions(fragment);
-
-        // reenter transition
-        FragmentTestUtil.popBackStackImmediate(mActivityRule);
-        fragment.waitForNoTransition();
-        verifyNoOtherTransitions(fragment);
-
-        // return transition
-        FragmentTestUtil.popBackStackImmediate(mActivityRule);
-        fragment.waitForNoTransition();
-        verifyNoOtherTransitions(fragment);
-    }
-
-    // No crash when transitioning a shared element and there is no shared element transition.
-    @Test
-    public void noSharedElementTransition() throws Throwable {
-        TransitionFragment fragment1 = setupInitialFragment();
-
-        final View startBlue = findBlue();
-        final View startGreen = findGreen();
-        final Rect startBlueBounds = getBoundsOnScreen(startBlue);
-
-        TransitionFragment fragment2 = new TransitionFragment();
-        fragment2.setLayoutId(R.layout.scene2);
-
-        mFragmentManager.beginTransaction()
-                .setReorderingAllowed(mReorderingAllowed)
-                .addSharedElement(startBlue, "blueSquare")
-                .replace(R.id.fragmentContainer, fragment2)
-                .addToBackStack(null)
-                .commit();
-
-        fragment1.waitForTransition();
-        fragment2.waitForTransition();
-        final View midGreen = findGreen();
-        final View midBlue = findBlue();
-        final Rect midBlueBounds = getBoundsOnScreen(midBlue);
-        verifyAndClearTransition(fragment1.exitTransition, startBlueBounds, startGreen);
-        verifyAndClearTransition(fragment2.sharedElementEnter, startBlueBounds, startBlue, midBlue);
-        verifyAndClearTransition(fragment2.enterTransition, midBlueBounds, midGreen);
-        verifyNoOtherTransitions(fragment1);
-        verifyNoOtherTransitions(fragment2);
-
-        final TransitionFragment fragment3 = new TransitionFragment();
-        fragment3.setLayoutId(R.layout.scene3);
-
-        mActivityRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                FragmentManager fm = mActivityRule.getActivity().getSupportFragmentManager();
-                fm.popBackStack();
-                fm.beginTransaction()
-                        .setReorderingAllowed(mReorderingAllowed)
-                        .replace(R.id.fragmentContainer, fragment3)
-                        .addToBackStack(null)
-                        .commit();
-            }
-        });
-
-        // This shouldn't give an error.
-        FragmentTestUtil.executePendingTransactions(mActivityRule);
-
-        fragment2.waitForTransition();
-        // It does not transition properly for ordered transactions, though.
-        if (mReorderingAllowed) {
-            verifyAndClearTransition(fragment2.returnTransition, null, midGreen, midBlue);
-            final View endGreen = findGreen();
-            final View endBlue = findBlue();
-            final View endRed = findRed();
-            verifyAndClearTransition(fragment3.enterTransition, null, endGreen, endBlue, endRed);
-            verifyNoOtherTransitions(fragment2);
-            verifyNoOtherTransitions(fragment3);
-        } else {
-            // fragment3 doesn't get a transition since it conflicts with the pop transition
-            verifyNoOtherTransitions(fragment3);
-            // Everything else is just doing its best. Ordered transactions can't handle
-            // multiple transitions acting together except for popping multiple together.
-        }
-    }
-
-    // When there is no matching shared element, the transition name should not be changed
-    @Test
-    public void noMatchingSharedElementRetainName() throws Throwable {
-        TransitionFragment fragment1 = setupInitialFragment();
-
-        final View startBlue = findBlue();
-        final View startGreen = findGreen();
-        final Rect startGreenBounds = getBoundsOnScreen(startGreen);
-
-        TransitionFragment fragment2 = new TransitionFragment();
-        fragment2.setLayoutId(R.layout.scene3);
-
-        mFragmentManager.beginTransaction()
-                .setReorderingAllowed(mReorderingAllowed)
-                .addSharedElement(startGreen, "greenSquare")
-                .addSharedElement(startBlue, "blueSquare")
-                .replace(R.id.fragmentContainer, fragment2)
-                .addToBackStack(null)
-                .commit();
-
-        fragment2.waitForTransition();
-        final View midGreen = findGreen();
-        final View midBlue = findBlue();
-        final View midRed = findRed();
-        final Rect midGreenBounds = getBoundsOnScreen(midGreen);
-        if (mReorderingAllowed) {
-            verifyAndClearTransition(fragment2.sharedElementEnter, startGreenBounds, startGreen,
-                    midGreen);
-        } else {
-            verifyAndClearTransition(fragment2.sharedElementEnter, startGreenBounds, startGreen,
-                    midGreen, startBlue);
-        }
-        verifyAndClearTransition(fragment2.enterTransition, midGreenBounds, midBlue, midRed);
-        verifyNoOtherTransitions(fragment2);
-
-        FragmentTestUtil.popBackStackImmediate(mActivityRule);
-        fragment2.waitForTransition();
-        fragment1.waitForTransition();
-
-        final View endBlue = findBlue();
-        final View endGreen = findGreen();
-
-        assertEquals("blueSquare", endBlue.getTransitionName());
-        assertEquals("greenSquare", endGreen.getTransitionName());
-    }
-
-    private TransitionFragment setupInitialFragment() throws Throwable {
-        TransitionFragment fragment1 = new TransitionFragment();
-        fragment1.setLayoutId(R.layout.scene1);
-        mFragmentManager.beginTransaction()
-                .setReorderingAllowed(mReorderingAllowed)
-                .add(R.id.fragmentContainer, fragment1)
-                .addToBackStack(null)
-                .commit();
-        FragmentTestUtil.waitForExecution(mActivityRule);
-        assertEquals(1, mOnBackStackChangedTimes);
-        fragment1.waitForTransition();
-        final View blueSquare1 = findBlue();
-        final View greenSquare1 = findGreen();
-        verifyAndClearTransition(fragment1.enterTransition, null, blueSquare1, greenSquare1);
-        verifyNoOtherTransitions(fragment1);
-        return fragment1;
-    }
-
-    private View findViewById(Fragment fragment, int id) {
-        return fragment.requireView().findViewById(id);
-    }
-
-    private View findGreen() {
-        return mActivityRule.getActivity().findViewById(R.id.greenSquare);
-    }
-
-    private View findBlue() {
-        return mActivityRule.getActivity().findViewById(R.id.blueSquare);
-    }
-
-    private View findRed() {
-        return mActivityRule.getActivity().findViewById(R.id.redSquare);
-    }
-
-    private void verifyAndClearTransition(TargetTracking transition, Rect epicenter,
-            View... expected) {
-        if (epicenter == null) {
-            assertNull(transition.getCapturedEpicenter());
-        } else {
-            assertEquals(epicenter, transition.getCapturedEpicenter());
-        }
-        ArrayList<View> targets = transition.getTrackedTargets();
-        StringBuilder sb = new StringBuilder();
-        sb
-                .append("Expected: [")
-                .append(expected.length)
-                .append("] {");
-        boolean isFirst = true;
-        for (View view : expected) {
-            if (isFirst) {
-                isFirst = false;
-            } else {
-                sb.append(", ");
-            }
-            sb.append(view);
-        }
-        sb
-                .append("}, but got: [")
-                .append(targets.size())
-                .append("] {");
-        isFirst = true;
-        for (View view : targets) {
-            if (isFirst) {
-                isFirst = false;
-            } else {
-                sb.append(", ");
-            }
-            sb.append(view);
-        }
-        sb.append("}");
-        String errorMessage = sb.toString();
-
-        assertEquals(errorMessage, expected.length, targets.size());
-        for (View view : expected) {
-            assertTrue(errorMessage, targets.contains(view));
-        }
-        transition.clearTargets();
-    }
-
-    private void verifyNoOtherTransitions(TransitionFragment fragment) {
-        assertEquals(0, fragment.enterTransition.targets.size());
-        assertEquals(0, fragment.exitTransition.targets.size());
-        assertEquals(0, fragment.reenterTransition.targets.size());
-        assertEquals(0, fragment.returnTransition.targets.size());
-        assertEquals(0, fragment.sharedElementEnter.targets.size());
-        assertEquals(0, fragment.sharedElementReturn.targets.size());
-    }
-
-    private void verifyTransition(TransitionFragment from, TransitionFragment to,
-            String sharedElementName) throws Throwable {
-        final int startOnBackStackChanged = mOnBackStackChangedTimes;
-        final View startBlue = findBlue();
-        final View startGreen = findGreen();
-        final View startRed = findRed();
-
-        final Rect startBlueRect = getBoundsOnScreen(startBlue);
-
-        mFragmentManager.beginTransaction()
-                .setReorderingAllowed(mReorderingAllowed)
-                .addSharedElement(startBlue, sharedElementName)
-                .replace(R.id.fragmentContainer, to)
-                .addToBackStack(null)
-                .commit();
-
-        FragmentTestUtil.waitForExecution(mActivityRule);
-        assertEquals(startOnBackStackChanged + 1, mOnBackStackChangedTimes);
-
-        to.waitForTransition();
-        final View endGreen = findGreen();
-        final View endBlue = findBlue();
-        final View endRed = findRed();
-        final Rect endBlueRect = getBoundsOnScreen(endBlue);
-
-        if (startRed != null) {
-            verifyAndClearTransition(from.exitTransition, startBlueRect, startGreen, startRed);
-        } else {
-            verifyAndClearTransition(from.exitTransition, startBlueRect, startGreen);
-        }
-        verifyNoOtherTransitions(from);
-
-        if (endRed != null) {
-            verifyAndClearTransition(to.enterTransition, endBlueRect, endGreen, endRed);
-        } else {
-            verifyAndClearTransition(to.enterTransition, endBlueRect, endGreen);
-        }
-        verifyAndClearTransition(to.sharedElementEnter, startBlueRect, startBlue, endBlue);
-        verifyNoOtherTransitions(to);
-    }
-
-    private void verifyCrossTransition(boolean swapSource,
-            TransitionFragment from1, TransitionFragment from2) throws Throwable {
-        final int startNumOnBackStackChanged = mOnBackStackChangedTimes;
-        final int changesPerOperation = mReorderingAllowed ? 1 : 2;
-
-        final TransitionFragment to1 = new TransitionFragment();
-        to1.setLayoutId(R.layout.scene2);
-        final TransitionFragment to2 = new TransitionFragment();
-        to2.setLayoutId(R.layout.scene2);
-
-        final View fromExit1 = findViewById(from1, R.id.greenSquare);
-        final View fromShared1 = findViewById(from1, R.id.blueSquare);
-        final Rect fromSharedRect1 = getBoundsOnScreen(fromShared1);
-
-        final int fromExitId2 = swapSource ? R.id.blueSquare : R.id.greenSquare;
-        final int fromSharedId2 = swapSource ? R.id.greenSquare : R.id.blueSquare;
-        final View fromExit2 = findViewById(from2, fromExitId2);
-        final View fromShared2 = findViewById(from2, fromSharedId2);
-        final Rect fromSharedRect2 = getBoundsOnScreen(fromShared2);
-
-        final String sharedElementName = swapSource ? "blueSquare" : "greenSquare";
-
-        mActivityRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                mFragmentManager.beginTransaction()
-                        .setReorderingAllowed(mReorderingAllowed)
-                        .addSharedElement(fromShared1, "blueSquare")
-                        .replace(R.id.fragmentContainer1, to1)
-                        .addToBackStack(null)
-                        .commit();
-                mFragmentManager.beginTransaction()
-                        .setReorderingAllowed(mReorderingAllowed)
-                        .addSharedElement(fromShared2, sharedElementName)
-                        .replace(R.id.fragmentContainer2, to2)
-                        .addToBackStack(null)
-                        .commit();
-            }
-        });
-        FragmentTestUtil.waitForExecution(mActivityRule);
-        assertEquals(startNumOnBackStackChanged + changesPerOperation, mOnBackStackChangedTimes);
-
-        from1.waitForTransition();
-        from2.waitForTransition();
-        to1.waitForTransition();
-        to2.waitForTransition();
-
-        final View toEnter1 = findViewById(to1, R.id.greenSquare);
-        final View toShared1 = findViewById(to1, R.id.blueSquare);
-        final Rect toSharedRect1 = getBoundsOnScreen(toShared1);
-
-        final View toEnter2 = findViewById(to2, fromSharedId2);
-        final View toShared2 = findViewById(to2, fromExitId2);
-        final Rect toSharedRect2 = getBoundsOnScreen(toShared2);
-
-        verifyAndClearTransition(from1.exitTransition, fromSharedRect1, fromExit1);
-        verifyAndClearTransition(from2.exitTransition, fromSharedRect2, fromExit2);
-        verifyNoOtherTransitions(from1);
-        verifyNoOtherTransitions(from2);
-
-        verifyAndClearTransition(to1.enterTransition, toSharedRect1, toEnter1);
-        verifyAndClearTransition(to2.enterTransition, toSharedRect2, toEnter2);
-        verifyAndClearTransition(to1.sharedElementEnter, fromSharedRect1, fromShared1, toShared1);
-        verifyAndClearTransition(to2.sharedElementEnter, fromSharedRect2, fromShared2, toShared2);
-        verifyNoOtherTransitions(to1);
-        verifyNoOtherTransitions(to2);
-
-        // Now pop it back
-        mActivityRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                mFragmentManager.popBackStack();
-                mFragmentManager.popBackStack();
-            }
-        });
-        FragmentTestUtil.waitForExecution(mActivityRule);
-        assertEquals(startNumOnBackStackChanged + changesPerOperation + 1,
-                mOnBackStackChangedTimes);
-
-        from1.waitForTransition();
-        from2.waitForTransition();
-        to1.waitForTransition();
-        to2.waitForTransition();
-
-        final View returnEnter1 = findViewById(from1, R.id.greenSquare);
-        final View returnShared1 = findViewById(from1, R.id.blueSquare);
-
-        final View returnEnter2 = findViewById(from2, fromExitId2);
-        final View returnShared2 = findViewById(from2, fromSharedId2);
-
-        verifyAndClearTransition(to1.returnTransition, toSharedRect1, toEnter1);
-        verifyAndClearTransition(to2.returnTransition, toSharedRect2, toEnter2);
-        verifyAndClearTransition(to1.sharedElementReturn, toSharedRect1, toShared1, returnShared1);
-        verifyAndClearTransition(to2.sharedElementReturn, toSharedRect2, toShared2, returnShared2);
-        verifyNoOtherTransitions(to1);
-        verifyNoOtherTransitions(to2);
-
-        verifyAndClearTransition(from1.reenterTransition, fromSharedRect1, returnEnter1);
-        verifyAndClearTransition(from2.reenterTransition, fromSharedRect2, returnEnter2);
-        verifyNoOtherTransitions(from1);
-        verifyNoOtherTransitions(from2);
-    }
-
-    private void verifyPopTransition(final int numPops, TransitionFragment from,
-            TransitionFragment to, TransitionFragment... others) throws Throwable {
-        final int startOnBackStackChanged = mOnBackStackChangedTimes;
-        final View startBlue = findBlue();
-        final View startGreen = findGreen();
-        final View startRed = findRed();
-        final Rect startSharedRect = getBoundsOnScreen(startBlue);
-
-        mInstrumentation.runOnMainSync(new Runnable() {
-            @Override
-            public void run() {
-                for (int i = 0; i < numPops; i++) {
-                    mFragmentManager.popBackStack();
-                }
-            }
-        });
-        FragmentTestUtil.waitForExecution(mActivityRule);
-        assertEquals(startOnBackStackChanged + 1, mOnBackStackChangedTimes);
-
-        to.waitForTransition();
-        final View endGreen = findGreen();
-        final View endBlue = findBlue();
-        final View endRed = findRed();
-        final Rect endSharedRect = getBoundsOnScreen(endBlue);
-
-        if (startRed != null) {
-            verifyAndClearTransition(from.returnTransition, startSharedRect, startGreen, startRed);
-        } else {
-            verifyAndClearTransition(from.returnTransition, startSharedRect, startGreen);
-        }
-        verifyAndClearTransition(from.sharedElementReturn, startSharedRect, startBlue, endBlue);
-        verifyNoOtherTransitions(from);
-
-        if (endRed != null) {
-            verifyAndClearTransition(to.reenterTransition, endSharedRect, endGreen, endRed);
-        } else {
-            verifyAndClearTransition(to.reenterTransition, endSharedRect, endGreen);
-        }
-        verifyNoOtherTransitions(to);
-
-        if (others != null) {
-            for (TransitionFragment fragment : others) {
-                verifyNoOtherTransitions(fragment);
-            }
-        }
-    }
-
-    private static Rect getBoundsOnScreen(View view) {
-        final int[] loc = new int[2];
-        view.getLocationOnScreen(loc);
-        return new Rect(loc[0], loc[1], loc[0] + view.getWidth(), loc[1] + view.getHeight());
-    }
-
-    public static class ComplexTransitionFragment extends TransitionFragment {
-        public final TrackingTransition sharedElementEnterTransition1 = new TrackingTransition();
-        public final TrackingTransition sharedElementEnterTransition2 = new TrackingTransition();
-        public final TrackingTransition sharedElementReturnTransition1 = new TrackingTransition();
-        public final TrackingTransition sharedElementReturnTransition2 = new TrackingTransition();
-
-        public final TransitionSet sharedElementEnterTransition = new TransitionSet()
-                .addTransition(sharedElementEnterTransition1)
-                .addTransition(sharedElementEnterTransition2);
-        public final TransitionSet sharedElementReturnTransition = new TransitionSet()
-                .addTransition(sharedElementReturnTransition1)
-                .addTransition(sharedElementReturnTransition2);
-
-        public ComplexTransitionFragment() {
-            sharedElementEnterTransition1.addTarget(R.id.blueSquare);
-            sharedElementEnterTransition2.addTarget(R.id.greenSquare);
-            sharedElementReturnTransition1.addTarget(R.id.blueSquare);
-            sharedElementReturnTransition2.addTarget(R.id.greenSquare);
-            setSharedElementEnterTransition(sharedElementEnterTransition);
-            setSharedElementReturnTransition(sharedElementReturnTransition);
-        }
-    }
-
-    public static class InvisibleFragment extends TransitionFragment {
-        @Override
-        public void onViewCreated(View view, Bundle savedInstanceState) {
-            view.setVisibility(View.INVISIBLE);
-            super.onViewCreated(view, savedInstanceState);
-        }
-    }
-}
diff --git a/fragment/src/androidTest/java/androidx/fragment/app/FragmentTransitionTest.kt b/fragment/src/androidTest/java/androidx/fragment/app/FragmentTransitionTest.kt
new file mode 100644
index 0000000..83d8465
--- /dev/null
+++ b/fragment/src/androidTest/java/androidx/fragment/app/FragmentTransitionTest.kt
@@ -0,0 +1,1147 @@
+/*
+ * 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.graphics.Rect
+import android.os.Build
+import android.os.Bundle
+import android.transition.TransitionSet
+import android.view.View
+import androidx.core.app.SharedElementCallback
+import androidx.fragment.app.test.FragmentTestActivity
+import androidx.fragment.test.R
+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.After
+import org.junit.Assert.fail
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+import org.mockito.ArgumentCaptor
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.reset
+import org.mockito.Mockito.verify
+
+@MediumTest
+@RunWith(Parameterized::class)
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.LOLLIPOP)
+class FragmentTransitionTest(private val reorderingAllowed: Boolean) {
+
+    @get:Rule
+    val activityRule = ActivityTestRule(FragmentTestActivity::class.java)
+
+    private val instrumentation = InstrumentationRegistry.getInstrumentation()
+    private lateinit var fragmentManager: FragmentManager
+    private var onBackStackChangedTimes: Int = 0
+    private val onBackStackChangedListener =
+        FragmentManager.OnBackStackChangedListener { onBackStackChangedTimes++ }
+
+    @Before
+    fun setup() {
+        FragmentTestUtil.setContentView(activityRule, R.layout.simple_container)
+        onBackStackChangedTimes = 0
+        fragmentManager = activityRule.activity.supportFragmentManager
+        fragmentManager.addOnBackStackChangedListener(onBackStackChangedListener)
+    }
+
+    @After
+    fun teardown() {
+        fragmentManager.removeOnBackStackChangedListener(onBackStackChangedListener)
+    }
+
+    // Test that normal view transitions (enter, exit, reenter, return) run with
+    // a single fragment.
+    @Test
+    fun enterExitTransitions() {
+        // enter transition
+        val fragment = setupInitialFragment()
+        val blue = findBlue()
+        val green = findBlue()
+
+        // exit transition
+        fragmentManager.beginTransaction()
+            .setReorderingAllowed(reorderingAllowed)
+            .remove(fragment)
+            .addToBackStack(null)
+            .commit()
+
+        fragment.waitForTransition()
+        verifyAndClearTransition(fragment.exitTransition, null, green, blue)
+        verifyNoOtherTransitions(fragment)
+        assertThat(onBackStackChangedTimes).isEqualTo(2)
+
+        // reenter transition
+        FragmentTestUtil.popBackStackImmediate(activityRule)
+        fragment.waitForTransition()
+        val green2 = findGreen()
+        val blue2 = findBlue()
+        verifyAndClearTransition(fragment.reenterTransition, null, green2, blue2)
+        verifyNoOtherTransitions(fragment)
+        assertThat(onBackStackChangedTimes).isEqualTo(3)
+
+        // return transition
+        FragmentTestUtil.popBackStackImmediate(activityRule)
+        fragment.waitForTransition()
+        verifyAndClearTransition(fragment.returnTransition, null, green2, blue2)
+        verifyNoOtherTransitions(fragment)
+        assertThat(onBackStackChangedTimes).isEqualTo(4)
+    }
+
+    // Test that shared elements transition from one fragment to the next
+    // and back during pop.
+    @Test
+    fun sharedElement() {
+        val fragment1 = setupInitialFragment()
+
+        // Now do a transition to scene2
+        val fragment2 = TransitionFragment(R.layout.scene2)
+
+        verifyTransition(fragment1, fragment2, "blueSquare")
+
+        // Now pop the back stack
+        verifyPopTransition(1, fragment2, fragment1)
+    }
+
+    // Test that shared element transitions through multiple fragments work together
+    @Test
+    fun intermediateFragment() {
+        val fragment1 = setupInitialFragment()
+
+        val fragment2 = TransitionFragment(R.layout.scene3)
+
+        verifyTransition(fragment1, fragment2, "shared")
+
+        val fragment3 = TransitionFragment(R.layout.scene2)
+
+        verifyTransition(fragment2, fragment3, "blueSquare")
+
+        // Should transfer backwards when popping multiple:
+        verifyPopTransition(2, fragment3, fragment1, fragment2)
+    }
+
+    // Adding/removing the same fragment multiple times shouldn't mess anything up
+    @Test
+    fun removeAdded() {
+        val fragment1 = setupInitialFragment()
+
+        val startBlue = findBlue()
+        val startGreen = findGreen()
+
+        val fragment2 = TransitionFragment(R.layout.scene2)
+
+        instrumentation.runOnMainSync {
+            fragmentManager.beginTransaction()
+                .setReorderingAllowed(reorderingAllowed)
+                .replace(R.id.fragmentContainer, fragment2)
+                .replace(R.id.fragmentContainer, fragment1)
+                .replace(R.id.fragmentContainer, fragment2)
+                .addToBackStack(null)
+                .commit()
+        }
+        FragmentTestUtil.waitForExecution(activityRule)
+        assertThat(onBackStackChangedTimes).isEqualTo(2)
+
+        // should be a normal transition from fragment1 to fragment2
+        fragment2.waitForTransition()
+        val endBlue = findBlue()
+        val endGreen = findGreen()
+        verifyAndClearTransition(fragment1.exitTransition, null, startBlue, startGreen)
+        verifyAndClearTransition(fragment2.enterTransition, null, endBlue, endGreen)
+        verifyNoOtherTransitions(fragment1)
+        verifyNoOtherTransitions(fragment2)
+
+        // Pop should also do the same thing
+        FragmentTestUtil.popBackStackImmediate(activityRule)
+        assertThat(onBackStackChangedTimes).isEqualTo(3)
+
+        fragment1.waitForTransition()
+        val popBlue = findBlue()
+        val popGreen = findGreen()
+        verifyAndClearTransition(fragment1.reenterTransition, null, popBlue, popGreen)
+        verifyAndClearTransition(fragment2.returnTransition, null, endBlue, endGreen)
+        verifyNoOtherTransitions(fragment1)
+        verifyNoOtherTransitions(fragment2)
+    }
+
+    // Make sure that shared elements on two different fragment containers don't interact
+    @Test
+    fun crossContainer() {
+        FragmentTestUtil.setContentView(activityRule, R.layout.double_container)
+        val fragment1 = TransitionFragment(R.layout.scene1)
+        val fragment2 = TransitionFragment(R.layout.scene1)
+        fragmentManager.beginTransaction()
+            .setReorderingAllowed(reorderingAllowed)
+            .add(R.id.fragmentContainer1, fragment1)
+            .add(R.id.fragmentContainer2, fragment2)
+            .addToBackStack(null)
+            .commit()
+        FragmentTestUtil.waitForExecution(activityRule)
+        assertThat(onBackStackChangedTimes).isEqualTo(1)
+
+        fragment1.waitForTransition()
+        val greenSquare1 = findViewById(fragment1, R.id.greenSquare)
+        val blueSquare1 = findViewById(fragment1, R.id.blueSquare)
+        verifyAndClearTransition(fragment1.enterTransition, null, greenSquare1, blueSquare1)
+        verifyNoOtherTransitions(fragment1)
+        fragment2.waitForTransition()
+        val greenSquare2 = findViewById(fragment2, R.id.greenSquare)
+        val blueSquare2 = findViewById(fragment2, R.id.blueSquare)
+        verifyAndClearTransition(fragment2.enterTransition, null, greenSquare2, blueSquare2)
+        verifyNoOtherTransitions(fragment2)
+
+        // Make sure the correct transitions are run when the target names
+        // are different in both shared elements. We may fool the system.
+        verifyCrossTransition(false, fragment1, fragment2)
+
+        // Make sure the correct transitions are run when the source names
+        // are different in both shared elements. We may fool the system.
+        verifyCrossTransition(true, fragment1, fragment2)
+    }
+
+    // Make sure that onSharedElementStart and onSharedElementEnd are called
+    @Test
+    fun callStartEndWithSharedElements() {
+        val fragment1 = setupInitialFragment()
+
+        // Now do a transition to scene2
+        val fragment2 = TransitionFragment(R.layout.scene2)
+
+        val enterCallback = mock(SharedElementCallback::class.java)
+        fragment2.setEnterSharedElementCallback(enterCallback)
+
+        val startBlue = findBlue()
+
+        verifyTransition(fragment1, fragment2, "blueSquare")
+
+        val names = ArgumentCaptor.forClass(List::class.java as Class<List<String>>)
+        val views = ArgumentCaptor.forClass(List::class.java as Class<List<View>>)
+        val snapshots = ArgumentCaptor.forClass(List::class.java as Class<List<View>>)
+        verify(enterCallback).onSharedElementStart(
+            names.capture(), views.capture(),
+            snapshots.capture()
+        )
+        assertThat(names.value.size).isEqualTo(1)
+        assertThat(views.value.size).isEqualTo(1)
+        assertThat(snapshots.value).isNull()
+        assertThat(names.value[0]).isEqualTo("blueSquare")
+        assertThat(views.value[0]).isEqualTo(startBlue)
+
+        val endBlue = findBlue()
+
+        verify(enterCallback).onSharedElementEnd(
+            names.capture(), views.capture(),
+            snapshots.capture()
+        )
+        assertThat(names.value.size).isEqualTo(1)
+        assertThat(views.value.size).isEqualTo(1)
+        assertThat(snapshots.value).isNull()
+        assertThat(names.value[0]).isEqualTo("blueSquare")
+        assertThat(views.value[0]).isEqualTo(endBlue)
+
+        // Now pop the back stack
+        reset(enterCallback)
+        verifyPopTransition(1, fragment2, fragment1)
+
+        verify(enterCallback).onSharedElementStart(
+            names.capture(), views.capture(),
+            snapshots.capture()
+        )
+        assertThat(names.value.size).isEqualTo(1)
+        assertThat(views.value.size).isEqualTo(1)
+        assertThat(snapshots.value).isNull()
+        assertThat(names.value[0]).isEqualTo("blueSquare")
+        assertThat(views.value[0]).isEqualTo(endBlue)
+
+        val reenterBlue = findBlue()
+
+        verify(enterCallback).onSharedElementEnd(
+            names.capture(), views.capture(),
+            snapshots.capture()
+        )
+        assertThat(names.value.size).isEqualTo(1)
+        assertThat(views.value.size).isEqualTo(1)
+        assertThat(snapshots.value).isNull()
+        assertThat(names.value[0]).isEqualTo("blueSquare")
+        assertThat(views.value[0]).isEqualTo(reenterBlue)
+    }
+
+    // Make sure that onMapSharedElement works to change the shared element going out
+    @Test
+    fun onMapSharedElementOut() {
+        val fragment1 = setupInitialFragment()
+
+        // Now do a transition to scene2
+        val fragment2 = TransitionFragment(R.layout.scene2)
+
+        val startBlue = findBlue()
+        val startGreen = findGreen()
+
+        val startGreenBounds = getBoundsOnScreen(startGreen)
+
+        val mapOut = object : SharedElementCallback() {
+            override fun onMapSharedElements(
+                names: List<String>,
+                sharedElements: MutableMap<String, View>
+            ) {
+                assertThat(names.size).isEqualTo(1)
+                assertThat(names[0]).isEqualTo("blueSquare")
+                assertThat(sharedElements.size).isEqualTo(1)
+                assertThat(sharedElements["blueSquare"]).isEqualTo(startBlue)
+                sharedElements["blueSquare"] = startGreen
+            }
+        }
+        fragment1.setExitSharedElementCallback(mapOut)
+
+        fragmentManager.beginTransaction()
+            .addSharedElement(startBlue, "blueSquare")
+            .replace(R.id.fragmentContainer, fragment2)
+            .setReorderingAllowed(reorderingAllowed)
+            .addToBackStack(null)
+            .commit()
+        FragmentTestUtil.waitForExecution(activityRule)
+
+        fragment1.waitForTransition()
+        fragment2.waitForTransition()
+
+        val endBlue = findBlue()
+        val endBlueBounds = getBoundsOnScreen(endBlue)
+
+        verifyAndClearTransition(
+            fragment2.sharedElementEnter, startGreenBounds, startGreen,
+            endBlue
+        )
+
+        val mapBack = object : SharedElementCallback() {
+            override fun onMapSharedElements(
+                names: List<String>,
+                sharedElements: MutableMap<String, View>
+            ) {
+                assertThat(names.size).isEqualTo(1)
+                assertThat(names[0]).isEqualTo("blueSquare")
+                assertThat(sharedElements.size).isEqualTo(1)
+                val expectedBlue = findViewById(fragment1, R.id.blueSquare)
+                assertThat(sharedElements["blueSquare"]).isEqualTo(expectedBlue)
+                val greenSquare = findViewById(fragment1, R.id.greenSquare)
+                sharedElements["blueSquare"] = greenSquare
+            }
+        }
+        fragment1.setExitSharedElementCallback(mapBack)
+
+        FragmentTestUtil.popBackStackImmediate(activityRule)
+
+        fragment1.waitForTransition()
+        fragment2.waitForTransition()
+
+        val reenterGreen = findGreen()
+        verifyAndClearTransition(
+            fragment2.sharedElementReturn, endBlueBounds, endBlue,
+            reenterGreen
+        )
+    }
+
+    // Make sure that onMapSharedElement works to change the shared element target
+    @Test
+    fun onMapSharedElementIn() {
+        val fragment1 = setupInitialFragment()
+
+        // Now do a transition to scene2
+        val fragment2 = TransitionFragment(R.layout.scene2)
+
+        val startBlue = findBlue()
+        val startBlueBounds = getBoundsOnScreen(startBlue)
+
+        val mapIn = object : SharedElementCallback() {
+            override fun onMapSharedElements(
+                names: List<String>,
+                sharedElements: MutableMap<String, View>
+            ) {
+                assertThat(names.size).isEqualTo(1)
+                assertThat(names[0]).isEqualTo("blueSquare")
+                assertThat(sharedElements.size).isEqualTo(1)
+                val blueSquare = findViewById(fragment2, R.id.blueSquare)
+                assertThat(sharedElements["blueSquare"]).isEqualTo(blueSquare)
+                val greenSquare = findViewById(fragment2, R.id.greenSquare)
+                sharedElements["blueSquare"] = greenSquare
+            }
+        }
+        fragment2.setEnterSharedElementCallback(mapIn)
+
+        fragmentManager.beginTransaction()
+            .addSharedElement(startBlue, "blueSquare")
+            .replace(R.id.fragmentContainer, fragment2)
+            .setReorderingAllowed(reorderingAllowed)
+            .addToBackStack(null)
+            .commit()
+        FragmentTestUtil.waitForExecution(activityRule)
+
+        fragment1.waitForTransition()
+        fragment2.waitForTransition()
+
+        val endGreen = findGreen()
+        val endBlue = findBlue()
+        val endGreenBounds = getBoundsOnScreen(endGreen)
+
+        verifyAndClearTransition(
+            fragment2.sharedElementEnter, startBlueBounds, startBlue,
+            endGreen
+        )
+
+        val mapBack = object : SharedElementCallback() {
+            override fun onMapSharedElements(
+                names: List<String>,
+                sharedElements: MutableMap<String, View>
+            ) {
+                assertThat(names.size).isEqualTo(1)
+                assertThat(names[0]).isEqualTo("blueSquare")
+                assertThat(sharedElements.size).isEqualTo(1)
+                assertThat(sharedElements["blueSquare"]).isEqualTo(endBlue)
+                sharedElements["blueSquare"] = endGreen
+            }
+        }
+        fragment2.setEnterSharedElementCallback(mapBack)
+
+        FragmentTestUtil.popBackStackImmediate(activityRule)
+
+        fragment1.waitForTransition()
+        fragment2.waitForTransition()
+
+        val reenterBlue = findBlue()
+        verifyAndClearTransition(
+            fragment2.sharedElementReturn, endGreenBounds, endGreen,
+            reenterBlue
+        )
+    }
+
+    // Ensure that shared element transitions that have targets properly target the views
+    @Test
+    fun complexSharedElementTransition() {
+        val fragment1 = setupInitialFragment()
+
+        // Now do a transition to scene2
+        val fragment2 = ComplexTransitionFragment()
+
+        val startBlue = findBlue()
+        val startGreen = findGreen()
+        val startBlueBounds = getBoundsOnScreen(startBlue)
+
+        fragmentManager.beginTransaction()
+            .addSharedElement(startBlue, "blueSquare")
+            .addSharedElement(startGreen, "greenSquare")
+            .replace(R.id.fragmentContainer, fragment2)
+            .addToBackStack(null)
+            .setReorderingAllowed(true)
+            .commit()
+        FragmentTestUtil.waitForExecution(activityRule)
+        assertThat(onBackStackChangedTimes).isEqualTo(2)
+
+        fragment1.waitForTransition()
+        fragment2.waitForTransition()
+
+        val endBlue = findBlue()
+        val endGreen = findGreen()
+        val endBlueBounds = getBoundsOnScreen(endBlue)
+
+        verifyAndClearTransition(
+            fragment2.sharedElementEnterTransition1, startBlueBounds,
+            startBlue, endBlue
+        )
+        verifyAndClearTransition(
+            fragment2.sharedElementEnterTransition2, startBlueBounds,
+            startGreen, endGreen
+        )
+
+        // Now see if it works when popped
+        FragmentTestUtil.popBackStackImmediate(activityRule)
+        assertThat(onBackStackChangedTimes).isEqualTo(3)
+
+        fragment1.waitForTransition()
+        fragment2.waitForTransition()
+
+        val reenterBlue = findBlue()
+        val reenterGreen = findGreen()
+
+        verifyAndClearTransition(
+            fragment2.sharedElementReturnTransition1, endBlueBounds,
+            endBlue, reenterBlue
+        )
+        verifyAndClearTransition(
+            fragment2.sharedElementReturnTransition2, endBlueBounds,
+            endGreen, reenterGreen
+        )
+    }
+
+    // Ensure that after transitions have executed that they don't have any targets or other
+    // unfortunate modifications.
+    @Test
+    fun transitionsEndUnchanged() {
+        val fragment1 = setupInitialFragment()
+
+        // Now do a transition to scene2
+        val fragment2 = TransitionFragment(R.layout.scene2)
+
+        verifyTransition(fragment1, fragment2, "blueSquare")
+        assertThat(fragment1.exitTransition.getTargets().size).isEqualTo(0)
+        assertThat(fragment2.sharedElementEnter.getTargets().size).isEqualTo(0)
+        assertThat(fragment2.enterTransition.getTargets().size).isEqualTo(0)
+        assertThat(fragment1.exitTransition.epicenterCallback).isNull()
+        assertThat(fragment2.enterTransition.epicenterCallback).isNull()
+        assertThat(fragment2.sharedElementEnter.epicenterCallback).isNull()
+
+        // Now pop the back stack
+        verifyPopTransition(1, fragment2, fragment1)
+
+        assertThat(fragment2.returnTransition.getTargets().size).isEqualTo(0)
+        assertThat(fragment2.sharedElementReturn.getTargets().size).isEqualTo(0)
+        assertThat(fragment1.reenterTransition.getTargets().size).isEqualTo(0)
+        assertThat(fragment2.returnTransition.epicenterCallback).isNull()
+        assertThat(fragment2.sharedElementReturn.epicenterCallback).isNull()
+        assertThat(fragment2.reenterTransition.epicenterCallback).isNull()
+    }
+
+    // Ensure that transitions are done when a fragment is shown and hidden
+    @Test
+    fun showHideTransition() {
+        val fragment1 = setupInitialFragment()
+        val fragment2 = TransitionFragment(R.layout.scene2)
+
+        val startBlue = findBlue()
+        val startGreen = findGreen()
+
+        fragmentManager.beginTransaction()
+            .setReorderingAllowed(reorderingAllowed)
+            .add(R.id.fragmentContainer, fragment2)
+            .hide(fragment1)
+            .addToBackStack(null)
+            .commit()
+
+        FragmentTestUtil.waitForExecution(activityRule)
+        fragment1.waitForTransition()
+        fragment2.waitForTransition()
+
+        val endGreen = findViewById(fragment2, R.id.greenSquare)
+        val endBlue = findViewById(fragment2, R.id.blueSquare)
+
+        assertThat(fragment1.requireView().visibility).isEqualTo(View.GONE)
+        assertThat(startGreen.visibility).isEqualTo(View.VISIBLE)
+        assertThat(startBlue.visibility).isEqualTo(View.VISIBLE)
+
+        verifyAndClearTransition(fragment1.exitTransition, null, startGreen, startBlue)
+        verifyNoOtherTransitions(fragment1)
+
+        verifyAndClearTransition(fragment2.enterTransition, null, endGreen, endBlue)
+        verifyNoOtherTransitions(fragment2)
+
+        FragmentTestUtil.popBackStackImmediate(activityRule)
+
+        FragmentTestUtil.waitForExecution(activityRule)
+        fragment1.waitForTransition()
+        fragment2.waitForTransition()
+
+        verifyAndClearTransition(fragment1.reenterTransition, null, startGreen, startBlue)
+        verifyNoOtherTransitions(fragment1)
+
+        assertThat(fragment1.requireView().visibility).isEqualTo(View.VISIBLE)
+        assertThat(startGreen.visibility).isEqualTo(View.VISIBLE)
+        assertThat(startBlue.visibility).isEqualTo(View.VISIBLE)
+
+        verifyAndClearTransition(fragment2.returnTransition, null, endGreen, endBlue)
+        verifyNoOtherTransitions(fragment2)
+    }
+
+    // Ensure that transitions are done when a fragment is attached and detached
+    @Test
+    fun attachDetachTransition() {
+        val fragment1 = setupInitialFragment()
+        val fragment2 = TransitionFragment(R.layout.scene2)
+
+        val startBlue = findBlue()
+        val startGreen = findGreen()
+
+        fragmentManager.beginTransaction()
+            .setReorderingAllowed(reorderingAllowed)
+            .add(R.id.fragmentContainer, fragment2)
+            .detach(fragment1)
+            .addToBackStack(null)
+            .commit()
+
+        FragmentTestUtil.waitForExecution(activityRule)
+
+        val endGreen = findViewById(fragment2, R.id.greenSquare)
+        val endBlue = findViewById(fragment2, R.id.blueSquare)
+
+        verifyAndClearTransition(fragment1.exitTransition, null, startGreen, startBlue)
+        verifyNoOtherTransitions(fragment1)
+
+        verifyAndClearTransition(fragment2.enterTransition, null, endGreen, endBlue)
+        verifyNoOtherTransitions(fragment2)
+
+        FragmentTestUtil.popBackStackImmediate(activityRule)
+
+        FragmentTestUtil.waitForExecution(activityRule)
+
+        val reenterBlue = findBlue()
+        val reenterGreen = findGreen()
+
+        verifyAndClearTransition(fragment1.reenterTransition, null, reenterGreen, reenterBlue)
+        verifyNoOtherTransitions(fragment1)
+
+        verifyAndClearTransition(fragment2.returnTransition, null, endGreen, endBlue)
+        verifyNoOtherTransitions(fragment2)
+    }
+
+    // Ensure that shared element without matching transition name doesn't error out
+    @Test
+    fun sharedElementMismatch() {
+        val fragment1 = setupInitialFragment()
+
+        // Now do a transition to scene2
+        val fragment2 = TransitionFragment(R.layout.scene2)
+
+        val startBlue = findBlue()
+        val startGreen = findGreen()
+        val startBlueBounds = getBoundsOnScreen(startBlue)
+
+        fragmentManager.beginTransaction()
+            .addSharedElement(startBlue, "fooSquare")
+            .replace(R.id.fragmentContainer, fragment2)
+            .setReorderingAllowed(reorderingAllowed)
+            .addToBackStack(null)
+            .commit()
+        FragmentTestUtil.waitForExecution(activityRule)
+
+        fragment1.waitForTransition()
+        fragment2.waitForTransition()
+
+        val endBlue = findBlue()
+        val endGreen = findGreen()
+
+        if (reorderingAllowed) {
+            verifyAndClearTransition(fragment1.exitTransition, null, startGreen, startBlue)
+        } else {
+            verifyAndClearTransition(fragment1.exitTransition, startBlueBounds, startGreen)
+            verifyAndClearTransition(fragment2.sharedElementEnter, startBlueBounds, startBlue)
+        }
+        verifyNoOtherTransitions(fragment1)
+
+        verifyAndClearTransition(fragment2.enterTransition, null, endGreen, endBlue)
+        verifyNoOtherTransitions(fragment2)
+    }
+
+    // Ensure that using the same source or target shared element results in an exception.
+    @Test
+    fun sharedDuplicateTargetNames() {
+        setupInitialFragment()
+
+        val startBlue = findBlue()
+        val startGreen = findGreen()
+
+        val ft = fragmentManager.beginTransaction()
+        ft.addSharedElement(startBlue, "blueSquare")
+        try {
+            ft.addSharedElement(startGreen, "blueSquare")
+            fail("Expected IllegalArgumentException")
+        } catch (e: IllegalArgumentException) {
+            assertThat(e)
+                .hasMessageThat().contains("A shared element with the target name 'blueSquare' " +
+                        "has already been added to the transaction.")
+        }
+
+        try {
+            ft.addSharedElement(startBlue, "greenSquare")
+            fail("Expected IllegalArgumentException")
+        } catch (e: IllegalArgumentException) {
+            assertThat(e)
+                .hasMessageThat().contains("A shared element with the source name 'blueSquare' " +
+                        "has already been added to the transaction.")
+        }
+    }
+
+    // Test that invisible fragment views don't participate in transitions
+    @Test
+    fun invisibleNoTransitions() {
+        if (!reorderingAllowed) {
+            return // only reordered transitions can avoid interaction
+        }
+        // enter transition
+        val fragment = InvisibleFragment()
+        fragmentManager.beginTransaction()
+            .setReorderingAllowed(reorderingAllowed)
+            .add(R.id.fragmentContainer, fragment)
+            .addToBackStack(null)
+            .commit()
+        FragmentTestUtil.waitForExecution(activityRule)
+        fragment.waitForNoTransition()
+        verifyNoOtherTransitions(fragment)
+
+        // exit transition
+        fragmentManager.beginTransaction()
+            .setReorderingAllowed(reorderingAllowed)
+            .remove(fragment)
+            .addToBackStack(null)
+            .commit()
+
+        fragment.waitForNoTransition()
+        verifyNoOtherTransitions(fragment)
+
+        // reenter transition
+        FragmentTestUtil.popBackStackImmediate(activityRule)
+        fragment.waitForNoTransition()
+        verifyNoOtherTransitions(fragment)
+
+        // return transition
+        FragmentTestUtil.popBackStackImmediate(activityRule)
+        fragment.waitForNoTransition()
+        verifyNoOtherTransitions(fragment)
+    }
+
+    // No crash when transitioning a shared element and there is no shared element transition.
+    @Test
+    fun noSharedElementTransition() {
+        val fragment1 = setupInitialFragment()
+
+        val startBlue = findBlue()
+        val startGreen = findGreen()
+        val startBlueBounds = getBoundsOnScreen(startBlue)
+
+        val fragment2 = TransitionFragment(R.layout.scene2)
+
+        fragmentManager.beginTransaction()
+            .setReorderingAllowed(reorderingAllowed)
+            .addSharedElement(startBlue, "blueSquare")
+            .replace(R.id.fragmentContainer, fragment2)
+            .addToBackStack(null)
+            .commit()
+
+        fragment1.waitForTransition()
+        fragment2.waitForTransition()
+        val midGreen = findGreen()
+        val midBlue = findBlue()
+        val midBlueBounds = getBoundsOnScreen(midBlue)
+        verifyAndClearTransition(fragment1.exitTransition, startBlueBounds, startGreen)
+        verifyAndClearTransition(fragment2.sharedElementEnter, startBlueBounds, startBlue, midBlue)
+        verifyAndClearTransition(fragment2.enterTransition, midBlueBounds, midGreen)
+        verifyNoOtherTransitions(fragment1)
+        verifyNoOtherTransitions(fragment2)
+
+        val fragment3 = TransitionFragment(R.layout.scene3)
+
+        activityRule.runOnUiThread {
+            val fm = activityRule.activity.supportFragmentManager
+            fm.popBackStack()
+            fm.beginTransaction()
+                .setReorderingAllowed(reorderingAllowed)
+                .replace(R.id.fragmentContainer, fragment3)
+                .addToBackStack(null)
+                .commit()
+        }
+
+        // This shouldn't give an error.
+        FragmentTestUtil.executePendingTransactions(activityRule)
+
+        fragment2.waitForTransition()
+        // It does not transition properly for ordered transactions, though.
+        if (reorderingAllowed) {
+            verifyAndClearTransition(fragment2.returnTransition, null, midGreen, midBlue)
+            val endGreen = findGreen()
+            val endBlue = findBlue()
+            val endRed = findRed()
+            verifyAndClearTransition(fragment3.enterTransition, null, endGreen, endBlue, endRed!!)
+            verifyNoOtherTransitions(fragment2)
+            verifyNoOtherTransitions(fragment3)
+        } else {
+            // fragment3 doesn't get a transition since it conflicts with the pop transition
+            verifyNoOtherTransitions(fragment3)
+            // Everything else is just doing its best. Ordered transactions can't handle
+            // multiple transitions acting together except for popping multiple together.
+        }
+    }
+
+    // When there is no matching shared element, the transition name should not be changed
+    @Test
+    fun noMatchingSharedElementRetainName() {
+        val fragment1 = setupInitialFragment()
+
+        val startBlue = findBlue()
+        val startGreen = findGreen()
+        val startGreenBounds = getBoundsOnScreen(startGreen)
+
+        val fragment2 = TransitionFragment(R.layout.scene3)
+
+        fragmentManager.beginTransaction()
+            .setReorderingAllowed(reorderingAllowed)
+            .addSharedElement(startGreen, "greenSquare")
+            .addSharedElement(startBlue, "blueSquare")
+            .replace(R.id.fragmentContainer, fragment2)
+            .addToBackStack(null)
+            .commit()
+
+        fragment2.waitForTransition()
+        val midGreen = findGreen()
+        val midBlue = findBlue()
+        val midRed = findRed()
+        val midGreenBounds = getBoundsOnScreen(midGreen)
+        if (reorderingAllowed) {
+            verifyAndClearTransition(
+                fragment2.sharedElementEnter, startGreenBounds, startGreen,
+                midGreen
+            )
+        } else {
+            verifyAndClearTransition(
+                fragment2.sharedElementEnter, startGreenBounds, startGreen,
+                midGreen, startBlue
+            )
+        }
+        verifyAndClearTransition(fragment2.enterTransition, midGreenBounds, midBlue, midRed!!)
+        verifyNoOtherTransitions(fragment2)
+
+        FragmentTestUtil.popBackStackImmediate(activityRule)
+        fragment2.waitForTransition()
+        fragment1.waitForTransition()
+
+        val endBlue = findBlue()
+        val endGreen = findGreen()
+
+        assertThat(endBlue.transitionName).isEqualTo("blueSquare")
+        assertThat(endGreen.transitionName).isEqualTo("greenSquare")
+    }
+
+    private fun setupInitialFragment(): TransitionFragment {
+        val fragment1 = TransitionFragment(R.layout.scene1)
+        fragmentManager.beginTransaction()
+            .setReorderingAllowed(reorderingAllowed)
+            .add(R.id.fragmentContainer, fragment1)
+            .addToBackStack(null)
+            .commit()
+        FragmentTestUtil.waitForExecution(activityRule)
+        assertThat(onBackStackChangedTimes).isEqualTo(1)
+        fragment1.waitForTransition()
+        val blueSquare1 = findBlue()
+        val greenSquare1 = findGreen()
+        verifyAndClearTransition(fragment1.enterTransition, null, blueSquare1, greenSquare1)
+        verifyNoOtherTransitions(fragment1)
+        return fragment1
+    }
+
+    private fun findViewById(fragment: Fragment, id: Int): View {
+        return fragment.requireView().findViewById(id)
+    }
+
+    private fun findGreen(): View {
+        return activityRule.activity.findViewById(R.id.greenSquare)
+    }
+
+    private fun findBlue(): View {
+        return activityRule.activity.findViewById(R.id.blueSquare)
+    }
+
+    private fun findRed(): View? {
+        return activityRule.activity.findViewById(R.id.redSquare)
+    }
+
+    private fun verifyAndClearTransition(
+        transition: TargetTracking,
+        epicenter: Rect?,
+        vararg expected: View
+    ) {
+        if (epicenter == null) {
+            assertThat(transition.capturedEpicenter).isNull()
+        } else {
+            assertThat(transition.capturedEpicenter).isEqualTo(epicenter)
+        }
+        val targets = transition.trackedTargets
+        val sb = StringBuilder()
+        sb.append("Expected: [")
+            .append(expected.size)
+            .append("] {")
+        var isFirst = true
+        for (view in expected) {
+            if (isFirst) {
+                isFirst = false
+            } else {
+                sb.append(", ")
+            }
+            sb.append(view)
+        }
+        sb.append("}, but got: [")
+            .append(targets.size)
+            .append("] {")
+        isFirst = true
+        for (view in targets) {
+            if (isFirst) {
+                isFirst = false
+            } else {
+                sb.append(", ")
+            }
+            sb.append(view)
+        }
+        sb.append("}")
+        val errorMessage = sb.toString()
+
+        assertWithMessage(errorMessage).that(targets.size).isEqualTo(expected.size)
+        for (view in expected) {
+            assertWithMessage(errorMessage).that(targets.contains(view)).isTrue()
+        }
+        transition.clearTargets()
+    }
+
+    private fun verifyNoOtherTransitions(fragment: TransitionFragment) {
+        assertThat(fragment.enterTransition.targets.size).isEqualTo(0)
+        assertThat(fragment.exitTransition.targets.size).isEqualTo(0)
+        assertThat(fragment.reenterTransition.targets.size).isEqualTo(0)
+        assertThat(fragment.returnTransition.targets.size).isEqualTo(0)
+        assertThat(fragment.sharedElementEnter.targets.size).isEqualTo(0)
+        assertThat(fragment.sharedElementReturn.targets.size).isEqualTo(0)
+    }
+
+    private fun verifyTransition(
+        from: TransitionFragment,
+        to: TransitionFragment,
+        sharedElementName: String
+    ) {
+        val startOnBackStackChanged = onBackStackChangedTimes
+        val startBlue = findBlue()
+        val startGreen = findGreen()
+        val startRed = findRed()
+
+        val startBlueRect = getBoundsOnScreen(startBlue)
+
+        fragmentManager.beginTransaction()
+            .setReorderingAllowed(reorderingAllowed)
+            .addSharedElement(startBlue, sharedElementName)
+            .replace(R.id.fragmentContainer, to)
+            .addToBackStack(null)
+            .commit()
+
+        FragmentTestUtil.waitForExecution(activityRule)
+        assertThat(onBackStackChangedTimes).isEqualTo(startOnBackStackChanged + 1)
+
+        to.waitForTransition()
+        val endGreen = findGreen()
+        val endBlue = findBlue()
+        val endRed = findRed()
+        val endBlueRect = getBoundsOnScreen(endBlue)
+
+        if (startRed != null) {
+            verifyAndClearTransition(from.exitTransition, startBlueRect, startGreen, startRed)
+        } else {
+            verifyAndClearTransition(from.exitTransition, startBlueRect, startGreen)
+        }
+        verifyNoOtherTransitions(from)
+
+        if (endRed != null) {
+            verifyAndClearTransition(to.enterTransition, endBlueRect, endGreen, endRed)
+        } else {
+            verifyAndClearTransition(to.enterTransition, endBlueRect, endGreen)
+        }
+        verifyAndClearTransition(to.sharedElementEnter, startBlueRect, startBlue, endBlue)
+        verifyNoOtherTransitions(to)
+    }
+
+    private fun verifyCrossTransition(
+        swapSource: Boolean,
+        from1: TransitionFragment,
+        from2: TransitionFragment
+    ) {
+        val startNumOnBackStackChanged = onBackStackChangedTimes
+        val changesPerOperation = if (reorderingAllowed) 1 else 2
+
+        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)
+        val fromSharedRect1 = getBoundsOnScreen(fromShared1)
+
+        val fromExitId2 = if (swapSource) R.id.blueSquare else R.id.greenSquare
+        val fromSharedId2 = if (swapSource) R.id.greenSquare else R.id.blueSquare
+        val fromExit2 = findViewById(from2, fromExitId2)
+        val fromShared2 = findViewById(from2, fromSharedId2)
+        val fromSharedRect2 = getBoundsOnScreen(fromShared2)
+
+        val sharedElementName = if (swapSource) "blueSquare" else "greenSquare"
+
+        activityRule.runOnUiThread {
+            fragmentManager.beginTransaction()
+                .setReorderingAllowed(reorderingAllowed)
+                .addSharedElement(fromShared1, "blueSquare")
+                .replace(R.id.fragmentContainer1, to1)
+                .addToBackStack(null)
+                .commit()
+            fragmentManager.beginTransaction()
+                .setReorderingAllowed(reorderingAllowed)
+                .addSharedElement(fromShared2, sharedElementName)
+                .replace(R.id.fragmentContainer2, to2)
+                .addToBackStack(null)
+                .commit()
+        }
+        FragmentTestUtil.waitForExecution(activityRule)
+        assertThat(onBackStackChangedTimes)
+            .isEqualTo(startNumOnBackStackChanged + changesPerOperation)
+
+        from1.waitForTransition()
+        from2.waitForTransition()
+        to1.waitForTransition()
+        to2.waitForTransition()
+
+        val toEnter1 = findViewById(to1, R.id.greenSquare)
+        val toShared1 = findViewById(to1, R.id.blueSquare)
+        val toSharedRect1 = getBoundsOnScreen(toShared1)
+
+        val toEnter2 = findViewById(to2, fromSharedId2)
+        val toShared2 = findViewById(to2, fromExitId2)
+        val toSharedRect2 = getBoundsOnScreen(toShared2)
+
+        verifyAndClearTransition(from1.exitTransition, fromSharedRect1, fromExit1)
+        verifyAndClearTransition(from2.exitTransition, fromSharedRect2, fromExit2)
+        verifyNoOtherTransitions(from1)
+        verifyNoOtherTransitions(from2)
+
+        verifyAndClearTransition(to1.enterTransition, toSharedRect1, toEnter1)
+        verifyAndClearTransition(to2.enterTransition, toSharedRect2, toEnter2)
+        verifyAndClearTransition(to1.sharedElementEnter, fromSharedRect1, fromShared1, toShared1)
+        verifyAndClearTransition(to2.sharedElementEnter, fromSharedRect2, fromShared2, toShared2)
+        verifyNoOtherTransitions(to1)
+        verifyNoOtherTransitions(to2)
+
+        // Now pop it back
+        activityRule.runOnUiThread {
+            fragmentManager.popBackStack()
+            fragmentManager.popBackStack()
+        }
+        FragmentTestUtil.waitForExecution(activityRule)
+        assertThat(onBackStackChangedTimes)
+            .isEqualTo(startNumOnBackStackChanged + changesPerOperation + 1)
+
+        from1.waitForTransition()
+        from2.waitForTransition()
+        to1.waitForTransition()
+        to2.waitForTransition()
+
+        val returnEnter1 = findViewById(from1, R.id.greenSquare)
+        val returnShared1 = findViewById(from1, R.id.blueSquare)
+
+        val returnEnter2 = findViewById(from2, fromExitId2)
+        val returnShared2 = findViewById(from2, fromSharedId2)
+
+        verifyAndClearTransition(to1.returnTransition, toSharedRect1, toEnter1)
+        verifyAndClearTransition(to2.returnTransition, toSharedRect2, toEnter2)
+        verifyAndClearTransition(to1.sharedElementReturn, toSharedRect1, toShared1, returnShared1)
+        verifyAndClearTransition(to2.sharedElementReturn, toSharedRect2, toShared2, returnShared2)
+        verifyNoOtherTransitions(to1)
+        verifyNoOtherTransitions(to2)
+
+        verifyAndClearTransition(from1.reenterTransition, fromSharedRect1, returnEnter1)
+        verifyAndClearTransition(from2.reenterTransition, fromSharedRect2, returnEnter2)
+        verifyNoOtherTransitions(from1)
+        verifyNoOtherTransitions(from2)
+    }
+
+    private fun verifyPopTransition(
+        numPops: Int,
+        from: TransitionFragment,
+        to: TransitionFragment,
+        vararg others: TransitionFragment
+    ) {
+        val startOnBackStackChanged = onBackStackChangedTimes
+        val startBlue = findBlue()
+        val startGreen = findGreen()
+        val startRed = findRed()
+        val startSharedRect = getBoundsOnScreen(startBlue)
+
+        instrumentation.runOnMainSync {
+            for (i in 0 until numPops) {
+                fragmentManager.popBackStack()
+            }
+        }
+        FragmentTestUtil.waitForExecution(activityRule)
+        assertThat(onBackStackChangedTimes).isEqualTo((startOnBackStackChanged + 1))
+
+        to.waitForTransition()
+        val endGreen = findGreen()
+        val endBlue = findBlue()
+        val endRed = findRed()
+        val endSharedRect = getBoundsOnScreen(endBlue)
+
+        if (startRed != null) {
+            verifyAndClearTransition(from.returnTransition, startSharedRect, startGreen, startRed)
+        } else {
+            verifyAndClearTransition(from.returnTransition, startSharedRect, startGreen)
+        }
+        verifyAndClearTransition(from.sharedElementReturn, startSharedRect, startBlue, endBlue)
+        verifyNoOtherTransitions(from)
+
+        if (endRed != null) {
+            verifyAndClearTransition(to.reenterTransition, endSharedRect, endGreen, endRed)
+        } else {
+            verifyAndClearTransition(to.reenterTransition, endSharedRect, endGreen)
+        }
+        verifyNoOtherTransitions(to)
+
+        for (fragment in others) {
+            verifyNoOtherTransitions(fragment)
+        }
+    }
+
+    class ComplexTransitionFragment : TransitionFragment(R.layout.scene2) {
+        val sharedElementEnterTransition1 = TrackingTransition()
+        val sharedElementEnterTransition2 = TrackingTransition()
+        val sharedElementReturnTransition1 = TrackingTransition()
+        val sharedElementReturnTransition2 = TrackingTransition()
+
+        val sharedElementEnterTransition: TransitionSet = TransitionSet()
+            .addTransition(sharedElementEnterTransition1)
+            .addTransition(sharedElementEnterTransition2)
+        val sharedElementReturnTransition: TransitionSet = TransitionSet()
+            .addTransition(sharedElementReturnTransition1)
+            .addTransition(sharedElementReturnTransition2)
+
+        init {
+            sharedElementEnterTransition1.addTarget(R.id.blueSquare)
+            sharedElementEnterTransition2.addTarget(R.id.greenSquare)
+            sharedElementReturnTransition1.addTarget(R.id.blueSquare)
+            sharedElementReturnTransition2.addTarget(R.id.greenSquare)
+            setSharedElementEnterTransition(sharedElementEnterTransition)
+            setSharedElementReturnTransition(sharedElementReturnTransition)
+        }
+    }
+
+    class InvisibleFragment : TransitionFragment(R.layout.scene1) {
+        override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+            view.visibility = View.INVISIBLE
+            super.onViewCreated(view, savedInstanceState)
+        }
+    }
+
+    companion object {
+        @JvmStatic
+        @Parameterized.Parameters
+        fun data(): Array<Boolean> {
+            return arrayOf(false, true)
+        }
+
+        private fun getBoundsOnScreen(view: View): Rect {
+            val loc = IntArray(2)
+            view.getLocationOnScreen(loc)
+            return Rect(loc[0], loc[1], loc[0] + view.width, loc[1] + view.height)
+        }
+    }
+}
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
new file mode 100644
index 0000000..c1d4d40
--- /dev/null
+++ b/fragment/src/androidTest/java/androidx/fragment/app/FragmentViewTest.kt
@@ -0,0 +1,1045 @@
+/*
+ * 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 org.junit.Assert.fail
+
+import android.os.Bundle
+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
+import androidx.test.filters.SmallTest
+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.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class FragmentViewTest {
+    @get:Rule
+    val activityRule = ActivityTestRule(FragmentTestActivity::class.java)
+
+    private val instrumentation = InstrumentationRegistry.getInstrumentation()
+
+    // Test that adding a fragment adds the Views in the proper order. Popping the back stack
+    // should remove the correct Views.
+    @Test
+    fun addFragments() {
+        FragmentTestUtil.setContentView(activityRule, R.layout.simple_container)
+        val container =
+            activityRule.activity.findViewById<View>(R.id.fragmentContainer) as ViewGroup
+        val fm = activityRule.activity.supportFragmentManager
+
+        // One fragment with a view
+        val fragment1 = StrictViewFragment()
+        fm.beginTransaction().add(R.id.fragmentContainer, fragment1).addToBackStack(null).commit()
+        FragmentTestUtil.executePendingTransactions(activityRule)
+        FragmentTestUtil.assertChildren(container, fragment1)
+
+        // Add another on top
+        val fragment2 = StrictViewFragment()
+        fm.beginTransaction().add(R.id.fragmentContainer, fragment2).addToBackStack(null).commit()
+        FragmentTestUtil.executePendingTransactions(activityRule)
+        FragmentTestUtil.assertChildren(container, fragment1, fragment2)
+
+        // Now add two in one transaction:
+        val fragment3 = StrictViewFragment()
+        val fragment4 = StrictViewFragment()
+        fm.beginTransaction()
+            .add(R.id.fragmentContainer, fragment3)
+            .add(R.id.fragmentContainer, fragment4)
+            .addToBackStack(null)
+            .commit()
+        FragmentTestUtil.executePendingTransactions(activityRule)
+        FragmentTestUtil.assertChildren(container, fragment1, fragment2, fragment3, fragment4)
+
+        fm.popBackStack()
+        FragmentTestUtil.executePendingTransactions(activityRule)
+        FragmentTestUtil.assertChildren(container, fragment1, fragment2)
+
+        fm.popBackStack()
+        FragmentTestUtil.executePendingTransactions(activityRule)
+        assertThat(container.childCount).isEqualTo(1)
+        FragmentTestUtil.assertChildren(container, fragment1)
+
+        fm.popBackStack()
+        FragmentTestUtil.executePendingTransactions(activityRule)
+        FragmentTestUtil.assertChildren(container)
+    }
+
+    // Add fragments to multiple containers in the same transaction. Make sure that
+    // they pop correctly, too.
+    @Test
+    fun addTwoContainers() {
+        FragmentTestUtil.setContentView(activityRule, R.layout.double_container)
+        val container1 =
+            activityRule.activity.findViewById<View>(R.id.fragmentContainer1) as ViewGroup
+        val container2 =
+            activityRule.activity.findViewById<View>(R.id.fragmentContainer2) as ViewGroup
+        val fm = activityRule.activity.supportFragmentManager
+
+        val fragment1 = StrictViewFragment()
+        fm.beginTransaction().add(R.id.fragmentContainer1, fragment1).addToBackStack(null).commit()
+        FragmentTestUtil.executePendingTransactions(activityRule)
+        FragmentTestUtil.assertChildren(container1, fragment1)
+
+        val fragment2 = StrictViewFragment()
+        fm.beginTransaction().add(R.id.fragmentContainer2, fragment2).addToBackStack(null).commit()
+        FragmentTestUtil.executePendingTransactions(activityRule)
+        FragmentTestUtil.assertChildren(container2, fragment2)
+
+        val fragment3 = StrictViewFragment()
+        val fragment4 = StrictViewFragment()
+        fm.beginTransaction()
+            .add(R.id.fragmentContainer1, fragment3)
+            .add(R.id.fragmentContainer2, fragment4)
+            .addToBackStack(null)
+            .commit()
+        FragmentTestUtil.executePendingTransactions(activityRule)
+
+        FragmentTestUtil.assertChildren(container1, fragment1, fragment3)
+        FragmentTestUtil.assertChildren(container2, fragment2, fragment4)
+
+        fm.popBackStack()
+        FragmentTestUtil.executePendingTransactions(activityRule)
+        FragmentTestUtil.assertChildren(container1, fragment1)
+        FragmentTestUtil.assertChildren(container2, fragment2)
+
+        fm.popBackStack()
+        FragmentTestUtil.executePendingTransactions(activityRule)
+        FragmentTestUtil.assertChildren(container1, fragment1)
+        FragmentTestUtil.assertChildren(container2)
+
+        fm.popBackStack()
+        FragmentTestUtil.executePendingTransactions(activityRule)
+        assertThat(container1.childCount).isEqualTo(0)
+    }
+
+    // When you add a fragment that's has already been added, it should throw.
+    @Test
+    fun doubleAdd() {
+        FragmentTestUtil.setContentView(activityRule, R.layout.simple_container)
+        val fm = activityRule.activity.supportFragmentManager
+        val fragment1 = StrictViewFragment()
+        fm.beginTransaction().add(R.id.fragmentContainer, fragment1).commit()
+        FragmentTestUtil.executePendingTransactions(activityRule)
+
+        instrumentation.runOnMainSync {
+            try {
+                fm.beginTransaction()
+                    .add(R.id.fragmentContainer, fragment1)
+                    .addToBackStack(null)
+                    .commit()
+                fm.executePendingTransactions()
+                fail("Adding a fragment that is already added should be an error")
+            } catch (e: IllegalStateException) {
+                assertThat(e)
+                    .hasMessageThat().contains("Fragment already added: $fragment1")
+            }
+        }
+    }
+
+    // Make sure that removed fragments remove the right Views. Popping the back stack should
+    // add the Views back properly
+    @Test
+    fun removeFragments() {
+        FragmentTestUtil.setContentView(activityRule, R.layout.simple_container)
+        val container =
+            activityRule.activity.findViewById<View>(R.id.fragmentContainer) as ViewGroup
+        val fm = activityRule.activity.supportFragmentManager
+        val fragment1 = StrictViewFragment()
+        val fragment2 = StrictViewFragment()
+        val fragment3 = StrictViewFragment()
+        val fragment4 = StrictViewFragment()
+        fm.beginTransaction()
+            .add(R.id.fragmentContainer, fragment1, "1")
+            .add(R.id.fragmentContainer, fragment2, "2")
+            .add(R.id.fragmentContainer, fragment3, "3")
+            .add(R.id.fragmentContainer, fragment4, "4")
+            .commit()
+        FragmentTestUtil.executePendingTransactions(activityRule)
+        FragmentTestUtil.assertChildren(container, fragment1, fragment2, fragment3, fragment4)
+
+        // Remove a view
+        fm.beginTransaction().remove(fragment4).addToBackStack(null).commit()
+        FragmentTestUtil.executePendingTransactions(activityRule)
+
+        assertThat(container.childCount).isEqualTo(3)
+        FragmentTestUtil.assertChildren(container, fragment1, fragment2, fragment3)
+
+        // remove another one
+        fm.beginTransaction().remove(fragment2).addToBackStack(null).commit()
+        FragmentTestUtil.executePendingTransactions(activityRule)
+        FragmentTestUtil.assertChildren(container, fragment1, fragment3)
+
+        // Now remove the remaining:
+        fm.beginTransaction()
+            .remove(fragment3)
+            .remove(fragment1)
+            .addToBackStack(null)
+            .commit()
+        FragmentTestUtil.executePendingTransactions(activityRule)
+        FragmentTestUtil.assertChildren(container)
+
+        fm.popBackStack()
+        FragmentTestUtil.executePendingTransactions(activityRule)
+        val replacement1 = fm.findFragmentByTag("1")
+        val replacement3 = fm.findFragmentByTag("3")
+        FragmentTestUtil.assertChildren(container, replacement1, replacement3)
+
+        fm.popBackStack()
+        FragmentTestUtil.executePendingTransactions(activityRule)
+        val replacement2 = fm.findFragmentByTag("2")
+        FragmentTestUtil.assertChildren(container, replacement1, replacement3, replacement2)
+
+        fm.popBackStack()
+        FragmentTestUtil.executePendingTransactions(activityRule)
+        val replacement4 = fm.findFragmentByTag("4")
+        FragmentTestUtil.assertChildren(
+            container, replacement1, replacement3, replacement2,
+            replacement4
+        )
+    }
+
+    // Removing a hidden fragment should remove the View and popping should bring it back hidden
+    @Test
+    fun removeHiddenView() {
+        FragmentTestUtil.setContentView(activityRule, R.layout.simple_container)
+        val container =
+            activityRule.activity.findViewById<View>(R.id.fragmentContainer) as ViewGroup
+        val fm = activityRule.activity.supportFragmentManager
+        val fragment1 = StrictViewFragment()
+        fm.beginTransaction().add(R.id.fragmentContainer, fragment1, "1").hide(fragment1).commit()
+        FragmentTestUtil.executePendingTransactions(activityRule)
+        FragmentTestUtil.assertChildren(container, fragment1)
+        assertThat(fragment1.isHidden).isTrue()
+
+        fm.beginTransaction().remove(fragment1).addToBackStack(null).commit()
+        FragmentTestUtil.executePendingTransactions(activityRule)
+        FragmentTestUtil.assertChildren(container)
+
+        fm.popBackStack()
+        FragmentTestUtil.executePendingTransactions(activityRule)
+        val replacement1 = fm.findFragmentByTag("1")!!
+        FragmentTestUtil.assertChildren(container, replacement1)
+        assertThat(replacement1.isHidden).isTrue()
+        assertThat(replacement1.requireView().visibility).isEqualTo(View.GONE)
+    }
+
+    // Removing a detached fragment should do nothing to the View and popping should bring
+    // the Fragment back detached
+    @Test
+    fun removeDetatchedView() {
+        FragmentTestUtil.setContentView(activityRule, R.layout.simple_container)
+        val container =
+            activityRule.activity.findViewById<View>(R.id.fragmentContainer) as ViewGroup
+        val fm = activityRule.activity.supportFragmentManager
+        val fragment1 = StrictViewFragment()
+        fm.beginTransaction()
+            .add(R.id.fragmentContainer, fragment1, "1")
+            .detach(fragment1)
+            .commit()
+        FragmentTestUtil.executePendingTransactions(activityRule)
+        FragmentTestUtil.assertChildren(container)
+        assertThat(fragment1.isDetached).isTrue()
+
+        fm.beginTransaction().remove(fragment1).addToBackStack(null).commit()
+        FragmentTestUtil.executePendingTransactions(activityRule)
+        FragmentTestUtil.assertChildren(container)
+
+        fm.popBackStack()
+        FragmentTestUtil.executePendingTransactions(activityRule)
+        val replacement1 = fm.findFragmentByTag("1")!!
+        FragmentTestUtil.assertChildren(container)
+        assertThat(replacement1.isDetached).isTrue()
+    }
+
+    // Unlike adding the same fragment twice, you should be able to add and then remove and then
+    // add the same fragment in one transaction.
+    @Test
+    fun addRemoveAdd() {
+        FragmentTestUtil.setContentView(activityRule, R.layout.simple_container)
+        val container =
+            activityRule.activity.findViewById<View>(R.id.fragmentContainer) as ViewGroup
+        val fm = activityRule.activity.supportFragmentManager
+        val fragment = StrictViewFragment()
+        fm.beginTransaction()
+            .add(R.id.fragmentContainer, fragment)
+            .remove(fragment)
+            .add(R.id.fragmentContainer, fragment)
+            .addToBackStack(null)
+            .commit()
+        FragmentTestUtil.executePendingTransactions(activityRule)
+        FragmentTestUtil.assertChildren(container, fragment)
+
+        fm.popBackStack()
+        FragmentTestUtil.executePendingTransactions(activityRule)
+        FragmentTestUtil.assertChildren(container)
+    }
+
+    // Removing a fragment that isn't in should not throw
+    @Test
+    fun removeNotThere() {
+        FragmentTestUtil.setContentView(activityRule, R.layout.simple_container)
+        val fm = activityRule.activity.supportFragmentManager
+        val fragment = StrictViewFragment()
+        fm.beginTransaction().remove(fragment).commit()
+        FragmentTestUtil.executePendingTransactions(activityRule)
+    }
+
+    // Hide a fragment and its View should be GONE. Then pop it and the View should be VISIBLE
+    @Test
+    fun hideFragment() {
+        FragmentTestUtil.setContentView(activityRule, R.layout.simple_container)
+        val container =
+            activityRule.activity.findViewById<View>(R.id.fragmentContainer) as ViewGroup
+        val fm = activityRule.activity.supportFragmentManager
+        val fragment = StrictViewFragment()
+        fm.beginTransaction().add(R.id.fragmentContainer, fragment).commit()
+        FragmentTestUtil.executePendingTransactions(activityRule)
+
+        FragmentTestUtil.assertChildren(container, fragment)
+        assertThat(fragment.requireView().visibility).isEqualTo(View.VISIBLE)
+
+        fm.beginTransaction().hide(fragment).addToBackStack(null).commit()
+        FragmentTestUtil.executePendingTransactions(activityRule)
+
+        FragmentTestUtil.assertChildren(container, fragment)
+        assertThat(fragment.isHidden).isTrue()
+        assertThat(fragment.requireView().visibility).isEqualTo(View.GONE)
+
+        fm.popBackStack()
+        FragmentTestUtil.executePendingTransactions(activityRule)
+
+        FragmentTestUtil.assertChildren(container, fragment)
+        assertThat(fragment.isHidden).isFalse()
+        assertThat(fragment.requireView().visibility).isEqualTo(View.VISIBLE)
+    }
+
+    // Hiding a hidden fragment should not throw
+    @Test
+    fun doubleHide() {
+        FragmentTestUtil.setContentView(activityRule, R.layout.simple_container)
+        val fm = activityRule.activity.supportFragmentManager
+        val fragment = StrictViewFragment()
+        fm.beginTransaction()
+            .add(R.id.fragmentContainer, fragment)
+            .hide(fragment)
+            .hide(fragment)
+            .commit()
+        FragmentTestUtil.executePendingTransactions(activityRule)
+    }
+
+    // Hiding a non-existing fragment should not throw
+    @Test
+    fun hideUnAdded() {
+        FragmentTestUtil.setContentView(activityRule, R.layout.simple_container)
+        val fm = activityRule.activity.supportFragmentManager
+        val fragment = StrictViewFragment()
+        fm.beginTransaction()
+            .hide(fragment)
+            .commit()
+        FragmentTestUtil.executePendingTransactions(activityRule)
+    }
+
+    // Show a hidden fragment and its View should be VISIBLE. Then pop it and the View should be
+    // GONE.
+    @Test
+    fun showFragment() {
+        FragmentTestUtil.setContentView(activityRule, R.layout.simple_container)
+        val container =
+            activityRule.activity.findViewById<View>(R.id.fragmentContainer) as ViewGroup
+        val fm = activityRule.activity.supportFragmentManager
+        val fragment = StrictViewFragment()
+        fm.beginTransaction().add(R.id.fragmentContainer, fragment).hide(fragment).commit()
+        FragmentTestUtil.executePendingTransactions(activityRule)
+
+        FragmentTestUtil.assertChildren(container, fragment)
+        assertThat(fragment.isHidden).isTrue()
+        assertThat(fragment.requireView().visibility).isEqualTo(View.GONE)
+
+        fm.beginTransaction().show(fragment).addToBackStack(null).commit()
+        FragmentTestUtil.executePendingTransactions(activityRule)
+
+        FragmentTestUtil.assertChildren(container, fragment)
+        assertThat(fragment.isHidden).isFalse()
+        assertThat(fragment.requireView().visibility).isEqualTo(View.VISIBLE)
+
+        fm.popBackStack()
+        FragmentTestUtil.executePendingTransactions(activityRule)
+
+        FragmentTestUtil.assertChildren(container, fragment)
+        assertThat(fragment.isHidden).isTrue()
+        assertThat(fragment.requireView().visibility).isEqualTo(View.GONE)
+    }
+
+    // Showing a shown fragment should not throw
+    @Test
+    fun showShown() {
+        FragmentTestUtil.setContentView(activityRule, R.layout.simple_container)
+        val fm = activityRule.activity.supportFragmentManager
+        val fragment = StrictViewFragment()
+        fm.beginTransaction()
+            .add(R.id.fragmentContainer, fragment)
+            .show(fragment)
+            .commit()
+        FragmentTestUtil.executePendingTransactions(activityRule)
+    }
+
+    // Showing a non-existing fragment should not throw
+    @Test
+    fun showUnAdded() {
+        FragmentTestUtil.setContentView(activityRule, R.layout.simple_container)
+        val fm = activityRule.activity.supportFragmentManager
+        val fragment = StrictViewFragment()
+        fm.beginTransaction()
+            .show(fragment)
+            .commit()
+        FragmentTestUtil.executePendingTransactions(activityRule)
+    }
+
+    // Detaching a fragment should remove the View from the hierarchy. Then popping it should
+    // bring it back VISIBLE
+    @Test
+    fun detachFragment() {
+        FragmentTestUtil.setContentView(activityRule, R.layout.simple_container)
+        val container =
+            activityRule.activity.findViewById<View>(R.id.fragmentContainer) as ViewGroup
+        val fm = activityRule.activity.supportFragmentManager
+        val fragment = StrictViewFragment()
+        fm.beginTransaction().add(R.id.fragmentContainer, fragment).commit()
+        FragmentTestUtil.executePendingTransactions(activityRule)
+
+        FragmentTestUtil.assertChildren(container, fragment)
+        assertThat(fragment.isDetached).isFalse()
+        assertThat(fragment.requireView().visibility).isEqualTo(View.VISIBLE)
+
+        fm.beginTransaction().detach(fragment).addToBackStack(null).commit()
+        FragmentTestUtil.executePendingTransactions(activityRule)
+
+        FragmentTestUtil.assertChildren(container)
+        assertThat(fragment.isDetached).isTrue()
+
+        fm.popBackStack()
+        FragmentTestUtil.executePendingTransactions(activityRule)
+
+        FragmentTestUtil.assertChildren(container, fragment)
+        assertThat(fragment.isDetached).isFalse()
+        assertThat(fragment.requireView().visibility).isEqualTo(View.VISIBLE)
+    }
+
+    // Detaching a hidden fragment should remove the View from the hierarchy. Then popping it should
+    // bring it back hidden
+    @Test
+    fun detachHiddenFragment() {
+        FragmentTestUtil.setContentView(activityRule, R.layout.simple_container)
+        val container =
+            activityRule.activity.findViewById<View>(R.id.fragmentContainer) as ViewGroup
+        val fm = activityRule.activity.supportFragmentManager
+        val fragment = StrictViewFragment()
+        fm.beginTransaction().add(R.id.fragmentContainer, fragment).hide(fragment).commit()
+        FragmentTestUtil.executePendingTransactions(activityRule)
+
+        FragmentTestUtil.assertChildren(container, fragment)
+        assertThat(fragment.isDetached).isFalse()
+        assertThat(fragment.isHidden).isTrue()
+        assertThat(fragment.requireView().visibility).isEqualTo(View.GONE)
+
+        fm.beginTransaction().detach(fragment).addToBackStack(null).commit()
+        FragmentTestUtil.executePendingTransactions(activityRule)
+
+        FragmentTestUtil.assertChildren(container)
+        assertThat(fragment.isHidden).isTrue()
+        assertThat(fragment.isDetached).isTrue()
+
+        fm.popBackStack()
+        FragmentTestUtil.executePendingTransactions(activityRule)
+
+        FragmentTestUtil.assertChildren(container, fragment)
+        assertThat(fragment.isHidden).isTrue()
+        assertThat(fragment.isDetached).isFalse()
+        assertThat(fragment.requireView().visibility).isEqualTo(View.GONE)
+    }
+
+    // Detaching a detached fragment should not throw
+    @Test
+    fun detachDetatched() {
+        FragmentTestUtil.setContentView(activityRule, R.layout.simple_container)
+        val fm = activityRule.activity.supportFragmentManager
+        val fragment = StrictViewFragment()
+        fm.beginTransaction()
+            .add(R.id.fragmentContainer, fragment)
+            .detach(fragment)
+            .detach(fragment)
+            .commit()
+        FragmentTestUtil.executePendingTransactions(activityRule)
+    }
+
+    // Detaching a non-existing fragment should not throw
+    @Test
+    fun detachUnAdded() {
+        FragmentTestUtil.setContentView(activityRule, R.layout.simple_container)
+        val fm = activityRule.activity.supportFragmentManager
+        val fragment = StrictViewFragment()
+        fm.beginTransaction()
+            .detach(fragment)
+            .commit()
+        FragmentTestUtil.executePendingTransactions(activityRule)
+    }
+
+    // Attaching a fragment should add the View back into the hierarchy. Then popping it should
+    // remove it again
+    @Test
+    fun attachFragment() {
+        FragmentTestUtil.setContentView(activityRule, R.layout.simple_container)
+        val container =
+            activityRule.activity.findViewById<View>(R.id.fragmentContainer) as ViewGroup
+        val fm = activityRule.activity.supportFragmentManager
+        val fragment = StrictViewFragment()
+        fm.beginTransaction().add(R.id.fragmentContainer, fragment).detach(fragment).commit()
+        FragmentTestUtil.executePendingTransactions(activityRule)
+
+        FragmentTestUtil.assertChildren(container)
+        assertThat(fragment.isDetached).isTrue()
+
+        fm.beginTransaction().attach(fragment).addToBackStack(null).commit()
+        FragmentTestUtil.executePendingTransactions(activityRule)
+
+        FragmentTestUtil.assertChildren(container, fragment)
+        assertThat(fragment.isDetached).isFalse()
+        assertThat(fragment.requireView().visibility).isEqualTo(View.VISIBLE)
+
+        fm.popBackStack()
+        FragmentTestUtil.executePendingTransactions(activityRule)
+
+        FragmentTestUtil.assertChildren(container)
+        assertThat(fragment.isDetached).isTrue()
+    }
+
+    // Attaching a hidden fragment should add the View as GONE the hierarchy. Then popping it should
+    // remove it again.
+    @Test
+    fun attachHiddenFragment() {
+        FragmentTestUtil.setContentView(activityRule, R.layout.simple_container)
+        val container =
+            activityRule.activity.findViewById<View>(R.id.fragmentContainer) as ViewGroup
+        val fm = activityRule.activity.supportFragmentManager
+        val fragment = StrictViewFragment()
+        fm.beginTransaction()
+            .add(R.id.fragmentContainer, fragment)
+            .hide(fragment)
+            .detach(fragment)
+            .commit()
+        FragmentTestUtil.executePendingTransactions(activityRule)
+
+        FragmentTestUtil.assertChildren(container)
+        assertThat(fragment.isDetached).isTrue()
+        assertThat(fragment.isHidden).isTrue()
+
+        fm.beginTransaction().attach(fragment).addToBackStack(null).commit()
+        FragmentTestUtil.executePendingTransactions(activityRule)
+
+        FragmentTestUtil.assertChildren(container, fragment)
+        assertThat(fragment.isHidden).isTrue()
+        assertThat(fragment.isDetached).isFalse()
+        assertThat(fragment.requireView().visibility).isEqualTo(View.GONE)
+
+        fm.popBackStack()
+        FragmentTestUtil.executePendingTransactions(activityRule)
+
+        FragmentTestUtil.assertChildren(container)
+        assertThat(fragment.isDetached).isTrue()
+        assertThat(fragment.isHidden).isTrue()
+    }
+
+    // Attaching an attached fragment should not throw
+    @Test
+    fun attachAttached() {
+        FragmentTestUtil.setContentView(activityRule, R.layout.simple_container)
+        val fm = activityRule.activity.supportFragmentManager
+        val fragment = StrictViewFragment()
+        fm.beginTransaction()
+            .add(R.id.fragmentContainer, fragment)
+            .attach(fragment)
+            .commit()
+        FragmentTestUtil.executePendingTransactions(activityRule)
+    }
+
+    // Attaching a non-existing fragment should not throw
+    @Test
+    fun attachUnAdded() {
+        FragmentTestUtil.setContentView(activityRule, R.layout.simple_container)
+        val fm = activityRule.activity.supportFragmentManager
+        val fragment = StrictViewFragment()
+        fm.beginTransaction()
+            .attach(fragment)
+            .commit()
+        FragmentTestUtil.executePendingTransactions(activityRule)
+    }
+
+    // Simple replace of one fragment in a container. Popping should replace it back again
+    @Test
+    fun replaceOne() {
+        FragmentTestUtil.setContentView(activityRule, R.layout.simple_container)
+        val container =
+            activityRule.activity.findViewById<View>(R.id.fragmentContainer) as ViewGroup
+        val fm = activityRule.activity.supportFragmentManager
+        val fragment1 = StrictViewFragment()
+        fm.beginTransaction().add(R.id.fragmentContainer, fragment1, "1").commit()
+        FragmentTestUtil.executePendingTransactions(activityRule)
+
+        FragmentTestUtil.assertChildren(container, fragment1)
+
+        val fragment2 = StrictViewFragment()
+        fm.beginTransaction()
+            .replace(R.id.fragmentContainer, fragment2)
+            .addToBackStack(null)
+            .commit()
+        FragmentTestUtil.executePendingTransactions(activityRule)
+
+        FragmentTestUtil.assertChildren(container, fragment2)
+        assertThat(fragment2.requireView().visibility).isEqualTo(View.VISIBLE)
+
+        fm.popBackStack()
+        FragmentTestUtil.executePendingTransactions(activityRule)
+
+        val replacement1 = fm.findFragmentByTag("1")!!
+        assertThat(replacement1).isNotNull()
+        FragmentTestUtil.assertChildren(container, replacement1)
+        assertThat(replacement1.isHidden).isFalse()
+        assertThat(replacement1.isAdded).isTrue()
+        assertThat(replacement1.isDetached).isFalse()
+        assertThat(replacement1.requireView().visibility).isEqualTo(View.VISIBLE)
+    }
+
+    // Replace of multiple fragments in a container. Popping should replace it back again
+    @Test
+    fun replaceTwo() {
+        FragmentTestUtil.setContentView(activityRule, R.layout.simple_container)
+        val container =
+            activityRule.activity.findViewById<View>(R.id.fragmentContainer) as ViewGroup
+        val fm = activityRule.activity.supportFragmentManager
+        val fragment1 = StrictViewFragment()
+        val fragment2 = StrictViewFragment()
+        fm.beginTransaction()
+            .add(R.id.fragmentContainer, fragment1, "1")
+            .add(R.id.fragmentContainer, fragment2, "2")
+            .hide(fragment2)
+            .commit()
+        FragmentTestUtil.executePendingTransactions(activityRule)
+
+        FragmentTestUtil.assertChildren(container, fragment1, fragment2)
+
+        val fragment3 = StrictViewFragment()
+        fm.beginTransaction()
+            .replace(R.id.fragmentContainer, fragment3)
+            .addToBackStack(null)
+            .commit()
+        FragmentTestUtil.executePendingTransactions(activityRule)
+
+        FragmentTestUtil.assertChildren(container, fragment3)
+        assertThat(fragment3.requireView().visibility).isEqualTo(View.VISIBLE)
+
+        fm.popBackStack()
+        FragmentTestUtil.executePendingTransactions(activityRule)
+
+        val replacement1 = fm.findFragmentByTag("1")!!
+        val replacement2 = fm.findFragmentByTag("2")!!
+        assertThat(replacement1).isNotNull()
+        assertThat(replacement2).isNotNull()
+        FragmentTestUtil.assertChildren(container, replacement1, replacement2)
+        assertThat(replacement1.isHidden).isFalse()
+        assertThat(replacement1.isAdded).isTrue()
+        assertThat(replacement1.isDetached).isFalse()
+        assertThat(replacement1.requireView().visibility).isEqualTo(View.VISIBLE)
+
+        // fragment2 was hidden, so it should be returned hidden
+        assertThat(replacement2.isHidden).isTrue()
+        assertThat(replacement2.isAdded).isTrue()
+        assertThat(replacement2.isDetached).isFalse()
+        assertThat(replacement2.requireView().visibility).isEqualTo(View.GONE)
+    }
+
+    // Replace of empty container. Should act as add and popping should just remove the fragment
+    @Test
+    fun replaceZero() {
+        FragmentTestUtil.setContentView(activityRule, R.layout.simple_container)
+        val container =
+            activityRule.activity.findViewById<View>(R.id.fragmentContainer) as ViewGroup
+        val fm = activityRule.activity.supportFragmentManager
+
+        val fragment = StrictViewFragment()
+        fm.beginTransaction()
+            .replace(R.id.fragmentContainer, fragment)
+            .addToBackStack(null)
+            .commit()
+        FragmentTestUtil.executePendingTransactions(activityRule)
+
+        FragmentTestUtil.assertChildren(container, fragment)
+        assertThat(fragment.requireView().visibility).isEqualTo(View.VISIBLE)
+
+        fm.popBackStack()
+        FragmentTestUtil.executePendingTransactions(activityRule)
+
+        FragmentTestUtil.assertChildren(container)
+    }
+
+    // Replace a fragment that exists with itself
+    @Test
+    fun replaceExisting() {
+        FragmentTestUtil.setContentView(activityRule, R.layout.simple_container)
+        val container =
+            activityRule.activity.findViewById<View>(R.id.fragmentContainer) as ViewGroup
+        val fm = activityRule.activity.supportFragmentManager
+        val fragment1 = StrictViewFragment()
+        val fragment2 = StrictViewFragment()
+        fm.beginTransaction()
+            .add(R.id.fragmentContainer, fragment1, "1")
+            .add(R.id.fragmentContainer, fragment2, "2")
+            .commit()
+        FragmentTestUtil.executePendingTransactions(activityRule)
+
+        FragmentTestUtil.assertChildren(container, fragment1, fragment2)
+
+        fm.beginTransaction()
+            .replace(R.id.fragmentContainer, fragment1)
+            .addToBackStack(null)
+            .commit()
+        FragmentTestUtil.executePendingTransactions(activityRule)
+
+        FragmentTestUtil.assertChildren(container, fragment1)
+
+        fm.popBackStack()
+        FragmentTestUtil.executePendingTransactions(activityRule)
+
+        val replacement1 = fm.findFragmentByTag("1")
+        val replacement2 = fm.findFragmentByTag("2")
+
+        assertThat(replacement1).isSameAs(fragment1)
+        FragmentTestUtil.assertChildren(container, replacement1, replacement2)
+    }
+
+    // Have two replace operations in the same transaction to ensure that they
+    // don't interfere with each other
+    @Test
+    fun replaceReplace() {
+        FragmentTestUtil.setContentView(activityRule, R.layout.double_container)
+        val container1 =
+            activityRule.activity.findViewById<View>(R.id.fragmentContainer1) as ViewGroup
+        val container2 =
+            activityRule.activity.findViewById<View>(R.id.fragmentContainer2) as ViewGroup
+        val fm = activityRule.activity.supportFragmentManager
+
+        val fragment1 = StrictViewFragment()
+        val fragment2 = StrictViewFragment()
+        val fragment3 = StrictViewFragment()
+        val fragment4 = StrictViewFragment()
+        val fragment5 = StrictViewFragment()
+        fm.beginTransaction()
+            .add(R.id.fragmentContainer1, fragment1)
+            .add(R.id.fragmentContainer2, fragment2)
+            .replace(R.id.fragmentContainer1, fragment3)
+            .replace(R.id.fragmentContainer2, fragment4)
+            .replace(R.id.fragmentContainer1, fragment5)
+            .addToBackStack(null)
+            .commit()
+        FragmentTestUtil.executePendingTransactions(activityRule)
+
+        assertChildren(container1, fragment5)
+        assertChildren(container2, fragment4)
+
+        fm.popBackStack()
+        FragmentTestUtil.executePendingTransactions(activityRule)
+
+        assertChildren(container1)
+        assertChildren(container2)
+    }
+
+    // Test to prevent regressions in FragmentManager fragment replace method. See b/24693644
+    @Test
+    fun testReplaceFragment() {
+        FragmentTestUtil.setContentView(activityRule, R.layout.simple_container)
+        val fm = activityRule.activity.supportFragmentManager
+        val fragmentA = StrictViewFragment(R.layout.text_a)
+
+        fm.beginTransaction()
+            .add(R.id.fragmentContainer, fragmentA)
+            .addToBackStack(null)
+            .commit()
+        FragmentTestUtil.executePendingTransactions(activityRule)
+
+        assertThat(findViewById(R.id.textA)).isNotNull()
+        assertThat(findViewById(R.id.textB)).isNull()
+        assertThat(findViewById(R.id.textC)).isNull()
+
+        val fragmentB = StrictViewFragment(R.layout.text_b)
+        fm.beginTransaction()
+            .add(R.id.fragmentContainer, fragmentB)
+            .addToBackStack(null)
+            .commit()
+        FragmentTestUtil.executePendingTransactions(activityRule)
+        assertThat(findViewById(R.id.textA)).isNotNull()
+        assertThat(findViewById(R.id.textB)).isNotNull()
+        assertThat(findViewById(R.id.textC)).isNull()
+
+        val fragmentC = StrictViewFragment(R.layout.text_c)
+        fm.beginTransaction()
+            .replace(R.id.fragmentContainer, fragmentC)
+            .addToBackStack(null)
+            .commit()
+        FragmentTestUtil.executePendingTransactions(activityRule)
+        assertThat(findViewById(R.id.textA)).isNull()
+        assertThat(findViewById(R.id.textB)).isNull()
+        assertThat(findViewById(R.id.textC)).isNotNull()
+    }
+
+    // Test that adding a fragment with invisible or gone views does not end up with the view
+    // being visible
+    @Test
+    fun addInvisibleAndGoneFragments() {
+        FragmentTestUtil.setContentView(activityRule, R.layout.simple_container)
+        val container =
+            activityRule.activity.findViewById<View>(R.id.fragmentContainer) as ViewGroup
+        val fm = activityRule.activity.supportFragmentManager
+
+        val fragment1 = InvisibleFragment()
+        fm.beginTransaction().add(R.id.fragmentContainer, fragment1).addToBackStack(null).commit()
+        FragmentTestUtil.executePendingTransactions(activityRule)
+        FragmentTestUtil.assertChildren(container, fragment1)
+
+        assertThat(fragment1.requireView().visibility).isEqualTo(View.INVISIBLE)
+
+        val fragment2 = InvisibleFragment()
+        fragment2.visibility = View.GONE
+        fm.beginTransaction()
+            .replace(R.id.fragmentContainer, fragment2)
+            .addToBackStack(null)
+            .commit()
+        FragmentTestUtil.executePendingTransactions(activityRule)
+        FragmentTestUtil.assertChildren(container, fragment2)
+
+        assertThat(fragment2.requireView().visibility).isEqualTo(View.GONE)
+    }
+
+    // Test to ensure that popping and adding a fragment properly track the fragments added
+    // and removed.
+    @Test
+    fun popAdd() {
+        FragmentTestUtil.setContentView(activityRule, R.layout.simple_container)
+        val container =
+            activityRule.activity.findViewById<View>(R.id.fragmentContainer) as ViewGroup
+        val fm = activityRule.activity.supportFragmentManager
+
+        // One fragment with a view
+        val fragment1 = StrictViewFragment()
+        fm.beginTransaction().add(R.id.fragmentContainer, fragment1).addToBackStack(null).commit()
+        FragmentTestUtil.executePendingTransactions(activityRule)
+        FragmentTestUtil.assertChildren(container, fragment1)
+
+        val fragment2 = StrictViewFragment()
+        val fragment3 = StrictViewFragment()
+        instrumentation.runOnMainSync {
+            fm.popBackStack()
+            fm.beginTransaction()
+                .replace(R.id.fragmentContainer, fragment2)
+                .addToBackStack(null)
+                .commit()
+            fm.executePendingTransactions()
+            fm.popBackStack()
+            fm.beginTransaction()
+                .replace(R.id.fragmentContainer, fragment3)
+                .addToBackStack(null)
+                .commit()
+            fm.executePendingTransactions()
+        }
+        FragmentTestUtil.assertChildren(container, fragment3)
+    }
+
+    // Ensure that ordered transactions are executed individually rather than together.
+    // This forces references from one fragment to another that should be executed earlier
+    // to work.
+    @Test
+    fun orderedOperationsTogether() {
+        FragmentTestUtil.setContentView(activityRule, R.layout.simple_container)
+        val container =
+            activityRule.activity.findViewById<View>(R.id.fragmentContainer) as ViewGroup
+        val fm = activityRule.activity.supportFragmentManager
+
+        val fragment1 = StrictViewFragment(R.layout.scene1)
+        val fragment2 = StrictViewFragment(R.layout.fragment_a)
+
+        activityRule.runOnUiThread {
+            fm.beginTransaction()
+                .add(R.id.fragmentContainer, fragment1)
+                .setReorderingAllowed(false)
+                .addToBackStack(null)
+                .commit()
+            fm.beginTransaction()
+                .add(R.id.squareContainer, fragment2)
+                .setReorderingAllowed(false)
+                .addToBackStack(null)
+                .commit()
+            fm.executePendingTransactions()
+        }
+        FragmentTestUtil.assertChildren(container, fragment1)
+        assertThat(findViewById(R.id.textA)).isNotNull()
+    }
+
+    // Ensure that there is no problem if the child fragment manager is used before
+    // the View has been added.
+    @Test
+    fun childFragmentManager() {
+        FragmentTestUtil.setContentView(activityRule, R.layout.simple_container)
+        val container =
+            activityRule.activity.findViewById<View>(R.id.fragmentContainer) as ViewGroup
+        val fm = activityRule.activity.supportFragmentManager
+
+        val fragment1 = ParentFragment()
+
+        fm.beginTransaction()
+            .add(R.id.fragmentContainer, fragment1)
+            .addToBackStack(null)
+            .commit()
+
+        FragmentTestUtil.executePendingTransactions(activityRule)
+
+        FragmentTestUtil.assertChildren(container, fragment1)
+        val innerContainer =
+            fragment1.requireView().findViewById<ViewGroup>(R.id.fragmentContainer1)
+
+        val fragment2 = fragment1.childFragmentManager.findFragmentByTag("inner")
+        FragmentTestUtil.assertChildren(innerContainer, fragment2)
+    }
+
+    // Popping the backstack with ordered fragments should execute the operations together.
+    // When a non-backstack fragment will be raised, it should not be destroyed.
+    @Test
+    fun popToNonBackStackFragment() {
+        FragmentTestUtil.setContentView(activityRule, R.layout.simple_container)
+        val fm = activityRule.activity.supportFragmentManager
+
+        val fragment1 = SimpleViewFragment()
+
+        fm.beginTransaction()
+            .add(R.id.fragmentContainer, fragment1)
+            .commit()
+
+        FragmentTestUtil.executePendingTransactions(activityRule)
+
+        val fragment2 = SimpleViewFragment()
+
+        fm.beginTransaction()
+            .replace(R.id.fragmentContainer, fragment2)
+            .addToBackStack("two")
+            .commit()
+
+        FragmentTestUtil.executePendingTransactions(activityRule)
+
+        val fragment3 = SimpleViewFragment()
+
+        fm.beginTransaction()
+            .replace(R.id.fragmentContainer, fragment3)
+            .addToBackStack("three")
+            .commit()
+
+        FragmentTestUtil.executePendingTransactions(activityRule)
+
+        assertThat(fragment1.onCreateViewCount).isEqualTo(1)
+        assertThat(fragment2.onCreateViewCount).isEqualTo(1)
+        assertThat(fragment3.onCreateViewCount).isEqualTo(1)
+
+        FragmentTestUtil.popBackStackImmediate(
+            activityRule, "two",
+            FragmentManager.POP_BACK_STACK_INCLUSIVE
+        )
+
+        val container =
+            activityRule.activity.findViewById<View>(R.id.fragmentContainer) as ViewGroup
+
+        FragmentTestUtil.assertChildren(container, fragment1)
+
+        assertThat(fragment1.onCreateViewCount).isEqualTo(2)
+        assertThat(fragment2.onCreateViewCount).isEqualTo(1)
+        assertThat(fragment3.onCreateViewCount).isEqualTo(1)
+    }
+
+    private fun findViewById(viewId: Int): View? {
+        return activityRule.activity.findViewById(viewId)
+    }
+
+    private fun assertChildren(container: ViewGroup, vararg fragments: Fragment) {
+        val numFragments = fragments.size
+        assertWithMessage("There aren't the correct number of fragment Views in its container")
+            .that(container.childCount)
+            .isEqualTo(numFragments)
+        for (i in 0 until numFragments) {
+            assertWithMessage("Wrong Fragment View order for [$i]")
+                .that(fragments[i].view)
+                .isEqualTo(container.getChildAt(i))
+        }
+    }
+
+    class InvisibleFragment : StrictViewFragment() {
+        var visibility = View.INVISIBLE
+
+        override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+            view.visibility = visibility
+            super.onViewCreated(view, savedInstanceState)
+        }
+    }
+
+    class ParentFragment : StrictViewFragment(R.layout.double_container) {
+
+        override fun onCreateView(
+            inflater: LayoutInflater,
+            container: ViewGroup?,
+            savedInstanceState: Bundle?
+        ): View? {
+            val fragment2 = StrictViewFragment(R.layout.fragment_a)
+
+            childFragmentManager.beginTransaction()
+                .add(R.id.fragmentContainer1, fragment2, "inner")
+                .addToBackStack(null)
+                .commit()
+            childFragmentManager.executePendingTransactions()
+            return super.onCreateView(inflater, container, savedInstanceState)
+        }
+    }
+
+    @ContentView(R.layout.fragment_a)
+    class SimpleViewFragment : Fragment() {
+        var onCreateViewCount: Int = 0
+
+        override fun onCreateView(
+            inflater: LayoutInflater,
+            container: ViewGroup?,
+            savedInstanceState: Bundle?
+        ): View? {
+            onCreateViewCount++
+            return super.onCreateView(inflater, container, savedInstanceState)
+        }
+    }
+}
diff --git a/fragment/src/androidTest/java/androidx/fragment/app/FragmentViewTests.java b/fragment/src/androidTest/java/androidx/fragment/app/FragmentViewTests.java
deleted file mode 100644
index 51a53c5..0000000
--- a/fragment/src/androidTest/java/androidx/fragment/app/FragmentViewTests.java
+++ /dev/null
@@ -1,1069 +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.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
-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 android.app.Instrumentation;
-import android.os.Bundle;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-
-import androidx.annotation.ContentView;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.fragment.app.test.FragmentTestActivity;
-import androidx.fragment.test.R;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SmallTest;
-import androidx.test.platform.app.InstrumentationRegistry;
-import androidx.test.rule.ActivityTestRule;
-
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class FragmentViewTests {
-    @Rule
-    public ActivityTestRule<FragmentTestActivity> mActivityRule =
-            new ActivityTestRule<FragmentTestActivity>(FragmentTestActivity.class);
-
-    private Instrumentation mInstrumentation;
-
-    @Before
-    public void setupInstrumentation() {
-        mInstrumentation = InstrumentationRegistry.getInstrumentation();
-    }
-
-    // Test that adding a fragment adds the Views in the proper order. Popping the back stack
-    // should remove the correct Views.
-    @Test
-    public void addFragments() throws Throwable {
-        FragmentTestUtil.setContentView(mActivityRule, R.layout.simple_container);
-        ViewGroup container = (ViewGroup)
-                mActivityRule.getActivity().findViewById(R.id.fragmentContainer);
-        final FragmentManager fm = mActivityRule.getActivity().getSupportFragmentManager();
-
-        // One fragment with a view
-        final StrictViewFragment fragment1 = new StrictViewFragment();
-        fm.beginTransaction().add(R.id.fragmentContainer, fragment1).addToBackStack(null).commit();
-        FragmentTestUtil.executePendingTransactions(mActivityRule);
-        FragmentTestUtil.assertChildren(container, fragment1);
-
-        // Add another on top
-        final StrictViewFragment fragment2 = new StrictViewFragment();
-        fm.beginTransaction().add(R.id.fragmentContainer, fragment2).addToBackStack(null).commit();
-        FragmentTestUtil.executePendingTransactions(mActivityRule);
-        FragmentTestUtil.assertChildren(container, fragment1, fragment2);
-
-        // Now add two in one transaction:
-        final StrictViewFragment fragment3 = new StrictViewFragment();
-        final StrictViewFragment fragment4 = new StrictViewFragment();
-        fm.beginTransaction()
-                .add(R.id.fragmentContainer, fragment3)
-                .add(R.id.fragmentContainer, fragment4)
-                .addToBackStack(null)
-                .commit();
-        FragmentTestUtil.executePendingTransactions(mActivityRule);
-        FragmentTestUtil.assertChildren(container, fragment1, fragment2, fragment3, fragment4);
-
-        fm.popBackStack();
-        FragmentTestUtil.executePendingTransactions(mActivityRule);
-        FragmentTestUtil.assertChildren(container, fragment1, fragment2);
-
-        fm.popBackStack();
-        FragmentTestUtil.executePendingTransactions(mActivityRule);
-        assertEquals(1, container.getChildCount());
-        FragmentTestUtil.assertChildren(container, fragment1);
-
-        fm.popBackStack();
-        FragmentTestUtil.executePendingTransactions(mActivityRule);
-        FragmentTestUtil.assertChildren(container);
-    }
-
-    // Add fragments to multiple containers in the same transaction. Make sure that
-    // they pop correctly, too.
-    @Test
-    public void addTwoContainers() throws Throwable {
-        FragmentTestUtil.setContentView(mActivityRule, R.layout.double_container);
-        ViewGroup container1 = (ViewGroup)
-                mActivityRule.getActivity().findViewById(R.id.fragmentContainer1);
-        ViewGroup container2 = (ViewGroup)
-                mActivityRule.getActivity().findViewById(R.id.fragmentContainer2);
-        final FragmentManager fm = mActivityRule.getActivity().getSupportFragmentManager();
-
-        final StrictViewFragment fragment1 = new StrictViewFragment();
-        fm.beginTransaction().add(R.id.fragmentContainer1, fragment1).addToBackStack(null).commit();
-        FragmentTestUtil.executePendingTransactions(mActivityRule);
-        FragmentTestUtil.assertChildren(container1, fragment1);
-
-        final StrictViewFragment fragment2 = new StrictViewFragment();
-        fm.beginTransaction().add(R.id.fragmentContainer2, fragment2).addToBackStack(null).commit();
-        FragmentTestUtil.executePendingTransactions(mActivityRule);
-        FragmentTestUtil.assertChildren(container2, fragment2);
-
-        final StrictViewFragment fragment3 = new StrictViewFragment();
-        final StrictViewFragment fragment4 = new StrictViewFragment();
-        fm.beginTransaction()
-                .add(R.id.fragmentContainer1, fragment3)
-                .add(R.id.fragmentContainer2, fragment4)
-                .addToBackStack(null)
-                .commit();
-        FragmentTestUtil.executePendingTransactions(mActivityRule);
-
-        FragmentTestUtil.assertChildren(container1, fragment1, fragment3);
-        FragmentTestUtil.assertChildren(container2, fragment2, fragment4);
-
-        fm.popBackStack();
-        FragmentTestUtil.executePendingTransactions(mActivityRule);
-        FragmentTestUtil.assertChildren(container1, fragment1);
-        FragmentTestUtil.assertChildren(container2, fragment2);
-
-        fm.popBackStack();
-        FragmentTestUtil.executePendingTransactions(mActivityRule);
-        FragmentTestUtil.assertChildren(container1, fragment1);
-        FragmentTestUtil.assertChildren(container2);
-
-        fm.popBackStack();
-        FragmentTestUtil.executePendingTransactions(mActivityRule);
-        assertEquals(0, container1.getChildCount());
-    }
-
-    // When you add a fragment that's has already been added, it should throw.
-    @Test
-    public void doubleAdd() throws Throwable {
-        FragmentTestUtil.setContentView(mActivityRule, R.layout.simple_container);
-        final FragmentManager fm = mActivityRule.getActivity().getSupportFragmentManager();
-        final StrictViewFragment fragment1 = new StrictViewFragment();
-        fm.beginTransaction().add(R.id.fragmentContainer, fragment1).commit();
-        FragmentTestUtil.executePendingTransactions(mActivityRule);
-
-        mInstrumentation.runOnMainSync(new Runnable() {
-            @Override
-            public void run() {
-                try {
-                    fm.beginTransaction()
-                            .add(R.id.fragmentContainer, fragment1)
-                            .addToBackStack(null)
-                            .commit();
-                    fm.executePendingTransactions();
-                    fail("Adding a fragment that is already added should be an error");
-                } catch (IllegalStateException e) {
-                    // expected
-                }
-            }
-        });
-    }
-
-    // Make sure that removed fragments remove the right Views. Popping the back stack should
-    // add the Views back properly
-    @Test
-    public void removeFragments() throws Throwable {
-        FragmentTestUtil.setContentView(mActivityRule, R.layout.simple_container);
-        ViewGroup container = (ViewGroup)
-                mActivityRule.getActivity().findViewById(R.id.fragmentContainer);
-        final FragmentManager fm = mActivityRule.getActivity().getSupportFragmentManager();
-        final StrictViewFragment fragment1 = new StrictViewFragment();
-        final StrictViewFragment fragment2 = new StrictViewFragment();
-        final StrictViewFragment fragment3 = new StrictViewFragment();
-        final StrictViewFragment fragment4 = new StrictViewFragment();
-        fm.beginTransaction()
-                .add(R.id.fragmentContainer, fragment1, "1")
-                .add(R.id.fragmentContainer, fragment2, "2")
-                .add(R.id.fragmentContainer, fragment3, "3")
-                .add(R.id.fragmentContainer, fragment4, "4")
-                .commit();
-        FragmentTestUtil.executePendingTransactions(mActivityRule);
-        FragmentTestUtil.assertChildren(container, fragment1, fragment2, fragment3, fragment4);
-
-        // Remove a view
-        fm.beginTransaction().remove(fragment4).addToBackStack(null).commit();
-        FragmentTestUtil.executePendingTransactions(mActivityRule);
-
-        assertEquals(3, container.getChildCount());
-        FragmentTestUtil.assertChildren(container, fragment1, fragment2, fragment3);
-
-        // remove another one
-        fm.beginTransaction().remove(fragment2).addToBackStack(null).commit();
-        FragmentTestUtil.executePendingTransactions(mActivityRule);
-        FragmentTestUtil.assertChildren(container, fragment1, fragment3);
-
-        // Now remove the remaining:
-        fm.beginTransaction()
-                .remove(fragment3)
-                .remove(fragment1)
-                .addToBackStack(null)
-                .commit();
-        FragmentTestUtil.executePendingTransactions(mActivityRule);
-        FragmentTestUtil.assertChildren(container);
-
-        fm.popBackStack();
-        FragmentTestUtil.executePendingTransactions(mActivityRule);
-        final Fragment replacement1 = fm.findFragmentByTag("1");
-        final Fragment replacement3 = fm.findFragmentByTag("3");
-        FragmentTestUtil.assertChildren(container, replacement1, replacement3);
-
-        fm.popBackStack();
-        FragmentTestUtil.executePendingTransactions(mActivityRule);
-        final Fragment replacement2 = fm.findFragmentByTag("2");
-        FragmentTestUtil.assertChildren(container, replacement1, replacement3, replacement2);
-
-        fm.popBackStack();
-        FragmentTestUtil.executePendingTransactions(mActivityRule);
-        final Fragment replacement4 = fm.findFragmentByTag("4");
-        FragmentTestUtil.assertChildren(container, replacement1, replacement3, replacement2,
-                replacement4);
-    }
-
-    // Removing a hidden fragment should remove the View and popping should bring it back hidden
-    @Test
-    public void removeHiddenView() throws Throwable {
-        FragmentTestUtil.setContentView(mActivityRule, R.layout.simple_container);
-        ViewGroup container = (ViewGroup)
-                mActivityRule.getActivity().findViewById(R.id.fragmentContainer);
-        final FragmentManager fm = mActivityRule.getActivity().getSupportFragmentManager();
-        final StrictViewFragment fragment1 = new StrictViewFragment();
-        fm.beginTransaction().add(R.id.fragmentContainer, fragment1, "1").hide(fragment1).commit();
-        FragmentTestUtil.executePendingTransactions(mActivityRule);
-        FragmentTestUtil.assertChildren(container, fragment1);
-        assertTrue(fragment1.isHidden());
-
-        fm.beginTransaction().remove(fragment1).addToBackStack(null).commit();
-        FragmentTestUtil.executePendingTransactions(mActivityRule);
-        FragmentTestUtil.assertChildren(container);
-
-        fm.popBackStack();
-        FragmentTestUtil.executePendingTransactions(mActivityRule);
-        final Fragment replacement1 = fm.findFragmentByTag("1");
-        FragmentTestUtil.assertChildren(container, replacement1);
-        assertTrue(replacement1.isHidden());
-        assertEquals(View.GONE, replacement1.requireView().getVisibility());
-    }
-
-    // Removing a detached fragment should do nothing to the View and popping should bring
-    // the Fragment back detached
-    @Test
-    public void removeDetatchedView() throws Throwable {
-        FragmentTestUtil.setContentView(mActivityRule, R.layout.simple_container);
-        ViewGroup container = (ViewGroup)
-                mActivityRule.getActivity().findViewById(R.id.fragmentContainer);
-        final FragmentManager fm = mActivityRule.getActivity().getSupportFragmentManager();
-        final StrictViewFragment fragment1 = new StrictViewFragment();
-        fm.beginTransaction()
-                .add(R.id.fragmentContainer, fragment1, "1")
-                .detach(fragment1)
-                .commit();
-        FragmentTestUtil.executePendingTransactions(mActivityRule);
-        FragmentTestUtil.assertChildren(container);
-        assertTrue(fragment1.isDetached());
-
-        fm.beginTransaction().remove(fragment1).addToBackStack(null).commit();
-        FragmentTestUtil.executePendingTransactions(mActivityRule);
-        FragmentTestUtil.assertChildren(container);
-
-        fm.popBackStack();
-        FragmentTestUtil.executePendingTransactions(mActivityRule);
-        final Fragment replacement1 = fm.findFragmentByTag("1");
-        FragmentTestUtil.assertChildren(container);
-        assertTrue(replacement1.isDetached());
-    }
-
-    // Unlike adding the same fragment twice, you should be able to add and then remove and then
-    // add the same fragment in one transaction.
-    @Test
-    public void addRemoveAdd() throws Throwable {
-        FragmentTestUtil.setContentView(mActivityRule, R.layout.simple_container);
-        ViewGroup container = (ViewGroup)
-                mActivityRule.getActivity().findViewById(R.id.fragmentContainer);
-        final FragmentManager fm = mActivityRule.getActivity().getSupportFragmentManager();
-        final StrictViewFragment fragment = new StrictViewFragment();
-        fm.beginTransaction()
-                .add(R.id.fragmentContainer, fragment)
-                .remove(fragment)
-                .add(R.id.fragmentContainer, fragment)
-                .addToBackStack(null)
-                .commit();
-        FragmentTestUtil.executePendingTransactions(mActivityRule);
-        FragmentTestUtil.assertChildren(container, fragment);
-
-        fm.popBackStack();
-        FragmentTestUtil.executePendingTransactions(mActivityRule);
-        FragmentTestUtil.assertChildren(container);
-    }
-
-    // Removing a fragment that isn't in should not throw
-    @Test
-    public void removeNothThere() throws Throwable {
-        FragmentTestUtil.setContentView(mActivityRule, R.layout.simple_container);
-        final FragmentManager fm = mActivityRule.getActivity().getSupportFragmentManager();
-        final StrictViewFragment fragment = new StrictViewFragment();
-        fm.beginTransaction().remove(fragment).commit();
-        FragmentTestUtil.executePendingTransactions(mActivityRule);
-    }
-
-    // Hide a fragment and its View should be GONE. Then pop it and the View should be VISIBLE
-    @Test
-    public void hideFragment() throws Throwable {
-        FragmentTestUtil.setContentView(mActivityRule, R.layout.simple_container);
-        ViewGroup container = (ViewGroup)
-                mActivityRule.getActivity().findViewById(R.id.fragmentContainer);
-        final FragmentManager fm = mActivityRule.getActivity().getSupportFragmentManager();
-        final StrictViewFragment fragment = new StrictViewFragment();
-        fm.beginTransaction().add(R.id.fragmentContainer, fragment).commit();
-        FragmentTestUtil.executePendingTransactions(mActivityRule);
-
-        FragmentTestUtil.assertChildren(container, fragment);
-        assertEquals(View.VISIBLE, fragment.requireView().getVisibility());
-
-        fm.beginTransaction().hide(fragment).addToBackStack(null).commit();
-        FragmentTestUtil.executePendingTransactions(mActivityRule);
-
-        FragmentTestUtil.assertChildren(container, fragment);
-        assertTrue(fragment.isHidden());
-        assertEquals(View.GONE, fragment.requireView().getVisibility());
-
-        fm.popBackStack();
-        FragmentTestUtil.executePendingTransactions(mActivityRule);
-
-        FragmentTestUtil.assertChildren(container, fragment);
-        assertFalse(fragment.isHidden());
-        assertEquals(View.VISIBLE, fragment.requireView().getVisibility());
-    }
-
-    // Hiding a hidden fragment should not throw
-    @Test
-    public void doubleHide() throws Throwable {
-        FragmentTestUtil.setContentView(mActivityRule, R.layout.simple_container);
-        final FragmentManager fm = mActivityRule.getActivity().getSupportFragmentManager();
-        final StrictViewFragment fragment = new StrictViewFragment();
-        fm.beginTransaction()
-                .add(R.id.fragmentContainer, fragment)
-                .hide(fragment)
-                .hide(fragment)
-                .commit();
-        FragmentTestUtil.executePendingTransactions(mActivityRule);
-    }
-
-    // Hiding a non-existing fragment should not throw
-    @Test
-    public void hideUnAdded() throws Throwable {
-        FragmentTestUtil.setContentView(mActivityRule, R.layout.simple_container);
-        final FragmentManager fm = mActivityRule.getActivity().getSupportFragmentManager();
-        final StrictViewFragment fragment = new StrictViewFragment();
-        fm.beginTransaction()
-                .hide(fragment)
-                .commit();
-        FragmentTestUtil.executePendingTransactions(mActivityRule);
-    }
-
-    // Show a hidden fragment and its View should be VISIBLE. Then pop it and the View should be
-    // GONE.
-    @Test
-    public void showFragment() throws Throwable {
-        FragmentTestUtil.setContentView(mActivityRule, R.layout.simple_container);
-        ViewGroup container = (ViewGroup)
-                mActivityRule.getActivity().findViewById(R.id.fragmentContainer);
-        final FragmentManager fm = mActivityRule.getActivity().getSupportFragmentManager();
-        final StrictViewFragment fragment = new StrictViewFragment();
-        fm.beginTransaction().add(R.id.fragmentContainer, fragment).hide(fragment).commit();
-        FragmentTestUtil.executePendingTransactions(mActivityRule);
-
-        FragmentTestUtil.assertChildren(container, fragment);
-        assertTrue(fragment.isHidden());
-        assertEquals(View.GONE, fragment.requireView().getVisibility());
-
-        fm.beginTransaction().show(fragment).addToBackStack(null).commit();
-        FragmentTestUtil.executePendingTransactions(mActivityRule);
-
-        FragmentTestUtil.assertChildren(container, fragment);
-        assertFalse(fragment.isHidden());
-        assertEquals(View.VISIBLE, fragment.requireView().getVisibility());
-
-        fm.popBackStack();
-        FragmentTestUtil.executePendingTransactions(mActivityRule);
-
-        FragmentTestUtil.assertChildren(container, fragment);
-        assertTrue(fragment.isHidden());
-        assertEquals(View.GONE, fragment.requireView().getVisibility());
-    }
-
-    // Showing a shown fragment should not throw
-    @Test
-    public void showShown() throws Throwable {
-        FragmentTestUtil.setContentView(mActivityRule, R.layout.simple_container);
-        final FragmentManager fm = mActivityRule.getActivity().getSupportFragmentManager();
-        final StrictViewFragment fragment = new StrictViewFragment();
-        fm.beginTransaction()
-                .add(R.id.fragmentContainer, fragment)
-                .show(fragment)
-                .commit();
-        FragmentTestUtil.executePendingTransactions(mActivityRule);
-    }
-
-    // Showing a non-existing fragment should not throw
-    @Test
-    public void showUnAdded() throws Throwable {
-        FragmentTestUtil.setContentView(mActivityRule, R.layout.simple_container);
-        final FragmentManager fm = mActivityRule.getActivity().getSupportFragmentManager();
-        final StrictViewFragment fragment = new StrictViewFragment();
-        fm.beginTransaction()
-                .show(fragment)
-                .commit();
-        FragmentTestUtil.executePendingTransactions(mActivityRule);
-    }
-
-    // Detaching a fragment should remove the View from the hierarchy. Then popping it should
-    // bring it back VISIBLE
-    @Test
-    public void detachFragment() throws Throwable {
-        FragmentTestUtil.setContentView(mActivityRule, R.layout.simple_container);
-        ViewGroup container = (ViewGroup)
-                mActivityRule.getActivity().findViewById(R.id.fragmentContainer);
-        final FragmentManager fm = mActivityRule.getActivity().getSupportFragmentManager();
-        final StrictViewFragment fragment = new StrictViewFragment();
-        fm.beginTransaction().add(R.id.fragmentContainer, fragment).commit();
-        FragmentTestUtil.executePendingTransactions(mActivityRule);
-
-        FragmentTestUtil.assertChildren(container, fragment);
-        assertFalse(fragment.isDetached());
-        assertEquals(View.VISIBLE, fragment.requireView().getVisibility());
-
-        fm.beginTransaction().detach(fragment).addToBackStack(null).commit();
-        FragmentTestUtil.executePendingTransactions(mActivityRule);
-
-        FragmentTestUtil.assertChildren(container);
-        assertTrue(fragment.isDetached());
-
-        fm.popBackStack();
-        FragmentTestUtil.executePendingTransactions(mActivityRule);
-
-        FragmentTestUtil.assertChildren(container, fragment);
-        assertFalse(fragment.isDetached());
-        assertEquals(View.VISIBLE, fragment.requireView().getVisibility());
-    }
-
-    // Detaching a hidden fragment should remove the View from the hierarchy. Then popping it should
-    // bring it back hidden
-    @Test
-    public void detachHiddenFragment() throws Throwable {
-        FragmentTestUtil.setContentView(mActivityRule, R.layout.simple_container);
-        ViewGroup container = (ViewGroup)
-                mActivityRule.getActivity().findViewById(R.id.fragmentContainer);
-        final FragmentManager fm = mActivityRule.getActivity().getSupportFragmentManager();
-        final StrictViewFragment fragment = new StrictViewFragment();
-        fm.beginTransaction().add(R.id.fragmentContainer, fragment).hide(fragment).commit();
-        FragmentTestUtil.executePendingTransactions(mActivityRule);
-
-        FragmentTestUtil.assertChildren(container, fragment);
-        assertFalse(fragment.isDetached());
-        assertTrue(fragment.isHidden());
-        assertEquals(View.GONE, fragment.requireView().getVisibility());
-
-        fm.beginTransaction().detach(fragment).addToBackStack(null).commit();
-        FragmentTestUtil.executePendingTransactions(mActivityRule);
-
-        FragmentTestUtil.assertChildren(container);
-        assertTrue(fragment.isHidden());
-        assertTrue(fragment.isDetached());
-
-        fm.popBackStack();
-        FragmentTestUtil.executePendingTransactions(mActivityRule);
-
-        FragmentTestUtil.assertChildren(container, fragment);
-        assertTrue(fragment.isHidden());
-        assertFalse(fragment.isDetached());
-        assertEquals(View.GONE, fragment.requireView().getVisibility());
-    }
-
-    // Detaching a detached fragment should not throw
-    @Test
-    public void detachDetatched() throws Throwable {
-        FragmentTestUtil.setContentView(mActivityRule, R.layout.simple_container);
-        final FragmentManager fm = mActivityRule.getActivity().getSupportFragmentManager();
-        final StrictViewFragment fragment = new StrictViewFragment();
-        fm.beginTransaction()
-                .add(R.id.fragmentContainer, fragment)
-                .detach(fragment)
-                .detach(fragment)
-                .commit();
-        FragmentTestUtil.executePendingTransactions(mActivityRule);
-    }
-
-    // Detaching a non-existing fragment should not throw
-    @Test
-    public void detachUnAdded() throws Throwable {
-        FragmentTestUtil.setContentView(mActivityRule, R.layout.simple_container);
-        final FragmentManager fm = mActivityRule.getActivity().getSupportFragmentManager();
-        final StrictViewFragment fragment = new StrictViewFragment();
-        fm.beginTransaction()
-                .detach(fragment)
-                .commit();
-        FragmentTestUtil.executePendingTransactions(mActivityRule);
-    }
-
-    // Attaching a fragment should add the View back into the hierarchy. Then popping it should
-    // remove it again
-    @Test
-    public void attachFragment() throws Throwable {
-        FragmentTestUtil.setContentView(mActivityRule, R.layout.simple_container);
-        ViewGroup container = (ViewGroup)
-                mActivityRule.getActivity().findViewById(R.id.fragmentContainer);
-        final FragmentManager fm = mActivityRule.getActivity().getSupportFragmentManager();
-        final StrictViewFragment fragment = new StrictViewFragment();
-        fm.beginTransaction().add(R.id.fragmentContainer, fragment).detach(fragment).commit();
-        FragmentTestUtil.executePendingTransactions(mActivityRule);
-
-        FragmentTestUtil.assertChildren(container);
-        assertTrue(fragment.isDetached());
-
-        fm.beginTransaction().attach(fragment).addToBackStack(null).commit();
-        FragmentTestUtil.executePendingTransactions(mActivityRule);
-
-        FragmentTestUtil.assertChildren(container, fragment);
-        assertFalse(fragment.isDetached());
-        assertEquals(View.VISIBLE, fragment.requireView().getVisibility());
-
-        fm.popBackStack();
-        FragmentTestUtil.executePendingTransactions(mActivityRule);
-
-        FragmentTestUtil.assertChildren(container);
-        assertTrue(fragment.isDetached());
-    }
-
-    // Attaching a hidden fragment should add the View as GONE the hierarchy. Then popping it should
-    // remove it again.
-    @Test
-    public void attachHiddenFragment() throws Throwable {
-        FragmentTestUtil.setContentView(mActivityRule, R.layout.simple_container);
-        ViewGroup container = (ViewGroup)
-                mActivityRule.getActivity().findViewById(R.id.fragmentContainer);
-        final FragmentManager fm = mActivityRule.getActivity().getSupportFragmentManager();
-        final StrictViewFragment fragment = new StrictViewFragment();
-        fm.beginTransaction()
-                .add(R.id.fragmentContainer, fragment)
-                .hide(fragment)
-                .detach(fragment)
-                .commit();
-        FragmentTestUtil.executePendingTransactions(mActivityRule);
-
-        FragmentTestUtil.assertChildren(container);
-        assertTrue(fragment.isDetached());
-        assertTrue(fragment.isHidden());
-
-        fm.beginTransaction().attach(fragment).addToBackStack(null).commit();
-        FragmentTestUtil.executePendingTransactions(mActivityRule);
-
-        FragmentTestUtil.assertChildren(container, fragment);
-        assertTrue(fragment.isHidden());
-        assertFalse(fragment.isDetached());
-        assertEquals(View.GONE, fragment.requireView().getVisibility());
-
-        fm.popBackStack();
-        FragmentTestUtil.executePendingTransactions(mActivityRule);
-
-        FragmentTestUtil.assertChildren(container);
-        assertTrue(fragment.isDetached());
-        assertTrue(fragment.isHidden());
-    }
-
-    // Attaching an attached fragment should not throw
-    @Test
-    public void attachAttached() throws Throwable {
-        FragmentTestUtil.setContentView(mActivityRule, R.layout.simple_container);
-        final FragmentManager fm = mActivityRule.getActivity().getSupportFragmentManager();
-        final StrictViewFragment fragment = new StrictViewFragment();
-        fm.beginTransaction()
-                .add(R.id.fragmentContainer, fragment)
-                .attach(fragment)
-                .commit();
-        FragmentTestUtil.executePendingTransactions(mActivityRule);
-    }
-
-    // Attaching a non-existing fragment should not throw
-    @Test
-    public void attachUnAdded() throws Throwable {
-        FragmentTestUtil.setContentView(mActivityRule, R.layout.simple_container);
-        final FragmentManager fm = mActivityRule.getActivity().getSupportFragmentManager();
-        final StrictViewFragment fragment = new StrictViewFragment();
-        fm.beginTransaction()
-                .attach(fragment)
-                .commit();
-        FragmentTestUtil.executePendingTransactions(mActivityRule);
-    }
-
-    // Simple replace of one fragment in a container. Popping should replace it back again
-    @Test
-    public void replaceOne() throws Throwable {
-        FragmentTestUtil.setContentView(mActivityRule, R.layout.simple_container);
-        ViewGroup container = (ViewGroup)
-                mActivityRule.getActivity().findViewById(R.id.fragmentContainer);
-        final FragmentManager fm = mActivityRule.getActivity().getSupportFragmentManager();
-        final StrictViewFragment fragment1 = new StrictViewFragment();
-        fm.beginTransaction().add(R.id.fragmentContainer, fragment1, "1").commit();
-        FragmentTestUtil.executePendingTransactions(mActivityRule);
-
-        FragmentTestUtil.assertChildren(container, fragment1);
-
-        final StrictViewFragment fragment2 = new StrictViewFragment();
-        fm.beginTransaction()
-                .replace(R.id.fragmentContainer, fragment2)
-                .addToBackStack(null)
-                .commit();
-        FragmentTestUtil.executePendingTransactions(mActivityRule);
-
-        FragmentTestUtil.assertChildren(container, fragment2);
-        assertEquals(View.VISIBLE, fragment2.requireView().getVisibility());
-
-        fm.popBackStack();
-        FragmentTestUtil.executePendingTransactions(mActivityRule);
-
-        Fragment replacement1 = fm.findFragmentByTag("1");
-        assertNotNull(replacement1);
-        FragmentTestUtil.assertChildren(container, replacement1);
-        assertFalse(replacement1.isHidden());
-        assertTrue(replacement1.isAdded());
-        assertFalse(replacement1.isDetached());
-        assertEquals(View.VISIBLE, replacement1.requireView().getVisibility());
-    }
-
-    // Replace of multiple fragments in a container. Popping should replace it back again
-    @Test
-    public void replaceTwo() throws Throwable {
-        FragmentTestUtil.setContentView(mActivityRule, R.layout.simple_container);
-        ViewGroup container = (ViewGroup)
-                mActivityRule.getActivity().findViewById(R.id.fragmentContainer);
-        final FragmentManager fm = mActivityRule.getActivity().getSupportFragmentManager();
-        final StrictViewFragment fragment1 = new StrictViewFragment();
-        final StrictViewFragment fragment2 = new StrictViewFragment();
-        fm.beginTransaction()
-                .add(R.id.fragmentContainer, fragment1, "1")
-                .add(R.id.fragmentContainer, fragment2, "2")
-                .hide(fragment2)
-                .commit();
-        FragmentTestUtil.executePendingTransactions(mActivityRule);
-
-        FragmentTestUtil.assertChildren(container, fragment1, fragment2);
-
-        final StrictViewFragment fragment3 = new StrictViewFragment();
-        fm.beginTransaction()
-                .replace(R.id.fragmentContainer, fragment3)
-                .addToBackStack(null)
-                .commit();
-        FragmentTestUtil.executePendingTransactions(mActivityRule);
-
-        FragmentTestUtil.assertChildren(container, fragment3);
-        assertEquals(View.VISIBLE, fragment3.requireView().getVisibility());
-
-        fm.popBackStack();
-        FragmentTestUtil.executePendingTransactions(mActivityRule);
-
-        Fragment replacement1 = fm.findFragmentByTag("1");
-        Fragment replacement2 = fm.findFragmentByTag("2");
-        assertNotNull(replacement1);
-        assertNotNull(replacement2);
-        FragmentTestUtil.assertChildren(container, replacement1, replacement2);
-        assertFalse(replacement1.isHidden());
-        assertTrue(replacement1.isAdded());
-        assertFalse(replacement1.isDetached());
-        assertEquals(View.VISIBLE, replacement1.requireView().getVisibility());
-
-        // fragment2 was hidden, so it should be returned hidden
-        assertTrue(replacement2.isHidden());
-        assertTrue(replacement2.isAdded());
-        assertFalse(replacement2.isDetached());
-        assertEquals(View.GONE, replacement2.requireView().getVisibility());
-    }
-
-    // Replace of empty container. Should act as add and popping should just remove the fragment
-    @Test
-    public void replaceZero() throws Throwable {
-        FragmentTestUtil.setContentView(mActivityRule, R.layout.simple_container);
-        ViewGroup container = (ViewGroup)
-                mActivityRule.getActivity().findViewById(R.id.fragmentContainer);
-        final FragmentManager fm = mActivityRule.getActivity().getSupportFragmentManager();
-
-        final StrictViewFragment fragment = new StrictViewFragment();
-        fm.beginTransaction()
-                .replace(R.id.fragmentContainer, fragment)
-                .addToBackStack(null)
-                .commit();
-        FragmentTestUtil.executePendingTransactions(mActivityRule);
-
-        FragmentTestUtil.assertChildren(container, fragment);
-        assertEquals(View.VISIBLE, fragment.requireView().getVisibility());
-
-        fm.popBackStack();
-        FragmentTestUtil.executePendingTransactions(mActivityRule);
-
-        FragmentTestUtil.assertChildren(container);
-    }
-
-    // Replace a fragment that exists with itself
-    @Test
-    public void replaceExisting() throws Throwable {
-        FragmentTestUtil.setContentView(mActivityRule, R.layout.simple_container);
-        ViewGroup container = (ViewGroup)
-                mActivityRule.getActivity().findViewById(R.id.fragmentContainer);
-        final FragmentManager fm = mActivityRule.getActivity().getSupportFragmentManager();
-        final StrictViewFragment fragment1 = new StrictViewFragment();
-        final StrictViewFragment fragment2 = new StrictViewFragment();
-        fm.beginTransaction()
-                .add(R.id.fragmentContainer, fragment1, "1")
-                .add(R.id.fragmentContainer, fragment2, "2")
-                .commit();
-        FragmentTestUtil.executePendingTransactions(mActivityRule);
-
-        FragmentTestUtil.assertChildren(container, fragment1, fragment2);
-
-        fm.beginTransaction()
-                .replace(R.id.fragmentContainer, fragment1)
-                .addToBackStack(null)
-                .commit();
-        FragmentTestUtil.executePendingTransactions(mActivityRule);
-
-        FragmentTestUtil.assertChildren(container, fragment1);
-
-        fm.popBackStack();
-        FragmentTestUtil.executePendingTransactions(mActivityRule);
-
-        final Fragment replacement1 = fm.findFragmentByTag("1");
-        final Fragment replacement2 = fm.findFragmentByTag("2");
-
-        assertSame(fragment1, replacement1);
-        FragmentTestUtil.assertChildren(container, replacement1, replacement2);
-    }
-
-    // Have two replace operations in the same transaction to ensure that they
-    // don't interfere with each other
-    @Test
-    public void replaceReplace() throws Throwable {
-        FragmentTestUtil.setContentView(mActivityRule, R.layout.double_container);
-        ViewGroup container1 = (ViewGroup)
-                mActivityRule.getActivity().findViewById(R.id.fragmentContainer1);
-        ViewGroup container2 = (ViewGroup)
-                mActivityRule.getActivity().findViewById(R.id.fragmentContainer2);
-        final FragmentManager fm = mActivityRule.getActivity().getSupportFragmentManager();
-
-        final StrictViewFragment fragment1 = new StrictViewFragment();
-        final StrictViewFragment fragment2 = new StrictViewFragment();
-        final StrictViewFragment fragment3 = new StrictViewFragment();
-        final StrictViewFragment fragment4 = new StrictViewFragment();
-        final StrictViewFragment fragment5 = new StrictViewFragment();
-        fm.beginTransaction()
-                .add(R.id.fragmentContainer1, fragment1)
-                .add(R.id.fragmentContainer2, fragment2)
-                .replace(R.id.fragmentContainer1, fragment3)
-                .replace(R.id.fragmentContainer2, fragment4)
-                .replace(R.id.fragmentContainer1, fragment5)
-                .addToBackStack(null)
-                .commit();
-        FragmentTestUtil.executePendingTransactions(mActivityRule);
-
-        assertChildren(container1, fragment5);
-        assertChildren(container2, fragment4);
-
-        fm.popBackStack();
-        FragmentTestUtil.executePendingTransactions(mActivityRule);
-
-        assertChildren(container1);
-        assertChildren(container2);
-    }
-
-    // Test to prevent regressions in FragmentManager fragment replace method. See b/24693644
-    @Test
-    public void testReplaceFragment() throws Throwable {
-        FragmentTestUtil.setContentView(mActivityRule, R.layout.simple_container);
-        final FragmentManager fm = mActivityRule.getActivity().getSupportFragmentManager();
-        StrictViewFragment fragmentA = new StrictViewFragment();
-        fragmentA.setLayoutId(R.layout.text_a);
-
-        fm.beginTransaction()
-                .add(R.id.fragmentContainer, fragmentA)
-                .addToBackStack(null)
-                .commit();
-        FragmentTestUtil.executePendingTransactions(mActivityRule);
-
-        assertNotNull(findViewById(R.id.textA));
-        assertNull(findViewById(R.id.textB));
-        assertNull(findViewById(R.id.textC));
-
-        StrictViewFragment fragmentB = new StrictViewFragment();
-        fragmentB.setLayoutId(R.layout.text_b);
-        fm.beginTransaction()
-                .add(R.id.fragmentContainer, fragmentB)
-                .addToBackStack(null)
-                .commit();
-        FragmentTestUtil.executePendingTransactions(mActivityRule);
-        assertNotNull(findViewById(R.id.textA));
-        assertNotNull(findViewById(R.id.textB));
-        assertNull(findViewById(R.id.textC));
-
-        StrictViewFragment fragmentC = new StrictViewFragment();
-        fragmentC.setLayoutId(R.layout.text_c);
-        fm.beginTransaction()
-                .replace(R.id.fragmentContainer, fragmentC)
-                .addToBackStack(null)
-                .commit();
-        FragmentTestUtil.executePendingTransactions(mActivityRule);
-        assertNull(findViewById(R.id.textA));
-        assertNull(findViewById(R.id.textB));
-        assertNotNull(findViewById(R.id.textC));
-    }
-
-    // Test that adding a fragment with invisible or gone views does not end up with the view
-    // being visible
-    @Test
-    public void addInvisibleAndGoneFragments() throws Throwable {
-        FragmentTestUtil.setContentView(mActivityRule, R.layout.simple_container);
-        ViewGroup container = (ViewGroup)
-                mActivityRule.getActivity().findViewById(R.id.fragmentContainer);
-        final FragmentManager fm = mActivityRule.getActivity().getSupportFragmentManager();
-
-        final StrictViewFragment fragment1 = new InvisibleFragment();
-        fm.beginTransaction().add(R.id.fragmentContainer, fragment1).addToBackStack(null).commit();
-        FragmentTestUtil.executePendingTransactions(mActivityRule);
-        FragmentTestUtil.assertChildren(container, fragment1);
-
-        assertEquals(View.INVISIBLE, fragment1.requireView().getVisibility());
-
-        final InvisibleFragment fragment2 = new InvisibleFragment();
-        fragment2.visibility = View.GONE;
-        fm.beginTransaction()
-                .replace(R.id.fragmentContainer, fragment2)
-                .addToBackStack(null)
-                .commit();
-        FragmentTestUtil.executePendingTransactions(mActivityRule);
-        FragmentTestUtil.assertChildren(container, fragment2);
-
-        assertEquals(View.GONE, fragment2.requireView().getVisibility());
-    }
-
-    // Test to ensure that popping and adding a fragment properly track the fragments added
-    // and removed.
-    @Test
-    public void popAdd() throws Throwable {
-        FragmentTestUtil.setContentView(mActivityRule, R.layout.simple_container);
-        ViewGroup container = (ViewGroup)
-                mActivityRule.getActivity().findViewById(R.id.fragmentContainer);
-        final FragmentManager fm = mActivityRule.getActivity().getSupportFragmentManager();
-
-        // One fragment with a view
-        final StrictViewFragment fragment1 = new StrictViewFragment();
-        fm.beginTransaction().add(R.id.fragmentContainer, fragment1).addToBackStack(null).commit();
-        FragmentTestUtil.executePendingTransactions(mActivityRule);
-        FragmentTestUtil.assertChildren(container, fragment1);
-
-        final StrictViewFragment fragment2 = new StrictViewFragment();
-        final StrictViewFragment fragment3 = new StrictViewFragment();
-        mInstrumentation.runOnMainSync(new Runnable() {
-            @Override
-            public void run() {
-                fm.popBackStack();
-                fm.beginTransaction()
-                        .replace(R.id.fragmentContainer, fragment2)
-                        .addToBackStack(null)
-                        .commit();
-                fm.executePendingTransactions();
-                fm.popBackStack();
-                fm.beginTransaction()
-                        .replace(R.id.fragmentContainer, fragment3)
-                        .addToBackStack(null)
-                        .commit();
-                fm.executePendingTransactions();
-            }
-        });
-        FragmentTestUtil.assertChildren(container, fragment3);
-    }
-
-    // Ensure that ordered transactions are executed individually rather than together.
-    // This forces references from one fragment to another that should be executed earlier
-    // to work.
-    @Test
-    public void orderedOperationsTogether() throws Throwable {
-        FragmentTestUtil.setContentView(mActivityRule, R.layout.simple_container);
-        ViewGroup container = (ViewGroup)
-                mActivityRule.getActivity().findViewById(R.id.fragmentContainer);
-        final FragmentManager fm = mActivityRule.getActivity().getSupportFragmentManager();
-
-        final StrictViewFragment fragment1 = new StrictViewFragment();
-        fragment1.setLayoutId(R.layout.scene1);
-        final StrictViewFragment fragment2 = new StrictViewFragment();
-        fragment2.setLayoutId(R.layout.fragment_a);
-
-        mActivityRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                fm.beginTransaction()
-                        .add(R.id.fragmentContainer, fragment1)
-                        .setReorderingAllowed(false)
-                        .addToBackStack(null)
-                        .commit();
-                fm.beginTransaction()
-                        .add(R.id.squareContainer, fragment2)
-                        .setReorderingAllowed(false)
-                        .addToBackStack(null)
-                        .commit();
-                fm.executePendingTransactions();
-            }
-        });
-        FragmentTestUtil.assertChildren(container, fragment1);
-        assertNotNull(findViewById(R.id.textA));
-    }
-
-    // Ensure that there is no problem if the child fragment manager is used before
-    // the View has been added.
-    @Test
-    public void childFragmentManager() throws Throwable {
-        FragmentTestUtil.setContentView(mActivityRule, R.layout.simple_container);
-        ViewGroup container = (ViewGroup)
-                mActivityRule.getActivity().findViewById(R.id.fragmentContainer);
-        final FragmentManager fm = mActivityRule.getActivity().getSupportFragmentManager();
-
-        final StrictViewFragment fragment1 = new ParentFragment();
-        fragment1.setLayoutId(R.layout.double_container);
-
-        fm.beginTransaction()
-                .add(R.id.fragmentContainer, fragment1)
-                .addToBackStack(null)
-                .commit();
-
-        FragmentTestUtil.executePendingTransactions(mActivityRule);
-
-        FragmentTestUtil.assertChildren(container, fragment1);
-        ViewGroup innerContainer = fragment1.requireView().findViewById(R.id.fragmentContainer1);
-
-        Fragment fragment2 = fragment1.getChildFragmentManager().findFragmentByTag("inner");
-        FragmentTestUtil.assertChildren(innerContainer, fragment2);
-    }
-
-    // Popping the backstack with ordered fragments should execute the operations together.
-    // When a non-backstack fragment will be raised, it should not be destroyed.
-    @Test
-    public void popToNonBackStackFragment() throws Throwable {
-        FragmentTestUtil.setContentView(mActivityRule, R.layout.simple_container);
-        final FragmentManager fm = mActivityRule.getActivity().getSupportFragmentManager();
-
-        final SimpleViewFragment fragment1 = new SimpleViewFragment();
-
-        fm.beginTransaction()
-                .add(R.id.fragmentContainer, fragment1)
-                .commit();
-
-        FragmentTestUtil.executePendingTransactions(mActivityRule);
-
-        final SimpleViewFragment fragment2 = new SimpleViewFragment();
-
-        fm.beginTransaction()
-                .replace(R.id.fragmentContainer, fragment2)
-                .addToBackStack("two")
-                .commit();
-
-        FragmentTestUtil.executePendingTransactions(mActivityRule);
-
-        final SimpleViewFragment fragment3 = new SimpleViewFragment();
-
-        fm.beginTransaction()
-                .replace(R.id.fragmentContainer, fragment3)
-                .addToBackStack("three")
-                .commit();
-
-        FragmentTestUtil.executePendingTransactions(mActivityRule);
-
-        assertEquals(1, fragment1.onCreateViewCount);
-        assertEquals(1, fragment2.onCreateViewCount);
-        assertEquals(1, fragment3.onCreateViewCount);
-
-        FragmentTestUtil.popBackStackImmediate(mActivityRule, "two",
-                FragmentManager.POP_BACK_STACK_INCLUSIVE);
-
-        ViewGroup container = (ViewGroup)
-                mActivityRule.getActivity().findViewById(R.id.fragmentContainer);
-
-        FragmentTestUtil.assertChildren(container, fragment1);
-
-        assertEquals(2, fragment1.onCreateViewCount);
-        assertEquals(1, fragment2.onCreateViewCount);
-        assertEquals(1, fragment3.onCreateViewCount);
-    }
-
-    private View findViewById(int viewId) {
-        return mActivityRule.getActivity().findViewById(viewId);
-    }
-
-    private void assertChildren(ViewGroup container, Fragment... fragments) {
-        final int numFragments = fragments == null ? 0 : fragments.length;
-        assertEquals("There aren't the correct number of fragment Views in its container",
-                numFragments, container.getChildCount());
-        for (int i = 0; i < numFragments; i++) {
-            assertEquals("Wrong Fragment View order for [" + i + "]", container.getChildAt(i),
-                    fragments[i].getView());
-        }
-    }
-
-    public static class InvisibleFragment extends StrictViewFragment {
-        public int visibility = View.INVISIBLE;
-
-        @Override
-        public void onViewCreated(View view, Bundle savedInstanceState) {
-            view.setVisibility(visibility);
-            super.onViewCreated(view, savedInstanceState);
-        }
-    }
-
-    public static class ParentFragment extends StrictViewFragment {
-        public ParentFragment() {
-            setLayoutId(R.layout.double_container);
-        }
-
-        @Override
-        public View onCreateView(LayoutInflater inflater, ViewGroup container,
-                                 Bundle savedInstanceState) {
-            View view = super.onCreateView(inflater, container, savedInstanceState);
-            final StrictViewFragment fragment2 = new StrictViewFragment();
-            fragment2.setLayoutId(R.layout.fragment_a);
-
-            getChildFragmentManager().beginTransaction()
-                    .add(R.id.fragmentContainer1, fragment2, "inner")
-                    .addToBackStack(null)
-                    .commit();
-            getChildFragmentManager().executePendingTransactions();
-            return view;
-        }
-    }
-
-    @ContentView(R.layout.fragment_a)
-    public static class SimpleViewFragment extends Fragment {
-        public int onCreateViewCount;
-
-        @Nullable
-        @Override
-        public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
-                @Nullable Bundle savedInstanceState) {
-            onCreateViewCount++;
-            return super.onCreateView(inflater, container, savedInstanceState);
-        }
-    }
-}
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.java b/fragment/src/androidTest/java/androidx/fragment/app/PostponedTransitionTest.java
deleted file mode 100644
index 114461e..0000000
--- a/fragment/src/androidTest/java/androidx/fragment/app/PostponedTransitionTest.java
+++ /dev/null
@@ -1,987 +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.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-
-import android.app.Instrumentation;
-import android.os.Build;
-import android.os.Bundle;
-import android.os.Parcelable;
-import android.util.Pair;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-
-import androidx.annotation.ContentView;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.fragment.app.test.FragmentTestActivity;
-import androidx.fragment.test.R;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.LargeTest;
-import androidx.test.filters.SdkSuppress;
-import androidx.test.platform.app.InstrumentationRegistry;
-import androidx.test.rule.ActivityTestRule;
-
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.concurrent.CountDownLatch;
-
-@LargeTest
-@RunWith(AndroidJUnit4.class)
-@SdkSuppress(minSdkVersion = Build.VERSION_CODES.LOLLIPOP)
-public class PostponedTransitionTest {
-    @Rule
-    public ActivityTestRule<FragmentTestActivity> mActivityRule =
-            new ActivityTestRule<FragmentTestActivity>(FragmentTestActivity.class);
-
-    private Instrumentation mInstrumentation;
-    private PostponedFragment1 mBeginningFragment;
-
-    @Before
-    public void setupContainer() throws Throwable {
-        mInstrumentation = InstrumentationRegistry.getInstrumentation();
-        FragmentTestUtil.setContentView(mActivityRule, R.layout.simple_container);
-        final FragmentManager fm = mActivityRule.getActivity().getSupportFragmentManager();
-        mBeginningFragment = new PostponedFragment1();
-
-        final CountDownLatch backStackLatch = new CountDownLatch(1);
-        FragmentManager.OnBackStackChangedListener backstackListener =
-                new FragmentManager.OnBackStackChangedListener() {
-
-            @Override
-            public void onBackStackChanged() {
-                backStackLatch.countDown();
-                fm.removeOnBackStackChangedListener(this);
-            }
-        };
-        fm.addOnBackStackChangedListener(backstackListener);
-        fm.beginTransaction()
-                .add(R.id.fragmentContainer, mBeginningFragment)
-                .setReorderingAllowed(true)
-                .addToBackStack(null)
-                .commit();
-
-        backStackLatch.await();
-
-        mBeginningFragment.startPostponedEnterTransition();
-        mBeginningFragment.waitForTransition();
-        clearTargets(mBeginningFragment);
-    }
-
-    // Ensure that replacing with a fragment that has a postponed transition
-    // will properly postpone it, both adding and popping.
-    @Test
-    public void replaceTransition() throws Throwable {
-        final FragmentManager fm = mActivityRule.getActivity().getSupportFragmentManager();
-        final View startBlue = mActivityRule.getActivity().findViewById(R.id.blueSquare);
-
-        final PostponedFragment2 fragment = new PostponedFragment2();
-        fm.beginTransaction()
-                .addSharedElement(startBlue, "blueSquare")
-                .replace(R.id.fragmentContainer, fragment)
-                .addToBackStack(null)
-                .setReorderingAllowed(true)
-                .commit();
-
-        FragmentTestUtil.waitForExecution(mActivityRule);
-
-        // should be postponed now
-        assertPostponedTransition(mBeginningFragment, fragment, null);
-
-        // start the postponed transition
-        fragment.startPostponedEnterTransition();
-
-        // make sure it ran
-        assertForwardTransition(mBeginningFragment, fragment);
-
-        FragmentTestUtil.popBackStackImmediate(mActivityRule);
-
-        // should be postponed going back, too
-        assertPostponedTransition(fragment, mBeginningFragment, null);
-
-        // start the postponed transition
-        mBeginningFragment.startPostponedEnterTransition();
-
-        // make sure it ran
-        assertBackTransition(fragment, mBeginningFragment);
-    }
-
-    // Ensure that replacing a fragment doesn't cause problems with the back stack nesting level
-    @Test
-    public void backStackNestingLevel() throws Throwable {
-        final FragmentManager fm = mActivityRule.getActivity().getSupportFragmentManager();
-        View startBlue = mActivityRule.getActivity().findViewById(R.id.blueSquare);
-
-        final TransitionFragment fragment1 = new TransitionFragment2();
-        fm.beginTransaction()
-                .addSharedElement(startBlue, "blueSquare")
-                .replace(R.id.fragmentContainer, fragment1)
-                .addToBackStack(null)
-                .setReorderingAllowed(true)
-                .commit();
-
-        // make sure transition ran
-        assertForwardTransition(mBeginningFragment, fragment1);
-
-        FragmentTestUtil.popBackStackImmediate(mActivityRule);
-
-        // should be postponed going back
-        assertPostponedTransition(fragment1, mBeginningFragment, null);
-
-        // start the postponed transition
-        mBeginningFragment.startPostponedEnterTransition();
-
-        // make sure it ran
-        assertBackTransition(fragment1, mBeginningFragment);
-
-        startBlue = mActivityRule.getActivity().findViewById(R.id.blueSquare);
-
-        final TransitionFragment fragment2 = new TransitionFragment2();
-        fm.beginTransaction()
-                .addSharedElement(startBlue, "blueSquare")
-                .replace(R.id.fragmentContainer, fragment2)
-                .addToBackStack(null)
-                .setReorderingAllowed(true)
-                .commit();
-
-        // make sure transition ran
-        assertForwardTransition(mBeginningFragment, fragment2);
-
-        FragmentTestUtil.popBackStackImmediate(mActivityRule);
-
-        // should be postponed going back
-        assertPostponedTransition(fragment2, mBeginningFragment, null);
-
-        // start the postponed transition
-        mBeginningFragment.startPostponedEnterTransition();
-
-        // make sure it ran
-        assertBackTransition(fragment2, mBeginningFragment);
-    }
-
-    // Ensure that postponed transition is forced after another has been committed.
-    // This tests when the transactions are executed together
-    @Test
-    public void forcedTransition1() throws Throwable {
-        final FragmentManager fm = mActivityRule.getActivity().getSupportFragmentManager();
-        final View startBlue = mActivityRule.getActivity().findViewById(R.id.blueSquare);
-
-        final PostponedFragment2 fragment2 = new PostponedFragment2();
-        final PostponedFragment1 fragment3 = new PostponedFragment1();
-
-        final int[] commit = new int[1];
-        // Need to run this on the UI thread so that the transaction doesn't start
-        // between the two
-        mInstrumentation.runOnMainSync(new Runnable() {
-            @Override
-            public void run() {
-                commit[0] = fm.beginTransaction()
-                        .addSharedElement(startBlue, "blueSquare")
-                        .replace(R.id.fragmentContainer, fragment2)
-                        .addToBackStack(null)
-                        .setReorderingAllowed(true)
-                        .commit();
-
-                fm.beginTransaction()
-                        .addSharedElement(startBlue, "blueSquare")
-                        .replace(R.id.fragmentContainer, fragment3)
-                        .addToBackStack(null)
-                        .setReorderingAllowed(true)
-                        .commit();
-            }
-        });
-        FragmentTestUtil.waitForExecution(mActivityRule);
-
-        // transition to fragment2 should be started
-        assertForwardTransition(mBeginningFragment, fragment2);
-
-        // fragment3 should be postponed, but fragment2 should be executed with no transition.
-        assertPostponedTransition(fragment2, fragment3, mBeginningFragment);
-
-        // start the postponed transition
-        fragment3.startPostponedEnterTransition();
-
-        // make sure it ran
-        assertForwardTransition(fragment2, fragment3);
-
-        FragmentTestUtil.popBackStackImmediate(mActivityRule, commit[0],
-                FragmentManager.POP_BACK_STACK_INCLUSIVE);
-
-        assertBackTransition(fragment3, fragment2);
-
-        assertPostponedTransition(fragment2, mBeginningFragment, fragment3);
-
-        // start the postponed transition
-        mBeginningFragment.startPostponedEnterTransition();
-
-        // make sure it ran
-        assertBackTransition(fragment2, mBeginningFragment);
-    }
-
-    // Ensure that postponed transition is forced after another has been committed.
-    // This tests when the transactions are processed separately.
-    @Test
-    public void forcedTransition2() throws Throwable {
-        final FragmentManager fm = mActivityRule.getActivity().getSupportFragmentManager();
-        final View startBlue = mActivityRule.getActivity().findViewById(R.id.blueSquare);
-
-        final PostponedFragment2 fragment2 = new PostponedFragment2();
-
-        fm.beginTransaction()
-                .addSharedElement(startBlue, "blueSquare")
-                .replace(R.id.fragmentContainer, fragment2)
-                .addToBackStack(null)
-                .setReorderingAllowed(true)
-                .commit();
-
-        FragmentTestUtil.waitForExecution(mActivityRule);
-
-        assertPostponedTransition(mBeginningFragment, fragment2, null);
-
-        final PostponedFragment1 fragment3 = new PostponedFragment1();
-        fm.beginTransaction()
-                .addSharedElement(startBlue, "blueSquare")
-                .replace(R.id.fragmentContainer, fragment3)
-                .addToBackStack(null)
-                .setReorderingAllowed(true)
-                .commit();
-
-        // This should cancel the mBeginningFragment -> fragment2 transition
-        // and start fragment2 -> fragment3 transition postponed
-        FragmentTestUtil.waitForExecution(mActivityRule);
-
-        // fragment3 should be postponed, but fragment2 should be executed with no transition.
-        assertPostponedTransition(fragment2, fragment3, mBeginningFragment);
-
-        // start the postponed transition
-        fragment3.startPostponedEnterTransition();
-
-        // make sure it ran
-        assertForwardTransition(fragment2, fragment3);
-
-        // Pop back to fragment2, but it should be postponed
-        FragmentTestUtil.popBackStackImmediate(mActivityRule);
-
-        assertPostponedTransition(fragment3, fragment2, null);
-
-        // Pop to mBeginningFragment -- should cancel the fragment2 transition and
-        // start the mBeginningFragment transaction postponed
-
-        FragmentTestUtil.popBackStackImmediate(mActivityRule);
-
-        assertPostponedTransition(fragment2, mBeginningFragment, fragment3);
-
-        // start the postponed transition
-        mBeginningFragment.startPostponedEnterTransition();
-
-        // make sure it ran
-        assertBackTransition(fragment2, mBeginningFragment);
-    }
-
-    // Do a bunch of things to one fragment in a transaction and see if it can screw things up.
-    @Test
-    public void crazyTransition() throws Throwable {
-        final FragmentManager fm = mActivityRule.getActivity().getSupportFragmentManager();
-        final View startBlue = mActivityRule.getActivity().findViewById(R.id.blueSquare);
-
-        final PostponedFragment2 fragment2 = new PostponedFragment2();
-
-        fm.beginTransaction()
-                .addSharedElement(startBlue, "blueSquare")
-                .hide(mBeginningFragment)
-                .replace(R.id.fragmentContainer, fragment2)
-                .hide(fragment2)
-                .detach(fragment2)
-                .attach(fragment2)
-                .show(fragment2)
-                .addToBackStack(null)
-                .setReorderingAllowed(true)
-                .commit();
-
-        FragmentTestUtil.waitForExecution(mActivityRule);
-
-        assertPostponedTransition(mBeginningFragment, fragment2, null);
-
-        // start the postponed transition
-        fragment2.startPostponedEnterTransition();
-
-        // make sure it ran
-        assertForwardTransition(mBeginningFragment, fragment2);
-
-        // Pop back to fragment2, but it should be postponed
-        FragmentTestUtil.popBackStackImmediate(mActivityRule);
-
-        assertPostponedTransition(fragment2, mBeginningFragment, null);
-
-        // start the postponed transition
-        mBeginningFragment.startPostponedEnterTransition();
-
-        // make sure it ran
-        assertBackTransition(fragment2, mBeginningFragment);
-    }
-
-    // Execute transactions on different containers and ensure that they don't conflict
-    @Test
-    public void differentContainers() throws Throwable {
-        final FragmentManager fm = mActivityRule.getActivity().getSupportFragmentManager();
-        fm.beginTransaction()
-                .remove(mBeginningFragment)
-                .setReorderingAllowed(true)
-                .commit();
-        FragmentTestUtil.waitForExecution(mActivityRule);
-        FragmentTestUtil.setContentView(mActivityRule, R.layout.double_container);
-
-        TransitionFragment fragment1 = new PostponedFragment1();
-        TransitionFragment fragment2 = new PostponedFragment1();
-
-        fm.beginTransaction()
-                .add(R.id.fragmentContainer1, fragment1)
-                .add(R.id.fragmentContainer2, fragment2)
-                .setReorderingAllowed(true)
-                .commit();
-        FragmentTestUtil.waitForExecution(mActivityRule);
-        fragment1.startPostponedEnterTransition();
-        fragment2.startPostponedEnterTransition();
-        fragment1.waitForTransition();
-        fragment2.waitForTransition();
-        clearTargets(fragment1);
-        clearTargets(fragment2);
-
-        final View startBlue1 = fragment1.requireView().findViewById(R.id.blueSquare);
-        final View startBlue2 = fragment2.requireView().findViewById(R.id.blueSquare);
-
-        final TransitionFragment fragment3 = new PostponedFragment2();
-
-        fm.beginTransaction()
-                .addSharedElement(startBlue1, "blueSquare")
-                .replace(R.id.fragmentContainer1, fragment3)
-                .addToBackStack(null)
-                .setReorderingAllowed(true)
-                .commit();
-
-        FragmentTestUtil.waitForExecution(mActivityRule);
-
-        assertPostponedTransition(fragment1, fragment3, null);
-
-        final TransitionFragment fragment4 = new PostponedFragment2();
-
-        fm.beginTransaction()
-                .addSharedElement(startBlue2, "blueSquare")
-                .replace(R.id.fragmentContainer2, fragment4)
-                .addToBackStack(null)
-                .setReorderingAllowed(true)
-                .commit();
-
-        FragmentTestUtil.waitForExecution(mActivityRule);
-
-        assertPostponedTransition(fragment1, fragment3, null);
-        assertPostponedTransition(fragment2, fragment4, null);
-
-        // start the postponed transition
-        fragment3.startPostponedEnterTransition();
-
-        // make sure only one ran
-        assertForwardTransition(fragment1, fragment3);
-        assertPostponedTransition(fragment2, fragment4, null);
-
-        // start the postponed transition
-        fragment4.startPostponedEnterTransition();
-
-        // make sure it ran
-        assertForwardTransition(fragment2, fragment4);
-
-        // Pop back to fragment2 -- should be postponed
-        FragmentTestUtil.popBackStackImmediate(mActivityRule);
-
-        assertPostponedTransition(fragment4, fragment2, null);
-
-        // Pop back to fragment1 -- also should be postponed
-        FragmentTestUtil.popBackStackImmediate(mActivityRule);
-
-        assertPostponedTransition(fragment4, fragment2, null);
-        assertPostponedTransition(fragment3, fragment1, null);
-
-        // start the postponed transition
-        fragment2.startPostponedEnterTransition();
-
-        // make sure it ran
-        assertBackTransition(fragment4, fragment2);
-
-        // but not the postponed one
-        assertPostponedTransition(fragment3, fragment1, null);
-
-        // start the postponed transition
-        fragment1.startPostponedEnterTransition();
-
-        // make sure it ran
-        assertBackTransition(fragment3, fragment1);
-    }
-
-    // Execute transactions on different containers and ensure that they don't conflict.
-    // The postponement can be started out-of-order
-    @Test
-    public void outOfOrderContainers() throws Throwable {
-        final FragmentManager fm = mActivityRule.getActivity().getSupportFragmentManager();
-        fm.beginTransaction()
-                .remove(mBeginningFragment)
-                .setReorderingAllowed(true)
-                .commit();
-        FragmentTestUtil.waitForExecution(mActivityRule);
-        FragmentTestUtil.setContentView(mActivityRule, R.layout.double_container);
-
-        TransitionFragment fragment1 = new PostponedFragment1();
-        TransitionFragment fragment2 = new PostponedFragment1();
-
-        fm.beginTransaction()
-                .add(R.id.fragmentContainer1, fragment1)
-                .add(R.id.fragmentContainer2, fragment2)
-                .setReorderingAllowed(true)
-                .commit();
-        FragmentTestUtil.waitForExecution(mActivityRule);
-        fragment1.startPostponedEnterTransition();
-        fragment2.startPostponedEnterTransition();
-        fragment1.waitForTransition();
-        fragment2.waitForTransition();
-        clearTargets(fragment1);
-        clearTargets(fragment2);
-
-        final View startBlue1 = fragment1.requireView().findViewById(R.id.blueSquare);
-        final View startBlue2 = fragment2.requireView().findViewById(R.id.blueSquare);
-
-        final TransitionFragment fragment3 = new PostponedFragment2();
-
-        fm.beginTransaction()
-                .addSharedElement(startBlue1, "blueSquare")
-                .replace(R.id.fragmentContainer1, fragment3)
-                .addToBackStack(null)
-                .setReorderingAllowed(true)
-                .commit();
-
-        FragmentTestUtil.waitForExecution(mActivityRule);
-
-        assertPostponedTransition(fragment1, fragment3, null);
-
-        final TransitionFragment fragment4 = new PostponedFragment2();
-
-        fm.beginTransaction()
-                .addSharedElement(startBlue2, "blueSquare")
-                .replace(R.id.fragmentContainer2, fragment4)
-                .addToBackStack(null)
-                .setReorderingAllowed(true)
-                .commit();
-
-        FragmentTestUtil.waitForExecution(mActivityRule);
-
-        assertPostponedTransition(fragment1, fragment3, null);
-        assertPostponedTransition(fragment2, fragment4, null);
-
-        // start the postponed transition
-        fragment4.startPostponedEnterTransition();
-
-        // make sure only one ran
-        assertForwardTransition(fragment2, fragment4);
-        assertPostponedTransition(fragment1, fragment3, null);
-
-        // start the postponed transition
-        fragment3.startPostponedEnterTransition();
-
-        // make sure it ran
-        assertForwardTransition(fragment1, fragment3);
-
-        // Pop back to fragment2 -- should be postponed
-        FragmentTestUtil.popBackStackImmediate(mActivityRule);
-
-        assertPostponedTransition(fragment4, fragment2, null);
-
-        // Pop back to fragment1 -- also should be postponed
-        FragmentTestUtil.popBackStackImmediate(mActivityRule);
-
-        assertPostponedTransition(fragment4, fragment2, null);
-        assertPostponedTransition(fragment3, fragment1, null);
-
-        // start the postponed transition
-        fragment1.startPostponedEnterTransition();
-
-        // make sure it ran
-        assertBackTransition(fragment3, fragment1);
-
-        // but not the postponed one
-        assertPostponedTransition(fragment4, fragment2, null);
-
-        // start the postponed transition
-        fragment2.startPostponedEnterTransition();
-
-        // make sure it ran
-        assertBackTransition(fragment4, fragment2);
-    }
-
-    // Make sure that commitNow for a transaction on a different fragment container doesn't
-    // affect the postponed transaction
-    @Test
-    public void commitNowNoEffect() throws Throwable {
-        final FragmentManager fm = mActivityRule.getActivity().getSupportFragmentManager();
-        fm.beginTransaction()
-                .remove(mBeginningFragment)
-                .setReorderingAllowed(true)
-                .commit();
-        FragmentTestUtil.waitForExecution(mActivityRule);
-        FragmentTestUtil.setContentView(mActivityRule, R.layout.double_container);
-
-        final TransitionFragment fragment1 = new PostponedFragment1();
-        final TransitionFragment fragment2 = new PostponedFragment1();
-
-        fm.beginTransaction()
-                .add(R.id.fragmentContainer1, fragment1)
-                .add(R.id.fragmentContainer2, fragment2)
-                .setReorderingAllowed(true)
-                .commit();
-        FragmentTestUtil.waitForExecution(mActivityRule);
-        fragment1.startPostponedEnterTransition();
-        fragment2.startPostponedEnterTransition();
-        fragment1.waitForTransition();
-        fragment2.waitForTransition();
-        clearTargets(fragment1);
-        clearTargets(fragment2);
-
-        final View startBlue1 = fragment1.requireView().findViewById(R.id.blueSquare);
-        final View startBlue2 = fragment2.requireView().findViewById(R.id.blueSquare);
-
-        final TransitionFragment fragment3 = new PostponedFragment2();
-        final StrictFragment strictFragment1 = new StrictFragment();
-
-        fm.beginTransaction()
-                .addSharedElement(startBlue1, "blueSquare")
-                .replace(R.id.fragmentContainer1, fragment3)
-                .add(strictFragment1, "1")
-                .addToBackStack(null)
-                .setReorderingAllowed(true)
-                .commit();
-
-        FragmentTestUtil.waitForExecution(mActivityRule);
-
-        assertPostponedTransition(fragment1, fragment3, null);
-
-        final TransitionFragment fragment4 = new PostponedFragment2();
-        final StrictFragment strictFragment2 = new StrictFragment();
-
-        mInstrumentation.runOnMainSync(new Runnable() {
-            @Override
-            public void run() {
-                fm.beginTransaction()
-                        .addSharedElement(startBlue2, "blueSquare")
-                        .replace(R.id.fragmentContainer2, fragment4)
-                        .remove(strictFragment1)
-                        .add(strictFragment2, "2")
-                        .setReorderingAllowed(true)
-                        .commitNow();
-            }
-        });
-
-        FragmentTestUtil.waitForExecution(mActivityRule);
-
-        assertPostponedTransition(fragment1, fragment3, null);
-        assertPostponedTransition(fragment2, fragment4, null);
-
-        // start the postponed transition
-        fragment4.startPostponedEnterTransition();
-
-        // make sure only one ran
-        assertForwardTransition(fragment2, fragment4);
-        assertPostponedTransition(fragment1, fragment3, null);
-
-        // start the postponed transition
-        fragment3.startPostponedEnterTransition();
-
-        // make sure it ran
-        assertForwardTransition(fragment1, fragment3);
-    }
-
-    // Make sure that commitNow for a transaction affecting a postponed fragment in the same
-    // container forces the postponed transition to start.
-    @Test
-    public void commitNowStartsPostponed() throws Throwable {
-        final FragmentManager fm = mActivityRule.getActivity().getSupportFragmentManager();
-        final View startBlue1 = mBeginningFragment.requireView().findViewById(R.id.blueSquare);
-
-        final TransitionFragment fragment2 = new PostponedFragment2();
-        final TransitionFragment fragment1 = new PostponedFragment1();
-
-        fm.beginTransaction()
-                .addSharedElement(startBlue1, "blueSquare")
-                .replace(R.id.fragmentContainer, fragment2)
-                .addToBackStack(null)
-                .setReorderingAllowed(true)
-                .commit();
-        FragmentTestUtil.waitForExecution(mActivityRule);
-
-        final View startBlue2 = fragment2.requireView().findViewById(R.id.blueSquare);
-
-        mInstrumentation.runOnMainSync(new Runnable() {
-            @Override
-            public void run() {
-                fm.beginTransaction()
-                        .addSharedElement(startBlue2, "blueSquare")
-                        .replace(R.id.fragmentContainer, fragment1)
-                        .setReorderingAllowed(true)
-                        .commitNow();
-            }
-        });
-
-        assertPostponedTransition(fragment2, fragment1, mBeginningFragment);
-
-        // start the postponed transition
-        fragment1.startPostponedEnterTransition();
-
-        assertForwardTransition(fragment2, fragment1);
-    }
-
-    // Make sure that when a transaction that removes a view is postponed that
-    // another transaction doesn't accidentally remove the view early.
-    @Test
-    public void noAccidentalRemoval() throws Throwable {
-        final FragmentManager fm = mActivityRule.getActivity().getSupportFragmentManager();
-        fm.beginTransaction()
-                .remove(mBeginningFragment)
-                .setReorderingAllowed(true)
-                .commit();
-        FragmentTestUtil.waitForExecution(mActivityRule);
-        FragmentTestUtil.setContentView(mActivityRule, R.layout.double_container);
-
-        TransitionFragment fragment1 = new PostponedFragment1();
-
-        fm.beginTransaction()
-                .add(R.id.fragmentContainer1, fragment1)
-                .setReorderingAllowed(true)
-                .commit();
-        FragmentTestUtil.waitForExecution(mActivityRule);
-        fragment1.startPostponedEnterTransition();
-        fragment1.waitForTransition();
-        clearTargets(fragment1);
-
-        TransitionFragment fragment2 = new PostponedFragment2();
-        // Create a postponed transaction that removes a view
-        fm.beginTransaction()
-                .replace(R.id.fragmentContainer1, fragment2)
-                .setReorderingAllowed(true)
-                .commit();
-        FragmentTestUtil.waitForExecution(mActivityRule);
-        assertPostponedTransition(fragment1, fragment2, null);
-
-        TransitionFragment fragment3 = new PostponedFragment1();
-        // Create a transaction that doesn't interfere with the previously postponed one
-        fm.beginTransaction()
-                .replace(R.id.fragmentContainer2, fragment3)
-                .setReorderingAllowed(true)
-                .commit();
-        FragmentTestUtil.waitForExecution(mActivityRule);
-
-        assertPostponedTransition(fragment1, fragment2, null);
-
-        fragment3.startPostponedEnterTransition();
-        fragment3.waitForTransition();
-        clearTargets(fragment3);
-
-        assertPostponedTransition(fragment1, fragment2, null);
-    }
-
-    // Ensure that a postponed transaction that is popped runs immediately and that
-    // the transaction results in the original state with no transition.
-    @Test
-    public void popPostponedTransaction() throws Throwable {
-        final FragmentManager fm = mActivityRule.getActivity().getSupportFragmentManager();
-        final View startBlue = mBeginningFragment.requireView().findViewById(R.id.blueSquare);
-
-        final TransitionFragment fragment = new PostponedFragment2();
-
-        fm.beginTransaction()
-                .addSharedElement(startBlue, "blueSquare")
-                .replace(R.id.fragmentContainer, fragment)
-                .addToBackStack(null)
-                .setReorderingAllowed(true)
-                .commit();
-        FragmentTestUtil.waitForExecution(mActivityRule);
-
-        assertPostponedTransition(mBeginningFragment, fragment, null);
-
-        FragmentTestUtil.popBackStackImmediate(mActivityRule);
-
-        fragment.waitForNoTransition();
-        mBeginningFragment.waitForNoTransition();
-
-        assureNoTransition(fragment);
-        assureNoTransition(mBeginningFragment);
-
-        assertFalse(fragment.isAdded());
-        assertNull(fragment.getView());
-        assertNotNull(mBeginningFragment.getView());
-        assertEquals(View.VISIBLE, mBeginningFragment.getView().getVisibility());
-        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
-            assertEquals(1f, mBeginningFragment.getView().getAlpha(), 0f);
-        }
-        assertTrue(mBeginningFragment.getView().isAttachedToWindow());
-    }
-
-    // Make sure that when saving the state during a postponed transaction that it saves
-    // the state as if it wasn't postponed.
-    @Test
-    public void saveWhilePostponed() throws Throwable {
-        final FragmentController fc1 = FragmentTestUtil.createController(mActivityRule);
-        FragmentTestUtil.resume(mActivityRule, fc1, null);
-
-        final FragmentManager fm1 = fc1.getSupportFragmentManager();
-
-        PostponedFragment1 fragment1 = new PostponedFragment1();
-        fm1.beginTransaction()
-                .add(R.id.fragmentContainer, fragment1, "1")
-                .addToBackStack(null)
-                .setReorderingAllowed(true)
-                .commit();
-        FragmentTestUtil.waitForExecution(mActivityRule);
-
-        Pair<Parcelable, FragmentManagerNonConfig> state =
-                FragmentTestUtil.destroy(mActivityRule, fc1);
-
-        final FragmentController fc2 = FragmentTestUtil.createController(mActivityRule);
-        FragmentTestUtil.resume(mActivityRule, fc2, state);
-
-        final FragmentManager fm2 = fc2.getSupportFragmentManager();
-        Fragment fragment2 = fm2.findFragmentByTag("1");
-        assertNotNull(fragment2);
-        assertNotNull(fragment2.getView());
-        assertEquals(View.VISIBLE, fragment2.getView().getVisibility());
-        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
-            assertEquals(1f, fragment2.getView().getAlpha(), 0f);
-        }
-        assertTrue(fragment2.isResumed());
-        assertTrue(fragment2.isAdded());
-        assertTrue(fragment2.getView().isAttachedToWindow());
-
-        mInstrumentation.runOnMainSync(new Runnable() {
-            @Override
-            public void run() {
-                assertTrue(fm2.popBackStackImmediate());
-
-            }
-        });
-
-        assertFalse(fragment2.isResumed());
-        assertFalse(fragment2.isAdded());
-        assertNull(fragment2.getView());
-    }
-
-    // Ensure that the postponed fragment transactions don't allow reentrancy in fragment manager
-    @Test
-    public void postponeDoesNotAllowReentrancy() throws Throwable {
-        final FragmentManager fm = mActivityRule.getActivity().getSupportFragmentManager();
-        final View startBlue = mActivityRule.getActivity().findViewById(R.id.blueSquare);
-
-        final CommitNowFragment fragment = new CommitNowFragment();
-        fm.beginTransaction()
-                .addSharedElement(startBlue, "blueSquare")
-                .replace(R.id.fragmentContainer, fragment)
-                .setReorderingAllowed(true)
-                .addToBackStack(null)
-                .commit();
-
-        FragmentTestUtil.waitForExecution(mActivityRule);
-
-        // should be postponed now
-        assertPostponedTransition(mBeginningFragment, fragment, null);
-
-        mActivityRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                // start the postponed transition
-                fragment.startPostponedEnterTransition();
-
-                try {
-                    // This should trigger an IllegalStateException
-                    fm.executePendingTransactions();
-                    fail("commitNow() while executing a transaction should cause an "
-                            + "IllegalStateException");
-                } catch (IllegalStateException e) {
-                    // expected
-                }
-            }
-        });
-    }
-
-    private void assertPostponedTransition(TransitionFragment fromFragment,
-            TransitionFragment toFragment, TransitionFragment removedFragment)
-            throws InterruptedException {
-        if (removedFragment != null) {
-            assertNull(removedFragment.getView());
-            assureNoTransition(removedFragment);
-        }
-
-        toFragment.waitForNoTransition();
-        assertNotNull(fromFragment.getView());
-        assertNotNull(toFragment.getView());
-        assertTrue(fromFragment.getView().isAttachedToWindow());
-        assertTrue(toFragment.getView().isAttachedToWindow());
-        assertEquals(View.VISIBLE, fromFragment.getView().getVisibility());
-        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
-            assertEquals(View.VISIBLE, toFragment.getView().getVisibility());
-            assertEquals(0f, toFragment.getView().getAlpha(), 0f);
-        } else {
-            assertEquals(View.INVISIBLE, toFragment.getView().getVisibility());
-        }
-        assureNoTransition(fromFragment);
-        assureNoTransition(toFragment);
-        assertTrue(fromFragment.isResumed());
-        assertFalse(toFragment.isResumed());
-    }
-
-    private void clearTargets(TransitionFragment fragment) {
-        fragment.enterTransition.targets.clear();
-        fragment.reenterTransition.targets.clear();
-        fragment.exitTransition.targets.clear();
-        fragment.returnTransition.targets.clear();
-        fragment.sharedElementEnter.targets.clear();
-        fragment.sharedElementReturn.targets.clear();
-    }
-
-    private void assureNoTransition(TransitionFragment fragment) {
-        assertEquals(0, fragment.enterTransition.targets.size());
-        assertEquals(0, fragment.reenterTransition.targets.size());
-        assertEquals(0, fragment.enterTransition.targets.size());
-        assertEquals(0, fragment.returnTransition.targets.size());
-        assertEquals(0, fragment.sharedElementEnter.targets.size());
-        assertEquals(0, fragment.sharedElementReturn.targets.size());
-    }
-
-    private void assertForwardTransition(TransitionFragment start, TransitionFragment end)
-            throws InterruptedException {
-        start.waitForTransition();
-        end.waitForTransition();
-        assertEquals(0, start.enterTransition.targets.size());
-        assertEquals(1, end.enterTransition.targets.size());
-
-        assertEquals(0, start.reenterTransition.targets.size());
-        assertEquals(0, end.reenterTransition.targets.size());
-
-        assertEquals(0, start.returnTransition.targets.size());
-        assertEquals(0, end.returnTransition.targets.size());
-
-        assertEquals(1, start.exitTransition.targets.size());
-        assertEquals(0, end.exitTransition.targets.size());
-
-        assertEquals(0, start.sharedElementEnter.targets.size());
-        assertEquals(2, end.sharedElementEnter.targets.size());
-
-        assertEquals(0, start.sharedElementReturn.targets.size());
-        assertEquals(0, end.sharedElementReturn.targets.size());
-
-        final View blue = end.requireView().findViewById(R.id.blueSquare);
-        assertTrue(end.sharedElementEnter.targets.contains(blue));
-        assertEquals("blueSquare", end.sharedElementEnter.targets.get(0).getTransitionName());
-        assertEquals("blueSquare", end.sharedElementEnter.targets.get(1).getTransitionName());
-
-        assertNoTargets(start);
-        assertNoTargets(end);
-
-        clearTargets(start);
-        clearTargets(end);
-    }
-
-    private void assertBackTransition(TransitionFragment start, TransitionFragment end)
-            throws InterruptedException {
-        start.waitForTransition();
-        end.waitForTransition();
-        assertEquals(1, end.reenterTransition.targets.size());
-        assertEquals(0, start.reenterTransition.targets.size());
-
-        assertEquals(0, end.returnTransition.targets.size());
-        assertEquals(1, start.returnTransition.targets.size());
-
-        assertEquals(0, start.enterTransition.targets.size());
-        assertEquals(0, end.enterTransition.targets.size());
-
-        assertEquals(0, start.exitTransition.targets.size());
-        assertEquals(0, end.exitTransition.targets.size());
-
-        assertEquals(0, start.sharedElementEnter.targets.size());
-        assertEquals(0, end.sharedElementEnter.targets.size());
-
-        assertEquals(2, start.sharedElementReturn.targets.size());
-        assertEquals(0, end.sharedElementReturn.targets.size());
-
-        final View blue = end.requireView().findViewById(R.id.blueSquare);
-        assertTrue(start.sharedElementReturn.targets.contains(blue));
-        assertEquals("blueSquare", start.sharedElementReturn.targets.get(0).getTransitionName());
-        assertEquals("blueSquare", start.sharedElementReturn.targets.get(1).getTransitionName());
-
-        assertNoTargets(end);
-        assertNoTargets(start);
-
-        clearTargets(start);
-        clearTargets(end);
-    }
-
-    private static void assertNoTargets(TransitionFragment fragment) {
-        assertTrue(fragment.enterTransition.getTargets().isEmpty());
-        assertTrue(fragment.reenterTransition.getTargets().isEmpty());
-        assertTrue(fragment.exitTransition.getTargets().isEmpty());
-        assertTrue(fragment.returnTransition.getTargets().isEmpty());
-        assertTrue(fragment.sharedElementEnter.getTargets().isEmpty());
-        assertTrue(fragment.sharedElementReturn.getTargets().isEmpty());
-    }
-
-    @ContentView(R.layout.scene1)
-    public static class PostponedFragment1 extends TransitionFragment {
-        @Override
-        public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
-                @Nullable Bundle savedInstanceState) {
-            postponeEnterTransition();
-            return super.onCreateView(inflater, container, savedInstanceState);
-        }
-    }
-
-    @ContentView(R.layout.scene2)
-    public static class PostponedFragment2 extends TransitionFragment {
-        @Override
-        public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
-                @Nullable Bundle savedInstanceState) {
-            postponeEnterTransition();
-            return super.onCreateView(inflater, container, savedInstanceState);
-        }
-    }
-
-    public static class CommitNowFragment extends PostponedFragment1 {
-        @Override
-        public void onResume() {
-            super.onResume();
-            // This should throw because this happens during the execution
-            getFragmentManager().beginTransaction()
-                    .add(R.id.fragmentContainer, new PostponedFragment1())
-                    .commitNow();
-        }
-    }
-
-    @ContentView(R.layout.scene2)
-    public static class TransitionFragment2 extends TransitionFragment {
-    }
-}
diff --git a/fragment/src/androidTest/java/androidx/fragment/app/PostponedTransitionTest.kt b/fragment/src/androidTest/java/androidx/fragment/app/PostponedTransitionTest.kt
new file mode 100644
index 0000000..5432969
--- /dev/null
+++ b/fragment/src/androidTest/java/androidx/fragment/app/PostponedTransitionTest.kt
@@ -0,0 +1,937 @@
+/*
+ * 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.Build
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.fragment.app.test.FragmentTestActivity
+import androidx.fragment.test.R
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.LargeTest
+import androidx.test.filters.SdkSuppress
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.rule.ActivityTestRule
+import com.google.common.truth.Truth.assertThat
+import org.junit.Assert.fail
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import java.util.concurrent.CountDownLatch
+
+@LargeTest
+@RunWith(AndroidJUnit4::class)
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.LOLLIPOP)
+class PostponedTransitionTest {
+    @get:Rule
+    val activityRule = ActivityTestRule(FragmentTestActivity::class.java)
+
+    private val instrumentation = InstrumentationRegistry.getInstrumentation()
+    private val beginningFragment = PostponedFragment1()
+
+    @Before
+    fun setupContainer() {
+        FragmentTestUtil.setContentView(activityRule, R.layout.simple_container)
+        val fm = activityRule.activity.supportFragmentManager
+
+        val backStackLatch = CountDownLatch(1)
+        val backStackListener = object : FragmentManager.OnBackStackChangedListener {
+            override fun onBackStackChanged() {
+                backStackLatch.countDown()
+                fm.removeOnBackStackChangedListener(this)
+            }
+        }
+        fm.addOnBackStackChangedListener(backStackListener)
+        fm.beginTransaction()
+            .add(R.id.fragmentContainer, beginningFragment)
+            .setReorderingAllowed(true)
+            .addToBackStack(null)
+            .commit()
+
+        backStackLatch.await()
+
+        beginningFragment.startPostponedEnterTransition()
+        beginningFragment.waitForTransition()
+        clearTargets(beginningFragment)
+    }
+
+    // Ensure that replacing with a fragment that has a postponed transition
+    // will properly postpone it, both adding and popping.
+    @Test
+    fun replaceTransition() {
+        val fm = activityRule.activity.supportFragmentManager
+        val startBlue = activityRule.activity.findViewById<View>(R.id.blueSquare)
+
+        val fragment = PostponedFragment2()
+        fm.beginTransaction()
+            .addSharedElement(startBlue, "blueSquare")
+            .replace(R.id.fragmentContainer, fragment)
+            .addToBackStack(null)
+            .setReorderingAllowed(true)
+            .commit()
+
+        FragmentTestUtil.waitForExecution(activityRule)
+
+        // should be postponed now
+        assertPostponedTransition(beginningFragment, fragment)
+
+        // start the postponed transition
+        fragment.startPostponedEnterTransition()
+
+        // make sure it ran
+        assertForwardTransition(beginningFragment, fragment)
+
+        FragmentTestUtil.popBackStackImmediate(activityRule)
+
+        // should be postponed going back, too
+        assertPostponedTransition(fragment, beginningFragment)
+
+        // start the postponed transition
+        beginningFragment.startPostponedEnterTransition()
+
+        // make sure it ran
+        assertBackTransition(fragment, beginningFragment)
+    }
+
+    // Ensure that replacing a fragment doesn't cause problems with the back stack nesting level
+    @Test
+    fun backStackNestingLevel() {
+        val fm = activityRule.activity.supportFragmentManager
+        var startBlue = activityRule.activity.findViewById<View>(R.id.blueSquare)
+
+        val fragment1 = TransitionFragment(R.layout.scene2)
+        fm.beginTransaction()
+            .addSharedElement(startBlue, "blueSquare")
+            .replace(R.id.fragmentContainer, fragment1)
+            .addToBackStack(null)
+            .setReorderingAllowed(true)
+            .commit()
+
+        // make sure transition ran
+        assertForwardTransition(beginningFragment, fragment1)
+
+        FragmentTestUtil.popBackStackImmediate(activityRule)
+
+        // should be postponed going back
+        assertPostponedTransition(fragment1, beginningFragment)
+
+        // start the postponed transition
+        beginningFragment.startPostponedEnterTransition()
+
+        // make sure it ran
+        assertBackTransition(fragment1, beginningFragment)
+
+        startBlue = activityRule.activity.findViewById(R.id.blueSquare)
+
+        val fragment2 = TransitionFragment(R.layout.scene2)
+        fm.beginTransaction()
+            .addSharedElement(startBlue, "blueSquare")
+            .replace(R.id.fragmentContainer, fragment2)
+            .addToBackStack(null)
+            .setReorderingAllowed(true)
+            .commit()
+
+        // make sure transition ran
+        assertForwardTransition(beginningFragment, fragment2)
+
+        FragmentTestUtil.popBackStackImmediate(activityRule)
+
+        // should be postponed going back
+        assertPostponedTransition(fragment2, beginningFragment)
+
+        // start the postponed transition
+        beginningFragment.startPostponedEnterTransition()
+
+        // make sure it ran
+        assertBackTransition(fragment2, beginningFragment)
+    }
+
+    // Ensure that postponed transition is forced after another has been committed.
+    // This tests when the transactions are executed together
+    @Test
+    fun forcedTransition1() {
+        val fm = activityRule.activity.supportFragmentManager
+        val startBlue = activityRule.activity.findViewById<View>(R.id.blueSquare)
+
+        val fragment2 = PostponedFragment2()
+        val fragment3 = PostponedFragment1()
+
+        var commit = 0
+        // Need to run this on the UI thread so that the transaction doesn't start
+        // between the two
+        instrumentation.runOnMainSync {
+            commit = fm.beginTransaction()
+                .addSharedElement(startBlue, "blueSquare")
+                .replace(R.id.fragmentContainer, fragment2)
+                .addToBackStack(null)
+                .setReorderingAllowed(true)
+                .commit()
+
+            fm.beginTransaction()
+                .addSharedElement(startBlue, "blueSquare")
+                .replace(R.id.fragmentContainer, fragment3)
+                .addToBackStack(null)
+                .setReorderingAllowed(true)
+                .commit()
+        }
+        FragmentTestUtil.waitForExecution(activityRule)
+
+        // transition to fragment2 should be started
+        assertForwardTransition(beginningFragment, fragment2)
+
+        // fragment3 should be postponed, but fragment2 should be executed with no transition.
+        assertPostponedTransition(fragment2, fragment3, beginningFragment)
+
+        // start the postponed transition
+        fragment3.startPostponedEnterTransition()
+
+        // make sure it ran
+        assertForwardTransition(fragment2, fragment3)
+
+        FragmentTestUtil.popBackStackImmediate(
+            activityRule, commit,
+            FragmentManager.POP_BACK_STACK_INCLUSIVE
+        )
+
+        assertBackTransition(fragment3, fragment2)
+
+        assertPostponedTransition(fragment2, beginningFragment, fragment3)
+
+        // start the postponed transition
+        beginningFragment.startPostponedEnterTransition()
+
+        // make sure it ran
+        assertBackTransition(fragment2, beginningFragment)
+    }
+
+    // Ensure that postponed transition is forced after another has been committed.
+    // This tests when the transactions are processed separately.
+    @Test
+    fun forcedTransition2() {
+        val fm = activityRule.activity.supportFragmentManager
+        val startBlue = activityRule.activity.findViewById<View>(R.id.blueSquare)
+
+        val fragment2 = PostponedFragment2()
+
+        fm.beginTransaction()
+            .addSharedElement(startBlue, "blueSquare")
+            .replace(R.id.fragmentContainer, fragment2)
+            .addToBackStack(null)
+            .setReorderingAllowed(true)
+            .commit()
+
+        FragmentTestUtil.waitForExecution(activityRule)
+
+        assertPostponedTransition(beginningFragment, fragment2)
+
+        val fragment3 = PostponedFragment1()
+        fm.beginTransaction()
+            .addSharedElement(startBlue, "blueSquare")
+            .replace(R.id.fragmentContainer, fragment3)
+            .addToBackStack(null)
+            .setReorderingAllowed(true)
+            .commit()
+
+        // This should cancel the beginningFragment -> fragment2 transition
+        // and start fragment2 -> fragment3 transition postponed
+        FragmentTestUtil.waitForExecution(activityRule)
+
+        // fragment3 should be postponed, but fragment2 should be executed with no transition.
+        assertPostponedTransition(fragment2, fragment3, beginningFragment)
+
+        // start the postponed transition
+        fragment3.startPostponedEnterTransition()
+
+        // make sure it ran
+        assertForwardTransition(fragment2, fragment3)
+
+        // Pop back to fragment2, but it should be postponed
+        FragmentTestUtil.popBackStackImmediate(activityRule)
+
+        assertPostponedTransition(fragment3, fragment2)
+
+        // Pop to beginningFragment -- should cancel the fragment2 transition and
+        // start the beginningFragment transaction postponed
+        FragmentTestUtil.popBackStackImmediate(activityRule)
+
+        assertPostponedTransition(fragment2, beginningFragment, fragment3)
+
+        // start the postponed transition
+        beginningFragment.startPostponedEnterTransition()
+
+        // make sure it ran
+        assertBackTransition(fragment2, beginningFragment)
+    }
+
+    // Do a bunch of things to one fragment in a transaction and see if it can screw things up.
+    @Test
+    fun crazyTransition() {
+        val fm = activityRule.activity.supportFragmentManager
+        val startBlue = activityRule.activity.findViewById<View>(R.id.blueSquare)
+
+        val fragment2 = PostponedFragment2()
+
+        fm.beginTransaction()
+            .addSharedElement(startBlue, "blueSquare")
+            .hide(beginningFragment)
+            .replace(R.id.fragmentContainer, fragment2)
+            .hide(fragment2)
+            .detach(fragment2)
+            .attach(fragment2)
+            .show(fragment2)
+            .addToBackStack(null)
+            .setReorderingAllowed(true)
+            .commit()
+
+        FragmentTestUtil.waitForExecution(activityRule)
+
+        assertPostponedTransition(beginningFragment, fragment2)
+
+        // start the postponed transition
+        fragment2.startPostponedEnterTransition()
+
+        // make sure it ran
+        assertForwardTransition(beginningFragment, fragment2)
+
+        // Pop back to fragment2, but it should be postponed
+        FragmentTestUtil.popBackStackImmediate(activityRule)
+
+        assertPostponedTransition(fragment2, beginningFragment)
+
+        // start the postponed transition
+        beginningFragment.startPostponedEnterTransition()
+
+        // make sure it ran
+        assertBackTransition(fragment2, beginningFragment)
+    }
+
+    // Execute transactions on different containers and ensure that they don't conflict
+    @Test
+    fun differentContainers() {
+        val fm = activityRule.activity.supportFragmentManager
+        fm.beginTransaction()
+            .remove(beginningFragment)
+            .setReorderingAllowed(true)
+            .commit()
+        FragmentTestUtil.waitForExecution(activityRule)
+        FragmentTestUtil.setContentView(activityRule, R.layout.double_container)
+
+        val fragment1 = PostponedFragment1()
+        val fragment2 = PostponedFragment1()
+
+        fm.beginTransaction()
+            .add(R.id.fragmentContainer1, fragment1)
+            .add(R.id.fragmentContainer2, fragment2)
+            .setReorderingAllowed(true)
+            .commit()
+        FragmentTestUtil.waitForExecution(activityRule)
+        fragment1.startPostponedEnterTransition()
+        fragment2.startPostponedEnterTransition()
+        fragment1.waitForTransition()
+        fragment2.waitForTransition()
+        clearTargets(fragment1)
+        clearTargets(fragment2)
+
+        val startBlue1 = fragment1.requireView().findViewById<View>(R.id.blueSquare)
+        val startBlue2 = fragment2.requireView().findViewById<View>(R.id.blueSquare)
+
+        val fragment3 = PostponedFragment2()
+
+        fm.beginTransaction()
+            .addSharedElement(startBlue1, "blueSquare")
+            .replace(R.id.fragmentContainer1, fragment3)
+            .addToBackStack(null)
+            .setReorderingAllowed(true)
+            .commit()
+
+        FragmentTestUtil.waitForExecution(activityRule)
+
+        assertPostponedTransition(fragment1, fragment3)
+
+        val fragment4 = PostponedFragment2()
+
+        fm.beginTransaction()
+            .addSharedElement(startBlue2, "blueSquare")
+            .replace(R.id.fragmentContainer2, fragment4)
+            .addToBackStack(null)
+            .setReorderingAllowed(true)
+            .commit()
+
+        FragmentTestUtil.waitForExecution(activityRule)
+
+        assertPostponedTransition(fragment1, fragment3)
+        assertPostponedTransition(fragment2, fragment4)
+
+        // start the postponed transition
+        fragment3.startPostponedEnterTransition()
+
+        // make sure only one ran
+        assertForwardTransition(fragment1, fragment3)
+        assertPostponedTransition(fragment2, fragment4)
+
+        // start the postponed transition
+        fragment4.startPostponedEnterTransition()
+
+        // make sure it ran
+        assertForwardTransition(fragment2, fragment4)
+
+        // Pop back to fragment2 -- should be postponed
+        FragmentTestUtil.popBackStackImmediate(activityRule)
+
+        assertPostponedTransition(fragment4, fragment2)
+
+        // Pop back to fragment1 -- also should be postponed
+        FragmentTestUtil.popBackStackImmediate(activityRule)
+
+        assertPostponedTransition(fragment4, fragment2)
+        assertPostponedTransition(fragment3, fragment1)
+
+        // start the postponed transition
+        fragment2.startPostponedEnterTransition()
+
+        // make sure it ran
+        assertBackTransition(fragment4, fragment2)
+
+        // but not the postponed one
+        assertPostponedTransition(fragment3, fragment1)
+
+        // start the postponed transition
+        fragment1.startPostponedEnterTransition()
+
+        // make sure it ran
+        assertBackTransition(fragment3, fragment1)
+    }
+
+    // Execute transactions on different containers and ensure that they don't conflict.
+    // The postponement can be started out-of-order
+    @Test
+    fun outOfOrderContainers() {
+        val fm = activityRule.activity.supportFragmentManager
+        fm.beginTransaction()
+            .remove(beginningFragment)
+            .setReorderingAllowed(true)
+            .commit()
+        FragmentTestUtil.waitForExecution(activityRule)
+        FragmentTestUtil.setContentView(activityRule, R.layout.double_container)
+
+        val fragment1 = PostponedFragment1()
+        val fragment2 = PostponedFragment1()
+
+        fm.beginTransaction()
+            .add(R.id.fragmentContainer1, fragment1)
+            .add(R.id.fragmentContainer2, fragment2)
+            .setReorderingAllowed(true)
+            .commit()
+        FragmentTestUtil.waitForExecution(activityRule)
+        fragment1.startPostponedEnterTransition()
+        fragment2.startPostponedEnterTransition()
+        fragment1.waitForTransition()
+        fragment2.waitForTransition()
+        clearTargets(fragment1)
+        clearTargets(fragment2)
+
+        val startBlue1 = fragment1.requireView().findViewById<View>(R.id.blueSquare)
+        val startBlue2 = fragment2.requireView().findViewById<View>(R.id.blueSquare)
+
+        val fragment3 = PostponedFragment2()
+
+        fm.beginTransaction()
+            .addSharedElement(startBlue1, "blueSquare")
+            .replace(R.id.fragmentContainer1, fragment3)
+            .addToBackStack(null)
+            .setReorderingAllowed(true)
+            .commit()
+
+        FragmentTestUtil.waitForExecution(activityRule)
+
+        assertPostponedTransition(fragment1, fragment3)
+
+        val fragment4 = PostponedFragment2()
+
+        fm.beginTransaction()
+            .addSharedElement(startBlue2, "blueSquare")
+            .replace(R.id.fragmentContainer2, fragment4)
+            .addToBackStack(null)
+            .setReorderingAllowed(true)
+            .commit()
+
+        FragmentTestUtil.waitForExecution(activityRule)
+
+        assertPostponedTransition(fragment1, fragment3)
+        assertPostponedTransition(fragment2, fragment4)
+
+        // start the postponed transition
+        fragment4.startPostponedEnterTransition()
+
+        // make sure only one ran
+        assertForwardTransition(fragment2, fragment4)
+        assertPostponedTransition(fragment1, fragment3)
+
+        // start the postponed transition
+        fragment3.startPostponedEnterTransition()
+
+        // make sure it ran
+        assertForwardTransition(fragment1, fragment3)
+
+        // Pop back to fragment2 -- should be postponed
+        FragmentTestUtil.popBackStackImmediate(activityRule)
+
+        assertPostponedTransition(fragment4, fragment2)
+
+        // Pop back to fragment1 -- also should be postponed
+        FragmentTestUtil.popBackStackImmediate(activityRule)
+
+        assertPostponedTransition(fragment4, fragment2)
+        assertPostponedTransition(fragment3, fragment1)
+
+        // start the postponed transition
+        fragment1.startPostponedEnterTransition()
+
+        // make sure it ran
+        assertBackTransition(fragment3, fragment1)
+
+        // but not the postponed one
+        assertPostponedTransition(fragment4, fragment2)
+
+        // start the postponed transition
+        fragment2.startPostponedEnterTransition()
+
+        // make sure it ran
+        assertBackTransition(fragment4, fragment2)
+    }
+
+    // Make sure that commitNow for a transaction on a different fragment container doesn't
+    // affect the postponed transaction
+    @Test
+    fun commitNowNoEffect() {
+        val fm = activityRule.activity.supportFragmentManager
+        fm.beginTransaction()
+            .remove(beginningFragment)
+            .setReorderingAllowed(true)
+            .commit()
+        FragmentTestUtil.waitForExecution(activityRule)
+        FragmentTestUtil.setContentView(activityRule, R.layout.double_container)
+
+        val fragment1 = PostponedFragment1()
+        val fragment2 = PostponedFragment1()
+
+        fm.beginTransaction()
+            .add(R.id.fragmentContainer1, fragment1)
+            .add(R.id.fragmentContainer2, fragment2)
+            .setReorderingAllowed(true)
+            .commit()
+        FragmentTestUtil.waitForExecution(activityRule)
+        fragment1.startPostponedEnterTransition()
+        fragment2.startPostponedEnterTransition()
+        fragment1.waitForTransition()
+        fragment2.waitForTransition()
+        clearTargets(fragment1)
+        clearTargets(fragment2)
+
+        val startBlue1 = fragment1.requireView().findViewById<View>(R.id.blueSquare)
+        val startBlue2 = fragment2.requireView().findViewById<View>(R.id.blueSquare)
+
+        val fragment3 = PostponedFragment2()
+        val strictFragment1 = StrictFragment()
+
+        fm.beginTransaction()
+            .addSharedElement(startBlue1, "blueSquare")
+            .replace(R.id.fragmentContainer1, fragment3)
+            .add(strictFragment1, "1")
+            .addToBackStack(null)
+            .setReorderingAllowed(true)
+            .commit()
+
+        FragmentTestUtil.waitForExecution(activityRule)
+
+        assertPostponedTransition(fragment1, fragment3)
+
+        val fragment4 = PostponedFragment2()
+        val strictFragment2 = StrictFragment()
+
+        instrumentation.runOnMainSync {
+            fm.beginTransaction()
+                .addSharedElement(startBlue2, "blueSquare")
+                .replace(R.id.fragmentContainer2, fragment4)
+                .remove(strictFragment1)
+                .add(strictFragment2, "2")
+                .setReorderingAllowed(true)
+                .commitNow()
+        }
+
+        FragmentTestUtil.waitForExecution(activityRule)
+
+        assertPostponedTransition(fragment1, fragment3)
+        assertPostponedTransition(fragment2, fragment4)
+
+        // start the postponed transition
+        fragment4.startPostponedEnterTransition()
+
+        // make sure only one ran
+        assertForwardTransition(fragment2, fragment4)
+        assertPostponedTransition(fragment1, fragment3)
+
+        // start the postponed transition
+        fragment3.startPostponedEnterTransition()
+
+        // make sure it ran
+        assertForwardTransition(fragment1, fragment3)
+    }
+
+    // Make sure that commitNow for a transaction affecting a postponed fragment in the same
+    // container forces the postponed transition to start.
+    @Test
+    fun commitNowStartsPostponed() {
+        val fm = activityRule.activity.supportFragmentManager
+        val startBlue1 = beginningFragment.requireView().findViewById<View>(R.id.blueSquare)
+
+        val fragment2 = PostponedFragment2()
+        val fragment1 = PostponedFragment1()
+
+        fm.beginTransaction()
+            .addSharedElement(startBlue1, "blueSquare")
+            .replace(R.id.fragmentContainer, fragment2)
+            .addToBackStack(null)
+            .setReorderingAllowed(true)
+            .commit()
+        FragmentTestUtil.waitForExecution(activityRule)
+
+        val startBlue2 = fragment2.requireView().findViewById<View>(R.id.blueSquare)
+
+        instrumentation.runOnMainSync {
+            fm.beginTransaction()
+                .addSharedElement(startBlue2, "blueSquare")
+                .replace(R.id.fragmentContainer, fragment1)
+                .setReorderingAllowed(true)
+                .commitNow()
+        }
+
+        assertPostponedTransition(fragment2, fragment1, beginningFragment)
+
+        // start the postponed transition
+        fragment1.startPostponedEnterTransition()
+
+        assertForwardTransition(fragment2, fragment1)
+    }
+
+    // Make sure that when a transaction that removes a view is postponed that
+    // another transaction doesn't accidentally remove the view early.
+    @Test
+    fun noAccidentalRemoval() {
+        val fm = activityRule.activity.supportFragmentManager
+        fm.beginTransaction()
+            .remove(beginningFragment)
+            .setReorderingAllowed(true)
+            .commit()
+        FragmentTestUtil.waitForExecution(activityRule)
+        FragmentTestUtil.setContentView(activityRule, R.layout.double_container)
+
+        val fragment1 = PostponedFragment1()
+
+        fm.beginTransaction()
+            .add(R.id.fragmentContainer1, fragment1)
+            .setReorderingAllowed(true)
+            .commit()
+        FragmentTestUtil.waitForExecution(activityRule)
+        fragment1.startPostponedEnterTransition()
+        fragment1.waitForTransition()
+        clearTargets(fragment1)
+
+        val fragment2 = PostponedFragment2()
+        // Create a postponed transaction that removes a view
+        fm.beginTransaction()
+            .replace(R.id.fragmentContainer1, fragment2)
+            .setReorderingAllowed(true)
+            .commit()
+        FragmentTestUtil.waitForExecution(activityRule)
+        assertPostponedTransition(fragment1, fragment2)
+
+        val fragment3 = PostponedFragment1()
+        // Create a transaction that doesn't interfere with the previously postponed one
+        fm.beginTransaction()
+            .replace(R.id.fragmentContainer2, fragment3)
+            .setReorderingAllowed(true)
+            .commit()
+        FragmentTestUtil.waitForExecution(activityRule)
+
+        assertPostponedTransition(fragment1, fragment2)
+
+        fragment3.startPostponedEnterTransition()
+        fragment3.waitForTransition()
+        clearTargets(fragment3)
+
+        assertPostponedTransition(fragment1, fragment2)
+    }
+
+    // Ensure that a postponed transaction that is popped runs immediately and that
+    // the transaction results in the original state with no transition.
+    @Test
+    fun popPostponedTransaction() {
+        val fm = activityRule.activity.supportFragmentManager
+        val startBlue = beginningFragment.requireView().findViewById<View>(R.id.blueSquare)
+
+        val fragment = PostponedFragment2()
+
+        fm.beginTransaction()
+            .addSharedElement(startBlue, "blueSquare")
+            .replace(R.id.fragmentContainer, fragment)
+            .addToBackStack(null)
+            .setReorderingAllowed(true)
+            .commit()
+        FragmentTestUtil.waitForExecution(activityRule)
+
+        assertPostponedTransition(beginningFragment, fragment)
+
+        FragmentTestUtil.popBackStackImmediate(activityRule)
+
+        fragment.waitForNoTransition()
+        beginningFragment.waitForNoTransition()
+
+        assureNoTransition(fragment)
+        assureNoTransition(beginningFragment)
+
+        assertThat(fragment.isAdded).isFalse()
+        assertThat(fragment.view).isNull()
+        assertThat(beginningFragment.view).isNotNull()
+        assertThat(beginningFragment.requireView().visibility).isEqualTo(View.VISIBLE)
+        assertThat(beginningFragment.requireView().alpha).isWithin(0f).of(1f)
+        assertThat(beginningFragment.requireView().isAttachedToWindow).isTrue()
+    }
+
+    // Make sure that when saving the state during a postponed transaction that it saves
+    // the state as if it wasn't postponed.
+    @Test
+    fun saveWhilePostponed() {
+        val fc1 = FragmentTestUtil.createController(activityRule)
+        FragmentTestUtil.resume(activityRule, fc1, null)
+
+        val fm1 = fc1.supportFragmentManager
+
+        val fragment1 = PostponedFragment1()
+        fm1.beginTransaction()
+            .add(R.id.fragmentContainer, fragment1, "1")
+            .addToBackStack(null)
+            .setReorderingAllowed(true)
+            .commit()
+        FragmentTestUtil.waitForExecution(activityRule)
+
+        val state = FragmentTestUtil.destroy(activityRule, fc1)
+
+        val fc2 = FragmentTestUtil.createController(activityRule)
+        FragmentTestUtil.resume(activityRule, fc2, state)
+
+        val fm2 = fc2.supportFragmentManager
+        val fragment2 = fm2.findFragmentByTag("1")!!
+        assertThat(fragment2).isNotNull()
+        assertThat(fragment2.requireView()).isNotNull()
+        assertThat(fragment2.requireView().visibility).isEqualTo(View.VISIBLE)
+        assertThat(fragment2.requireView().alpha).isWithin(0f).of(1f)
+        assertThat(fragment2.isResumed).isTrue()
+        assertThat(fragment2.isAdded).isTrue()
+        assertThat(fragment2.requireView().isAttachedToWindow).isTrue()
+
+        instrumentation.runOnMainSync { assertThat(fm2.popBackStackImmediate()).isTrue() }
+
+        assertThat(fragment2.isResumed).isFalse()
+        assertThat(fragment2.isAdded).isFalse()
+        assertThat(fragment2.view).isNull()
+    }
+
+    // Ensure that the postponed fragment transactions don't allow reentrancy in fragment manager
+    @Test
+    fun postponeDoesNotAllowReentrancy() {
+        val fm = activityRule.activity.supportFragmentManager
+        val startBlue = activityRule.activity.findViewById<View>(R.id.blueSquare)
+
+        val fragment = CommitNowFragment()
+        fm.beginTransaction()
+            .addSharedElement(startBlue, "blueSquare")
+            .replace(R.id.fragmentContainer, fragment)
+            .setReorderingAllowed(true)
+            .addToBackStack(null)
+            .commit()
+
+        FragmentTestUtil.waitForExecution(activityRule)
+
+        // should be postponed now
+        assertPostponedTransition(beginningFragment, fragment)
+
+        activityRule.runOnUiThread {
+            // start the postponed transition
+            fragment.startPostponedEnterTransition()
+
+            try {
+                // This should trigger an IllegalStateException
+                fm.executePendingTransactions()
+                fail("commitNow() while executing a transaction should cause an" +
+                        " IllegalStateException")
+            } catch (e: IllegalStateException) {
+                assertThat(e)
+                    .hasMessageThat().contains("FragmentManager is already executing transactions")
+            }
+        }
+    }
+
+    private fun assertPostponedTransition(
+        fromFragment: TransitionFragment,
+        toFragment: TransitionFragment,
+        removedFragment: TransitionFragment? = null
+    ) {
+        if (removedFragment != null) {
+            assertThat(removedFragment.view).isNull()
+            assureNoTransition(removedFragment)
+        }
+
+        toFragment.waitForNoTransition()
+        assertThat(fromFragment.view).isNotNull()
+        assertThat(toFragment.view).isNotNull()
+        assertThat(fromFragment.requireView().isAttachedToWindow).isTrue()
+        assertThat(toFragment.requireView().isAttachedToWindow).isTrue()
+        assertThat(fromFragment.requireView().visibility).isEqualTo(View.VISIBLE)
+        assertThat(toFragment.requireView().visibility).isEqualTo(View.VISIBLE)
+        assertThat(toFragment.requireView().alpha).isWithin(0f).of(0f)
+        assureNoTransition(fromFragment)
+        assureNoTransition(toFragment)
+        assertThat(fromFragment.isResumed).isTrue()
+        assertThat(toFragment.isResumed).isFalse()
+    }
+
+    private fun clearTargets(fragment: TransitionFragment) {
+        fragment.enterTransition.targets.clear()
+        fragment.reenterTransition.targets.clear()
+        fragment.exitTransition.targets.clear()
+        fragment.returnTransition.targets.clear()
+        fragment.sharedElementEnter.targets.clear()
+        fragment.sharedElementReturn.targets.clear()
+    }
+
+    private fun assureNoTransition(fragment: TransitionFragment) {
+        assertThat(fragment.enterTransition.targets.size).isEqualTo(0)
+        assertThat(fragment.reenterTransition.targets.size).isEqualTo(0)
+        assertThat(fragment.enterTransition.targets.size).isEqualTo(0)
+        assertThat(fragment.returnTransition.targets.size).isEqualTo(0)
+        assertThat(fragment.sharedElementEnter.targets.size).isEqualTo(0)
+        assertThat(fragment.sharedElementReturn.targets.size).isEqualTo(0)
+    }
+
+    private fun assertForwardTransition(start: TransitionFragment, end: TransitionFragment) {
+        start.waitForTransition()
+        end.waitForTransition()
+        assertThat(start.enterTransition.targets.size).isEqualTo(0)
+        assertThat(end.enterTransition.targets.size).isEqualTo(1)
+
+        assertThat(start.reenterTransition.targets.size).isEqualTo(0)
+        assertThat(end.reenterTransition.targets.size).isEqualTo(0)
+
+        assertThat(start.returnTransition.targets.size).isEqualTo(0)
+        assertThat(end.returnTransition.targets.size).isEqualTo(0)
+
+        assertThat(start.exitTransition.targets.size).isEqualTo(1)
+        assertThat(end.exitTransition.targets.size).isEqualTo(0)
+
+        assertThat(start.sharedElementEnter.targets.size).isEqualTo(0)
+        assertThat(end.sharedElementEnter.targets.size).isEqualTo(2)
+
+        assertThat(start.sharedElementReturn.targets.size).isEqualTo(0)
+        assertThat(end.sharedElementReturn.targets.size).isEqualTo(0)
+
+        val blue = end.requireView().findViewById<View>(R.id.blueSquare)
+        assertThat(end.sharedElementEnter.targets.contains(blue)).isTrue()
+        assertThat(end.sharedElementEnter.targets[0].transitionName).isEqualTo("blueSquare")
+        assertThat(end.sharedElementEnter.targets[1].transitionName).isEqualTo("blueSquare")
+
+        assertNoTargets(start)
+        assertNoTargets(end)
+
+        clearTargets(start)
+        clearTargets(end)
+    }
+
+    private fun assertBackTransition(start: TransitionFragment, end: TransitionFragment) {
+        start.waitForTransition()
+        end.waitForTransition()
+        assertThat(end.reenterTransition.targets.size).isEqualTo(1)
+        assertThat(start.reenterTransition.targets.size).isEqualTo(0)
+
+        assertThat(end.returnTransition.targets.size).isEqualTo(0)
+        assertThat(start.returnTransition.targets.size).isEqualTo(1)
+
+        assertThat(start.enterTransition.targets.size).isEqualTo(0)
+        assertThat(end.enterTransition.targets.size).isEqualTo(0)
+
+        assertThat(start.exitTransition.targets.size).isEqualTo(0)
+        assertThat(end.exitTransition.targets.size).isEqualTo(0)
+
+        assertThat(start.sharedElementEnter.targets.size).isEqualTo(0)
+        assertThat(end.sharedElementEnter.targets.size).isEqualTo(0)
+
+        assertThat(start.sharedElementReturn.targets.size).isEqualTo(2)
+        assertThat(end.sharedElementReturn.targets.size).isEqualTo(0)
+
+        val blue = end.requireView().findViewById<View>(R.id.blueSquare)
+        assertThat(start.sharedElementReturn.targets.contains(blue)).isTrue()
+        assertThat(start.sharedElementReturn.targets[0].transitionName).isEqualTo("blueSquare")
+        assertThat(start.sharedElementReturn.targets[1].transitionName).isEqualTo("blueSquare")
+
+        assertNoTargets(end)
+        assertNoTargets(start)
+
+        clearTargets(start)
+        clearTargets(end)
+    }
+
+    private fun assertNoTargets(fragment: TransitionFragment) {
+        assertThat(fragment.enterTransition.getTargets().isEmpty()).isTrue()
+        assertThat(fragment.reenterTransition.getTargets().isEmpty()).isTrue()
+        assertThat(fragment.exitTransition.getTargets().isEmpty()).isTrue()
+        assertThat(fragment.returnTransition.getTargets().isEmpty()).isTrue()
+        assertThat(fragment.sharedElementEnter.getTargets().isEmpty()).isTrue()
+        assertThat(fragment.sharedElementReturn.getTargets().isEmpty()).isTrue()
+    }
+
+    open class PostponedFragment1 : TransitionFragment(R.layout.scene1) {
+        override fun onCreateView(
+            inflater: LayoutInflater,
+            container: ViewGroup?,
+            savedInstanceState: Bundle?
+        ) = super.onCreateView(inflater, container, savedInstanceState).also {
+            postponeEnterTransition()
+        }
+    }
+
+    class PostponedFragment2 : TransitionFragment(R.layout.scene2) {
+        override fun onCreateView(
+            inflater: LayoutInflater,
+            container: ViewGroup?,
+            savedInstanceState: Bundle?
+        ) = super.onCreateView(inflater, container, savedInstanceState).also {
+            postponeEnterTransition()
+        }
+    }
+
+    class CommitNowFragment : PostponedFragment1() {
+        override fun onResume() {
+            super.onResume()
+            // This should throw because this happens during the execution
+            fragmentManager!!.beginTransaction()
+                .add(R.id.fragmentContainer, PostponedFragment1())
+                .commitNow()
+        }
+    }
+}
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/TrackingTransition.java b/fragment/src/androidTest/java/androidx/fragment/app/TrackingTransition.java
deleted file mode 100644
index 7b2a494..0000000
--- a/fragment/src/androidTest/java/androidx/fragment/app/TrackingTransition.java
+++ /dev/null
@@ -1,91 +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.animation.Animator;
-import android.graphics.Rect;
-import android.transition.Transition;
-import android.transition.TransitionValues;
-import android.view.View;
-import android.view.ViewGroup;
-
-import java.util.ArrayList;
-
-/**
- * A transition that tracks which targets are applied to it.
- * It will assume any target that it applies to will have differences
- * between the start and end state, regardless of the differences
- * that actually exist. In other words, it doesn't actually check
- * any size or position differences or any other property of the view.
- * It just records the difference.
- * <p>
- * Both start and end value Views are recorded, but no actual animation
- * is created.
- */
-class TrackingTransition extends Transition implements TargetTracking {
-    public final ArrayList<View> targets = new ArrayList<>();
-    private final Rect[] mEpicenter = new Rect[1];
-    private static final String PROP = "tracking:prop";
-    private static final String[] PROPS = { PROP };
-
-    @Override
-    public String[] getTransitionProperties() {
-        return PROPS;
-    }
-
-    @Override
-    public void captureStartValues(TransitionValues transitionValues) {
-        transitionValues.values.put(PROP, 0);
-    }
-
-    @Override
-    public void captureEndValues(TransitionValues transitionValues) {
-        transitionValues.values.put(PROP, 1);
-    }
-
-    @Override
-    public Animator createAnimator(ViewGroup sceneRoot, TransitionValues startValues,
-            TransitionValues endValues) {
-        if (startValues != null) {
-            targets.add(startValues.view);
-        }
-        if (endValues != null) {
-            targets.add(endValues.view);
-        }
-        Rect epicenter = getEpicenter();
-        if (epicenter != null) {
-            mEpicenter[0] = new Rect(epicenter);
-        } else {
-            mEpicenter[0] = null;
-        }
-        return null;
-    }
-
-    @Override
-    public ArrayList<View> getTrackedTargets() {
-        return targets;
-    }
-
-    @Override
-    public void clearTargets() {
-        targets.clear();
-    }
-
-    @Override
-    public Rect getCapturedEpicenter() {
-        return mEpicenter[0];
-    }
-}
diff --git a/fragment/src/androidTest/java/androidx/fragment/app/TrackingTransition.kt b/fragment/src/androidTest/java/androidx/fragment/app/TrackingTransition.kt
new file mode 100644
index 0000000..e45a428
--- /dev/null
+++ b/fragment/src/androidTest/java/androidx/fragment/app/TrackingTransition.kt
@@ -0,0 +1,86 @@
+/*
+ * 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.graphics.Rect
+import android.transition.Transition
+import android.transition.TransitionValues
+import android.view.View
+import android.view.ViewGroup
+
+import java.util.ArrayList
+
+/**
+ * A transition that tracks which targets are applied to it.
+ * It will assume any target that it applies to will have differences
+ * between the start and end state, regardless of the differences
+ * that actually exist. In other words, it doesn't actually check
+ * any size or position differences or any other property of the view.
+ * It just records the difference.
+ *
+ *
+ * Both start and end value Views are recorded, but no actual animation
+ * is created.
+ */
+class TrackingTransition : Transition(), TargetTracking {
+    val targets = ArrayList<View>()
+    private val baseEpicenter = Rect()
+
+    override fun getTransitionProperties(): Array<String> {
+        return PROPS
+    }
+
+    override fun captureStartValues(transitionValues: TransitionValues) {
+        transitionValues.values[PROP] = 0
+    }
+
+    override fun captureEndValues(transitionValues: TransitionValues) {
+        transitionValues.values[PROP] = 1
+    }
+
+    override fun createAnimator(
+        sceneRoot: ViewGroup,
+        startValues: TransitionValues?,
+        endValues: TransitionValues?
+    ) = null.also {
+        if (startValues != null) {
+            targets.add(startValues.view)
+        }
+        if (endValues != null) {
+            targets.add(endValues.view)
+        }
+        if (epicenter != null) {
+            baseEpicenter.set(Rect(epicenter))
+        }
+    }
+
+    override fun getTrackedTargets(): ArrayList<View> {
+        return targets
+    }
+
+    override fun clearTargets() {
+        targets.clear()
+    }
+
+    override fun getCapturedEpicenter(): Rect? {
+        return baseEpicenter
+    }
+
+    companion object {
+        private val PROP = "tracking:prop"
+        private val PROPS = arrayOf(PROP)
+    }
+}
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/androidTest/java/androidx/fragment/app/test/OuterPackagePrivateFragment.java b/fragment/src/androidTest/java/androidx/fragment/app/test/OuterPackagePrivateFragment.java
new file mode 100644
index 0000000..63092bc
--- /dev/null
+++ b/fragment/src/androidTest/java/androidx/fragment/app/test/OuterPackagePrivateFragment.java
@@ -0,0 +1,29 @@
+/*
+ * 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.test;
+
+import androidx.fragment.app.Fragment;
+
+/**
+ * A class with a static PackagePrivate Fragment.
+ * Used for testing FragmentTransactionTest.
+ *
+ * Must be java, the concept of a static PackagePrivate class does not exist in Kotlin.
+ */
+public class OuterPackagePrivateFragment {
+    static class PackagePrivateFragment extends Fragment {}
+}
diff --git a/fragment/src/main/java/androidx/fragment/app/BackStackRecord.java b/fragment/src/main/java/androidx/fragment/app/BackStackRecord.java
index a9feb93..618c387 100644
--- a/fragment/src/main/java/androidx/fragment/app/BackStackRecord.java
+++ b/fragment/src/main/java/androidx/fragment/app/BackStackRecord.java
@@ -422,7 +422,7 @@
                         + name + "' has already been added to the transaction.");
             } else if (mSharedElementSourceNames.contains(transitionName)) {
                 throw new IllegalArgumentException("A shared element with the source name '"
-                        + transitionName + " has already been added to the transaction.");
+                        + transitionName + "' has already been added to the transaction.");
             }
 
             mSharedElementSourceNames.add(transitionName);
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/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/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 c39f546..4a18f8d 100644
--- a/jetifier/jetifier/core/src/main/resources/default.config
+++ b/jetifier/jetifier/core/src/main/resources/default.config
@@ -1002,303 +1002,303 @@
     ],
     "packageMap": [
         {
-            "from" = "android/support/exifinterface",
-            "to" = "androidx/exifinterface"
+            "from" : "android/support/exifinterface",
+            "to" : "androidx/exifinterface"
         },
         {
-            "from" = "android/support/heifwriter",
-            "to" = "androidx/heifwriter"
+            "from" : "android/support/heifwriter",
+            "to" : "androidx/heifwriter"
         },
         {
-            "from" = "android/support/graphics/drawable",
-            "to" = "androidx/vectordrawable"
+            "from" : "android/support/graphics/drawable",
+            "to" : "androidx/vectordrawable"
         },
         {
-            "from" = "android/support/graphics/drawable/animated",
-            "to" = "androidx/vectordrawable"
+            "from" : "android/support/graphics/drawable/animated",
+            "to" : "androidx/vectordrawable"
         },
         {
-            "from" = "android/support/media/tv",
-            "to" = "androidx/tvprovider"
+            "from" : "android/support/media/tv",
+            "to" : "androidx/tvprovider"
         },
         {
-            "from" = "android/support/textclassifier",
-            "to" = "androidx/textclassifier"
+            "from" : "android/support/textclassifier",
+            "to" : "androidx/textclassifier"
         },
         {
-            "from" = "androidx/recyclerview/selection",
-            "to" = "androidx/recyclerview/selection"},
+            "from" : "androidx/recyclerview/selection",
+            "to" : "androidx/recyclerview/selection"},
         {
-            "from" = "android/support/v4",
-            "to" = "androidx/legacy/v4"
+            "from" : "android/support/v4",
+            "to" : "androidx/legacy/v4"
         },
         {
-            "from" = "android/support/print",
-            "to" = "androidx/print"
+            "from" : "android/support/print",
+            "to" : "androidx/print"
         },
         {
-            "from" = "android/support/documentfile",
-            "to" = "androidx/documentfile"
+            "from" : "android/support/documentfile",
+            "to" : "androidx/documentfile"
         },
         {
-            "from" = "android/support/coordinatorlayout",
-            "to" = "androidx/coordinatorlayout"
+            "from" : "android/support/coordinatorlayout",
+            "to" : "androidx/coordinatorlayout"
         },
         {
-            "from" = "android/support/swiperefreshlayout",
-            "to" = "androidx/swiperefreshlayout"
+            "from" : "android/support/swiperefreshlayout",
+            "to" : "androidx/swiperefreshlayout"
         },
         {
-            "from" = "android/support/slidingpanelayout",
-            "to" = "androidx/slidingpanelayout"
+            "from" : "android/support/slidingpanelayout",
+            "to" : "androidx/slidingpanelayout"
         },
         {
-            "from" = "android/support/asynclayoutinflater",
-            "to" = "androidx/asynclayoutinflater"
+            "from" : "android/support/asynclayoutinflater",
+            "to" : "androidx/asynclayoutinflater"
         },
         {
-            "from" = "android/support/interpolator",
-            "to" = "androidx/interpolator"
+            "from" : "android/support/interpolator",
+            "to" : "androidx/interpolator"
         },
         {
-            "from" = "android/support/v7/palette",
-            "to" = "androidx/palette"
+            "from" : "android/support/v7/palette",
+            "to" : "androidx/palette"
         },
         {
-            "from" = "android/support/v7/cardview",
-            "to" = "androidx/cardview"
+            "from" : "android/support/v7/cardview",
+            "to" : "androidx/cardview"
         },
         {
-            "from" = "android/support/customview",
-            "to" = "androidx/customview"
+            "from" : "android/support/customview",
+            "to" : "androidx/customview"
         },
         {
-            "from" = "android/support/loader",
-            "to" = "androidx/loader"
+            "from" : "android/support/loader",
+            "to" : "androidx/loader"
         },
         {
-            "from" = "android/support/cursoradapter",
-            "to" = "androidx/cursoradapter"
+            "from" : "android/support/cursoradapter",
+            "to" : "androidx/cursoradapter"
         },
         {
-            "from" = "android/support/v7/mediarouter",
-            "to" = "androidx/mediarouter"
+            "from" : "android/support/v7/mediarouter",
+            "to" : "androidx/mediarouter"
         },
         {
-            "from" = "android/support/v7/appcompat",
-            "to" = "androidx/appcompat"
+            "from" : "android/support/v7/appcompat",
+            "to" : "androidx/appcompat"
         },
         {
-            "from" = "android/support/v7/recyclerview",
-            "to" = "androidx/recyclerview"
+            "from" : "android/support/v7/recyclerview",
+            "to" : "androidx/recyclerview"
         },
         {
-            "from" = "android/support/v7/viewpager",
-            "to" = "androidx/viewpager"
+            "from" : "android/support/v7/viewpager",
+            "to" : "androidx/viewpager"
         },
         {
-            "from" = "android/support/percent",
-            "to" = "androidx/percentlayout"
+            "from" : "android/support/percent",
+            "to" : "androidx/percentlayout"
         },
         {
-            "from" = "android/support/v7/gridlayout",
-            "to" = "androidx/gridlayout"
+            "from" : "android/support/v7/gridlayout",
+            "to" : "androidx/gridlayout"
         },
         {
-            "from" = "android/support/v13",
-            "to" = "androidx/legacy/v13"
+            "from" : "android/support/v13",
+            "to" : "androidx/legacy/v13"
         },
         {
-            "from" = "android/support/v7/preference",
-            "to" = "androidx/preference"
+            "from" : "android/support/v7/preference",
+            "to" : "androidx/preference"
         },
         {
-            "from" = "android/support/v14/preference",
-            "to" = "androidx/legacy/preference"
+            "from" : "android/support/v14/preference",
+            "to" : "androidx/legacy/preference"
         },
         {
-            "from" = "android/support/v17/leanback",
-            "to" = "androidx/leanback"
+            "from" : "android/support/v17/leanback",
+            "to" : "androidx/leanback"
         },
         {
-            "from" = "android/support/v17/preference",
-            "to" = "androidx/leanback/preference"
+            "from" : "android/support/v17/preference",
+            "to" : "androidx/leanback/preference"
         },
         {
-            "from" = "android/support/compat",
-            "to" = "androidx/core"
+            "from" : "android/support/compat",
+            "to" : "androidx/core"
         },
         {
-            "from" = "android/support/mediacompat",
-            "to" = "androidx/media"
+            "from" : "android/support/mediacompat",
+            "to" : "androidx/media"
         },
         {
-            "from" = "android/support/media2",
-            "to" = "androidx/media2"
+            "from" : "android/support/media2",
+            "to" : "androidx/media2"
         },
         {
-            "from" = "androidx/media2/exoplayer/external",
-            "to" = "androidx/media2/exoplayer/external"
+            "from" : "androidx/media2/exoplayer/external",
+            "to" : "androidx/media2/exoplayer/external"
         },
         {
-            "from" = "android/support/fragment",
-            "to" = "androidx/fragment"
+            "from" : "android/support/fragment",
+            "to" : "androidx/fragment"
         },
         {
-            "from" = "android/support/coreutils",
-            "to" = "androidx/legacy/coreutils"
+            "from" : "android/support/coreutils",
+            "to" : "androidx/legacy/coreutils"
         },
         {
-            "from" = "android/support/dynamicanimation",
-            "to" = "androidx/dynamicanimation"
+            "from" : "android/support/dynamicanimation",
+            "to" : "androidx/dynamicanimation"
         },
         {
-            "from" = "android/support/customtabs",
-            "to" = "androidx/browser"
+            "from" : "android/support/customtabs",
+            "to" : "androidx/browser"
         },
         {
-            "from" = "android/support/coreui",
-            "to" = "androidx/legacy/coreui"
+            "from" : "android/support/coreui",
+            "to" : "androidx/legacy/coreui"
         },
         {
-            "from" = "android/support/content",
-            "to" = "androidx/contentpager"
+            "from" : "android/support/content",
+            "to" : "androidx/contentpager"
         },
         {
-            "from" = "android/support/transition",
-            "to" = "androidx/transition"
+            "from" : "android/support/transition",
+            "to" : "androidx/transition"
         },
         {
-            "from" = "android/support/recommendation",
-            "to" = "androidx/recommendation"
+            "from" : "android/support/recommendation",
+            "to" : "androidx/recommendation"
         },
         {
-            "from" = "android/support/drawerlayout",
-            "to" = "androidx/drawerlayout"
+            "from" : "android/support/drawerlayout",
+            "to" : "androidx/drawerlayout"
         },
         {
-            "from" = "android/support/wear",
-            "to" = "androidx/wear"
+            "from" : "android/support/wear",
+            "to" : "androidx/wear"
         },
         {
-            "from" = "android/support/design",
-            "to" = "com/google/android/material"
+            "from" : "android/support/design",
+            "to" : "com/google/android/material"
         },
         {
-            "from" = "android/support/text/emoji/appcompat",
-            "to" = "androidx/emoji/appcompat"
+            "from" : "android/support/text/emoji/appcompat",
+            "to" : "androidx/emoji/appcompat"
         },
         {
-            "from" = "android/support/text/emoji/bundled",
-            "to" = "androidx/emoji/bundled"
+            "from" : "android/support/text/emoji/bundled",
+            "to" : "androidx/emoji/bundled"
         },
         {
-            "from" = "android/support/text/emoji",
-            "to" = "androidx/emoji"
+            "from" : "android/support/text/emoji",
+            "to" : "androidx/emoji"
         },
         {
-            "from" = "androidx/text/emoji/bundled",
-            "to" = "androidx/text/emoji/bundled"
+            "from" : "androidx/text/emoji/bundled",
+            "to" : "androidx/text/emoji/bundled"
         },
         {
-            "from" = "android/support/localbroadcastmanager",
-            "to" = "androidx/localbroadcastmanager"
+            "from" : "android/support/localbroadcastmanager",
+            "to" : "androidx/localbroadcastmanager"
         },
         {
-            "from" = "androidx/text/emoji/bundled",
-            "to" = "androidx/text/emoji/bundled"
+            "from" : "androidx/text/emoji/bundled",
+            "to" : "androidx/text/emoji/bundled"
         },
         {
-            "from" = "androidx/webkit",
-            "to" = "androidx/webkit"
+            "from" : "androidx/webkit",
+            "to" : "androidx/webkit"
         },
         {
-            "from" = "androidx/versionedparcelable",
-            "to" = "androidx/versionedparcelable"
+            "from" : "androidx/versionedparcelable",
+            "to" : "androidx/versionedparcelable"
         },
         {
-            "from" = "androidx/slice/view",
-            "to" = "androidx/slice/view"
+            "from" : "androidx/slice/view",
+            "to" : "androidx/slice/view"
         },
         {
-            "from" = "androidx/slice/core",
-            "to" = "androidx/slice/core"
+            "from" : "androidx/slice/core",
+            "to" : "androidx/slice/core"
         },
         {
-            "from" = "androidx/slice/builders",
-            "to" = "androidx/slice/builders"
+            "from" : "androidx/slice/builders",
+            "to" : "androidx/slice/builders"
         },
         {
-            "from" = "android/arch/paging/runtime",
-            "to" = "androidx/paging/runtime"
+            "from" : "android/arch/paging/runtime",
+            "to" : "androidx/paging/runtime"
         },
         {
-            "from" = "android/arch/core/testing",
-            "to" = "androidx/arch/core/testing"
+            "from" : "android/arch/core/testing",
+            "to" : "androidx/arch/core/testing"
         },
         {
-            "from" = "android/arch/core",
-            "to" = "androidx/arch/core"
+            "from" : "android/arch/core",
+            "to" : "androidx/arch/core"
         },
         {
-            "from" = "android/arch/persistence/db/framework",
-            "to" = "androidx/sqlite/db/framework"
+            "from" : "android/arch/persistence/db/framework",
+            "to" : "androidx/sqlite/db/framework"
         },
         {
-            "from" = "android/arch/persistence/db",
-            "to" = "androidx/sqlite/db"
+            "from" : "android/arch/persistence/db",
+            "to" : "androidx/sqlite/db"
         },
         {
-            "from" = "android/arch/persistence/room/rxjava2",
-            "to" = "androidx/room/rxjava2"
+            "from" : "android/arch/persistence/room/rxjava2",
+            "to" : "androidx/room/rxjava2"
         },
         {
-            "from" = "android/arch/persistence/room/guava",
-            "to" = "androidx/room/guava"
+            "from" : "android/arch/persistence/room/guava",
+            "to" : "androidx/room/guava"
         },
         {
-            "from" = "android/arch/persistence/room/testing",
-            "to" = "androidx/room/testing"
+            "from" : "android/arch/persistence/room/testing",
+            "to" : "androidx/room/testing"
         },
         {
-            "from" = "android/arch/persistence/room",
-            "to" = "androidx/room"
+            "from" : "android/arch/persistence/room",
+            "to" : "androidx/room"
         },
         {
-            "from" = "android/arch/lifecycle/extensions",
-            "to" = "androidx/lifecycle/extensions"
+            "from" : "android/arch/lifecycle/extensions",
+            "to" : "androidx/lifecycle/extensions"
         },
         {
-            "from" = "android/arch/lifecycle/livedata/core",
-            "to" = "androidx/lifecycle/livedata/core"
+            "from" : "android/arch/lifecycle/livedata/core",
+            "to" : "androidx/lifecycle/livedata/core"
         },
         {
-            "from" = "android/arch/lifecycle",
-            "to" = "androidx/lifecycle"
+            "from" : "android/arch/lifecycle",
+            "to" : "androidx/lifecycle"
         },
         {
-            "from" = "android/arch/lifecycle/viewmodel",
-            "to" = "androidx/lifecycle/viewmodel"
+            "from" : "android/arch/lifecycle/viewmodel",
+            "to" : "androidx/lifecycle/viewmodel"
         },
         {
-            "from" = "android/arch/lifecycle/livedata",
-            "to" = "androidx/lifecycle/livedata"
+            "from" : "android/arch/lifecycle/livedata",
+            "to" : "androidx/lifecycle/livedata"
         },
         {
-            "from" = "android/arch/lifecycle/reactivestreams",
-            "to" = "androidx/lifecycle/reactivestreams"
+            "from" : "android/arch/lifecycle/reactivestreams",
+            "to" : "androidx/lifecycle/reactivestreams"
         },
         {
-            "from" = "android/support/multidex/instrumentation",
-            "to" = "androidx/multidex/instrumentation"
+            "from" : "android/support/multidex/instrumentation",
+            "to" : "androidx/multidex/instrumentation"
         },
         {
-            "from" = "android/support/multidex",
-            "to" = "androidx/multidex"
+            "from" : "android/support/multidex",
+            "to" : "androidx/multidex"
         },
         {
-            "from" = "android/support/biometric",
-            "to" = "androidx/biometric"
+            "from" : "android/support/biometric",
+            "to" : "androidx/biometric"
         }
     ],
     "pomRules": [
@@ -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/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/lifecycle/runtime/eap/README.md b/lifecycle/runtime/eap/README.md
new file mode 100644
index 0000000..1f4c9e7
--- /dev/null
+++ b/lifecycle/runtime/eap/README.md
@@ -0,0 +1,2 @@
+This is a temporary project for the coroutines EAP.
+Contents of this folder will be merged into ktx.
diff --git a/lifecycle/runtime/eap/api/1.0.0-alpha01.txt b/lifecycle/runtime/eap/api/1.0.0-alpha01.txt
new file mode 100644
index 0000000..09e9ab4
--- /dev/null
+++ b/lifecycle/runtime/eap/api/1.0.0-alpha01.txt
@@ -0,0 +1,16 @@
+// Signature format: 3.0
+package androidx.lifecycle {
+
+  public final class PausingDispatcherKt {
+    ctor public PausingDispatcherKt();
+    method public static suspend <T> Object? whenCreated(androidx.lifecycle.LifecycleOwner, kotlin.jvm.functions.Function2<? super kotlinx.coroutines.CoroutineScope,? super kotlin.coroutines.experimental.Continuation<? super T>,?> block, kotlin.coroutines.experimental.Continuation<? super T> p);
+    method public static suspend <T> Object? whenCreated(androidx.lifecycle.Lifecycle, kotlin.jvm.functions.Function2<? super kotlinx.coroutines.CoroutineScope,? super kotlin.coroutines.experimental.Continuation<? super T>,?> block, kotlin.coroutines.experimental.Continuation<? super T> p);
+    method public static suspend <T> Object? whenResumed(androidx.lifecycle.LifecycleOwner, kotlin.jvm.functions.Function2<? super kotlinx.coroutines.CoroutineScope,? super kotlin.coroutines.experimental.Continuation<? super T>,?> block, kotlin.coroutines.experimental.Continuation<? super T> p);
+    method public static suspend <T> Object? whenResumed(androidx.lifecycle.Lifecycle, kotlin.jvm.functions.Function2<? super kotlinx.coroutines.CoroutineScope,? super kotlin.coroutines.experimental.Continuation<? super T>,?> block, kotlin.coroutines.experimental.Continuation<? super T> p);
+    method public static suspend <T> Object? whenStarted(androidx.lifecycle.LifecycleOwner, kotlin.jvm.functions.Function2<? super kotlinx.coroutines.CoroutineScope,? super kotlin.coroutines.experimental.Continuation<? super T>,?> block, kotlin.coroutines.experimental.Continuation<? super T> p);
+    method public static suspend <T> Object? whenStarted(androidx.lifecycle.Lifecycle, kotlin.jvm.functions.Function2<? super kotlinx.coroutines.CoroutineScope,? super kotlin.coroutines.experimental.Continuation<? super T>,?> block, kotlin.coroutines.experimental.Continuation<? super T> p);
+    method public static suspend <T> Object? whenStateAtLeast(androidx.lifecycle.Lifecycle, androidx.lifecycle.Lifecycle.State minState, kotlin.jvm.functions.Function2<? super kotlinx.coroutines.CoroutineScope,? super kotlin.coroutines.experimental.Continuation<? super T>,?> block, kotlin.coroutines.experimental.Continuation<? super error.NonExistentClass> p);
+  }
+
+}
+
diff --git a/lifecycle/runtime/eap/api/current.txt b/lifecycle/runtime/eap/api/current.txt
new file mode 100644
index 0000000..09e9ab4
--- /dev/null
+++ b/lifecycle/runtime/eap/api/current.txt
@@ -0,0 +1,16 @@
+// Signature format: 3.0
+package androidx.lifecycle {
+
+  public final class PausingDispatcherKt {
+    ctor public PausingDispatcherKt();
+    method public static suspend <T> Object? whenCreated(androidx.lifecycle.LifecycleOwner, kotlin.jvm.functions.Function2<? super kotlinx.coroutines.CoroutineScope,? super kotlin.coroutines.experimental.Continuation<? super T>,?> block, kotlin.coroutines.experimental.Continuation<? super T> p);
+    method public static suspend <T> Object? whenCreated(androidx.lifecycle.Lifecycle, kotlin.jvm.functions.Function2<? super kotlinx.coroutines.CoroutineScope,? super kotlin.coroutines.experimental.Continuation<? super T>,?> block, kotlin.coroutines.experimental.Continuation<? super T> p);
+    method public static suspend <T> Object? whenResumed(androidx.lifecycle.LifecycleOwner, kotlin.jvm.functions.Function2<? super kotlinx.coroutines.CoroutineScope,? super kotlin.coroutines.experimental.Continuation<? super T>,?> block, kotlin.coroutines.experimental.Continuation<? super T> p);
+    method public static suspend <T> Object? whenResumed(androidx.lifecycle.Lifecycle, kotlin.jvm.functions.Function2<? super kotlinx.coroutines.CoroutineScope,? super kotlin.coroutines.experimental.Continuation<? super T>,?> block, kotlin.coroutines.experimental.Continuation<? super T> p);
+    method public static suspend <T> Object? whenStarted(androidx.lifecycle.LifecycleOwner, kotlin.jvm.functions.Function2<? super kotlinx.coroutines.CoroutineScope,? super kotlin.coroutines.experimental.Continuation<? super T>,?> block, kotlin.coroutines.experimental.Continuation<? super T> p);
+    method public static suspend <T> Object? whenStarted(androidx.lifecycle.Lifecycle, kotlin.jvm.functions.Function2<? super kotlinx.coroutines.CoroutineScope,? super kotlin.coroutines.experimental.Continuation<? super T>,?> block, kotlin.coroutines.experimental.Continuation<? super T> p);
+    method public static suspend <T> Object? whenStateAtLeast(androidx.lifecycle.Lifecycle, androidx.lifecycle.Lifecycle.State minState, kotlin.jvm.functions.Function2<? super kotlinx.coroutines.CoroutineScope,? super kotlin.coroutines.experimental.Continuation<? super T>,?> block, kotlin.coroutines.experimental.Continuation<? super error.NonExistentClass> p);
+  }
+
+}
+
diff --git a/lifecycle/runtime/eap/api/res-1.0.0-alpha01.txt b/lifecycle/runtime/eap/api/res-1.0.0-alpha01.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/lifecycle/runtime/eap/api/res-1.0.0-alpha01.txt
diff --git a/lifecycle/runtime/eap/api/restricted_1.0.0-alpha01.txt b/lifecycle/runtime/eap/api/restricted_1.0.0-alpha01.txt
new file mode 100644
index 0000000..da4f6cc
--- /dev/null
+++ b/lifecycle/runtime/eap/api/restricted_1.0.0-alpha01.txt
@@ -0,0 +1 @@
+// Signature format: 3.0
diff --git a/lifecycle/runtime/eap/api/restricted_current.txt b/lifecycle/runtime/eap/api/restricted_current.txt
new file mode 100644
index 0000000..da4f6cc
--- /dev/null
+++ b/lifecycle/runtime/eap/api/restricted_current.txt
@@ -0,0 +1 @@
+// Signature format: 3.0
diff --git a/lifecycle/runtime/eap/build.gradle b/lifecycle/runtime/eap/build.gradle
new file mode 100644
index 0000000..2ce5f78
--- /dev/null
+++ b/lifecycle/runtime/eap/build.gradle
@@ -0,0 +1,64 @@
+/*
+ * 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.
+ */
+
+import static androidx.build.dependencies.DependenciesKt.*
+import androidx.build.LibraryGroups
+import androidx.build.LibraryVersions
+
+plugins {
+    id("SupportAndroidLibraryPlugin")
+    id("org.jetbrains.kotlin.android")
+}
+
+android {
+    buildTypes {
+        debug {
+            testCoverageEnabled = false // Breaks Kotlin compiler.
+        }
+    }
+}
+
+dependencies {
+    api(project(":lifecycle:lifecycle-common-eap"))
+    api(project(":lifecycle:lifecycle-common"))
+    api(KOTLIN_STDLIB)
+    api(KOTLIN_COROUTINES)
+    implementation(SUPPORT_ANNOTATIONS)
+
+    testImplementation(JUNIT)
+    testImplementation(TEST_EXT_JUNIT)
+    testImplementation(TEST_CORE)
+    testImplementation(TEST_RUNNER)
+    testImplementation(TRUTH)
+
+    androidTestImplementation project(':lifecycle:lifecycle-runtime')
+    androidTestImplementation(TRUTH)
+    androidTestImplementation(TEST_EXT_JUNIT)
+    androidTestImplementation(TEST_CORE)
+    androidTestImplementation(TEST_RUNNER)
+    androidTestImplementation(KOTLIN_COROUTINES_TEST)
+
+}
+
+supportLibrary {
+    name = "Android Lifecycle Kotlin Extensions"
+    publish = false
+    mavenVersion = LibraryVersions.LIFECYCLES_COROUTINES
+    mavenGroup = LibraryGroups.LIFECYCLE
+    inceptionYear = "2019"
+    description = "Kotlin extensions for 'lifecycle' artifact"
+    useMetalava = true
+}
\ No newline at end of file
diff --git a/lifecycle/runtime/eap/src/androidTest/java/androidx/lifecycle/Expectations.kt b/lifecycle/runtime/eap/src/androidTest/java/androidx/lifecycle/Expectations.kt
new file mode 100644
index 0000000..395a840
--- /dev/null
+++ b/lifecycle/runtime/eap/src/androidTest/java/androidx/lifecycle/Expectations.kt
@@ -0,0 +1,42 @@
+/*
+ * 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.lifecycle
+
+import com.google.common.truth.Truth
+import java.util.concurrent.atomic.AtomicInteger
+
+/**
+ * Partial copy from
+ * https://github.com/Kotlin/kotlinx.coroutines/blob/master/core/kotlinx-coroutines-core/test/TestBase.kt
+ * to track execution order.
+ */
+class Expectations {
+    private var counter = AtomicInteger(0)
+
+    fun expect(expected: Int) {
+        val order = counter.incrementAndGet()
+        Truth.assertThat(order).isEqualTo(expected)
+    }
+
+    fun expectUnreached() {
+        throw AssertionError("should've not reached here")
+    }
+
+    fun expectTotal(total: Int) {
+        Truth.assertThat(counter.get()).isEqualTo(total)
+    }
+}
\ No newline at end of file
diff --git a/lifecycle/runtime/eap/src/androidTest/java/androidx/lifecycle/FakeLifecycleOwner.kt b/lifecycle/runtime/eap/src/androidTest/java/androidx/lifecycle/FakeLifecycleOwner.kt
new file mode 100644
index 0000000..cd93f87
--- /dev/null
+++ b/lifecycle/runtime/eap/src/androidTest/java/androidx/lifecycle/FakeLifecycleOwner.kt
@@ -0,0 +1,53 @@
+/*
+ * 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.lifecycle
+
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.withContext
+import kotlinx.coroutines.withTimeoutOrNull
+
+class FakeLifecycleOwner(initialState: Lifecycle.State? = null) : LifecycleOwner {
+    private val registry: LifecycleRegistry = LifecycleRegistry(this)
+
+    init {
+        initialState?.let {
+            setState(it)
+        }
+    }
+
+    override fun getLifecycle(): Lifecycle = registry
+
+    fun setState(state: Lifecycle.State) {
+        registry.markState(state)
+    }
+
+    suspend fun awaitExactObserverCount(count: Int, timeout: Long = 1000L): Boolean =
+    // just give job some time to start
+        withTimeoutOrNull(timeout) {
+            while (getObserverCount(count) != count) {
+                delay(50)
+            }
+            true
+        } ?: false
+
+    private suspend fun getObserverCount(count: Int): Int {
+        return withContext(Dispatchers.Main) {
+            registry.observerCount
+        }
+    }
+}
\ No newline at end of file
diff --git a/lifecycle/runtime/eap/src/androidTest/java/androidx/lifecycle/PausingDispatcherTest.kt b/lifecycle/runtime/eap/src/androidTest/java/androidx/lifecycle/PausingDispatcherTest.kt
new file mode 100644
index 0000000..ed241df
--- /dev/null
+++ b/lifecycle/runtime/eap/src/androidTest/java/androidx/lifecycle/PausingDispatcherTest.kt
@@ -0,0 +1,511 @@
+/*
+ * 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.lifecycle
+
+import android.util.Log
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.CompletableDeferred
+import kotlinx.coroutines.CoroutineExceptionHandler
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.InternalCoroutinesApi
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.asCoroutineDispatcher
+import kotlinx.coroutines.async
+import kotlinx.coroutines.cancelAndJoin
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.isActive
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.test.resetMain
+import kotlinx.coroutines.test.setMain
+import kotlinx.coroutines.withContext
+import kotlinx.coroutines.withTimeoutOrNull
+import kotlinx.coroutines.yield
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import java.util.concurrent.Executors
+import java.util.concurrent.TimeUnit
+
+@InternalCoroutinesApi
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class PausingDispatcherTest {
+    // TODO update custom dispatchers with the new TestCoroutineContext once available
+    // https://github.com/Kotlin/kotlinx.coroutines/pull/890
+    private val taskTracker = TaskTracker()
+    // track uncaught exceptions on the test scope
+    private val exceptionHandler = CoroutineExceptionHandler { _, throwable ->
+        testError = testError ?: throwable
+    }
+    // did we hit any error in the test scope
+    private var testError: Throwable? = null
+    // the executor for the testing scope that uses a different thread pool
+    private val testExecutor = TrackedExecutor(taskTracker, Executors.newFixedThreadPool(4))
+    private val testingScope =
+        CoroutineScope(testExecutor.asCoroutineDispatcher() + Job(null) + exceptionHandler)
+    private val owner = FakeLifecycleOwner(Lifecycle.State.RESUMED)
+    private val mainExecutor = TrackedExecutor(taskTracker, Executors.newSingleThreadExecutor())
+    // tracks execution order
+    private val expectations = Expectations()
+    private lateinit var mainThread: Thread
+
+    @ExperimentalCoroutinesApi
+    @Before
+    fun updateMainHandlerAndDispatcher() {
+        Dispatchers.setMain(mainExecutor.asCoroutineDispatcher())
+        runBlocking(Dispatchers.Main) {
+            // extract the main thread to field for assertions
+            mainThread = Thread.currentThread()
+        }
+    }
+
+    @ExperimentalCoroutinesApi
+    @After
+    fun clearHandlerAndDispatcher() {
+        waitTestingScopeChildren()
+        assertThat(mainExecutor.shutdown(10, TimeUnit.SECONDS)).isTrue()
+        assertThat(testExecutor.shutdown(10, TimeUnit.SECONDS)).isTrue()
+        assertThat(taskTracker.awaitIdle(10, TimeUnit.SECONDS)).isTrue()
+        Dispatchers.resetMain()
+    }
+
+    /**
+     * Ensure nothing in the testing scope is left behind w/o assertions
+     */
+    private fun waitTestingScopeChildren() {
+        runBlocking {
+            val testJob = testingScope.coroutineContext[Job]!!
+            do {
+                val children = testJob.children.toList()
+                assertThat(children.all {
+                    withTimeoutOrNull(10_000) {
+                        it.join()
+                        true
+                    } ?: false
+                })
+            } while (children.isNotEmpty())
+            assertThat(testJob.isActive)
+            assertThat(testError).isNull()
+        }
+    }
+
+    @Test
+    fun basic() {
+        val result = runBlocking {
+            owner.whenResumed {
+                assertThread()
+                3
+            }
+        }
+        assertThat(result).isEqualTo(3)
+    }
+
+    @Test
+    fun yieldTest() {
+        runBlocking(Dispatchers.Main) {
+            owner.whenResumed {
+                expectations.expect(1)
+                launch {
+                    expectations.expect(3)
+                    yield()
+                    expectations.expect(5)
+                }
+                expectations.expect(2)
+                launch {
+                    expectations.expect(4)
+                }
+            }
+            expectations.expectTotal(5)
+        }
+    }
+
+    @Test
+    fun runInsideMain() {
+        val res = runBlocking(Dispatchers.Main) {
+            owner.whenResumed {
+                2
+            }
+        }
+        assertThat(res).isEqualTo(2)
+    }
+
+    @Test
+    fun moveToAnotherDispatcher() {
+        val result = runBlocking {
+            owner.whenResumed {
+                assertThread()
+                val innerResult = withContext(testingScope.coroutineContext) {
+                    log("running inner")
+                    "hello"
+                }
+                assertThread()
+                log("received inner result $innerResult")
+                innerResult + innerResult
+            }
+        }
+        assertThat(result).isEqualTo("hellohello")
+    }
+
+    @Test
+    fun cancel() {
+        runBlocking {
+            val job = testingScope.launch {
+                owner.whenResumed {
+                    try {
+                        expectations.expect(1)
+                        delay(5000)
+                        expectations.expectUnreached()
+                    } finally {
+                        expectations.expect(2)
+                    }
+                }
+            }
+            drain()
+            expectations.expectTotal(1)
+            job.cancelAndJoin()
+            expectations.expectTotal(2)
+        }
+    }
+
+    @Test
+    fun throwException_thenRunAnother() {
+        runBlocking(testingScope.coroutineContext) {
+            try {
+                owner.whenResumed {
+                    assertThread()
+                    expectations.expect(1)
+                    throw IllegalArgumentException(" fail")
+                }
+                @Suppress("UNREACHABLE_CODE")
+                expectations.expectUnreached()
+            } catch (ignored: IllegalArgumentException) {
+            }
+            owner.whenResumed {
+                expectations.expect(2)
+            }
+        }
+        expectations.expectTotal(2)
+    }
+
+    @Test
+    fun innerThrowException() {
+        runBlocking {
+            val job = testingScope.launch {
+                val res = runCatching {
+                    owner.whenResumed {
+                        try {
+                            expectations.expect(1)
+                            withContext(testingScope.coroutineContext) {
+                                throw IllegalStateException("i fail")
+                            }
+                            @Suppress("UNREACHABLE_CODE")
+                            expectations.expectUnreached()
+                        } finally {
+                            expectations.expect(2)
+                        }
+                        @Suppress("UNREACHABLE_CODE")
+                        expectations.expectUnreached()
+                    }
+                }
+                assertThat(res.exceptionOrNull()).hasMessageThat().isEqualTo("i fail")
+            }
+            job.join()
+            expectations.expectTotal(2)
+        }
+    }
+
+    @Test
+    fun pause_thenResume() {
+        pause()
+        runBlocking {
+            val job = testingScope.launch {
+                owner.whenResumed {
+                    expectations.expect(1)
+                }
+            }
+            drain()
+            expectations.expectTotal(0)
+            resume()
+            job.join()
+            expectations.expectTotal(1)
+        }
+    }
+
+    @Test
+    fun pause_thenFinish() {
+        pause()
+        runBlocking {
+            val job = testingScope.launch {
+                owner.whenResumed {
+                    try {
+                        expectations.expectUnreached()
+                    } finally {
+                        expectations.expectUnreached()
+                    }
+                }
+            }
+            drain()
+            expectations.expectTotal(0)
+            finish()
+            job.join()
+            // never started so shouldn't run finally either
+            expectations.expectTotal(0)
+        }
+    }
+
+    @Test
+    fun finishWhileDelayed() {
+        runBlocking {
+            val job = testingScope.launch {
+                owner.whenResumed {
+                    try {
+                        expectations.expect(1)
+                        delay(100000)
+                        expectations.expectUnreached()
+                    } finally {
+                        expectations.expect(2)
+                        assertThat(isActive).isFalse()
+                    }
+                }
+            }
+            drain()
+            expectations.expectTotal(1)
+            finish()
+            job.join()
+            expectations.expectTotal(2)
+        }
+    }
+
+    @Test
+    fun innerScopeFailure() {
+        runBlocking {
+            owner.whenResumed {
+                val error = CompletableDeferred<Throwable>()
+                val exceptionHandler = CoroutineExceptionHandler { _, throwable ->
+                    error.complete(throwable)
+                }
+                launch(Job() + exceptionHandler) {
+                    throw IllegalStateException("i fail")
+                }
+                val a2 = async {
+                    expectations.expect(1)
+                }
+                assertThat(error.await()).hasMessageThat().contains("i fail")
+                a2.await()
+            }
+            expectations.expectTotal(1)
+        }
+    }
+
+    @Test
+    fun alreadyFinished() {
+        runBlocking {
+            finish()
+            launch {
+                owner.whenResumed {
+                    expectations.expectUnreached()
+                }
+            }.join()
+            expectations.expectTotal(0)
+        }
+    }
+
+    @Test
+    fun catchFinishWhileDelayed() {
+        runBlocking {
+            val job = testingScope.launch {
+                owner.whenResumed {
+                    try {
+                        expectations.expect(1)
+                        delay(100000)
+                        expectations.expectUnreached()
+                    } catch (e: Exception) {
+                        expectations.expect(2)
+                        assertThat(isActive).isFalse()
+                    } finally {
+                        expectations.expect(3)
+                    }
+                    expectations.expect(4)
+                }
+            }
+            drain()
+            expectations.expectTotal(1)
+            finish()
+            job.join()
+            expectations.expectTotal(4)
+        }
+    }
+
+    @Test
+    fun pauseThenContinue() {
+        runBlocking {
+            val job = testingScope.launch {
+                owner.whenResumed {
+                    expectations.expect(1)
+                    withContext(testingScope.coroutineContext) {
+                        pause()
+                    }
+                    expectations.expect(2)
+                }
+                expectations.expect(3)
+            }
+            drain()
+            expectations.expectTotal(1)
+            assertThat(owner.lifecycle.currentState).isEqualTo(Lifecycle.State.STARTED)
+            resume()
+            job.join()
+            expectations.expectTotal(3)
+        }
+    }
+
+    @Test
+    fun parentJobCancelled() {
+        runBlocking {
+            val parent = testingScope.launch {
+                owner.whenResumed {
+                    try {
+                        expectations.expect(1)
+                        delay(5000)
+                        expectations.expectUnreached()
+                    } finally {
+                        expectations.expect(2)
+                    }
+                }
+            }
+            drain()
+            expectations.expectTotal(1)
+            parent.cancelAndJoin()
+            expectations.expectTotal(2)
+        }
+    }
+
+    @Test
+    fun innerJobCancelsParent() {
+        try {
+            runBlocking(testingScope.coroutineContext) {
+                owner.whenResumed {
+                    throw IllegalStateException("i fail")
+                }
+            }
+            @Suppress("UNREACHABLE_CODE")
+            expectations.expectUnreached()
+        } catch (ex: IllegalStateException) {
+            assertThat(ex).hasMessageThat().isEqualTo("i fail")
+        }
+    }
+
+    @Test
+    fun lifecycleInsideLifecycle() {
+        runBlocking {
+            owner.whenResumed {
+                assertThread()
+                expectations.expect(1)
+                owner.whenResumed {
+                    assertThread()
+                    expectations.expect(2)
+                }
+            }
+        }
+        expectations.expectTotal(2)
+    }
+
+    @Test
+    fun lifecycleInsideLifecycle_innerFails() {
+        runBlocking {
+            val res = runCatching {
+                owner.whenResumed {
+                    try {
+                        assertThread()
+                        expectations.expect(1)
+                        owner.whenResumed {
+                            assertThread()
+                            expectations.expect(2)
+                            try {
+                                withContext(testingScope.coroutineContext) {
+                                    throw IllegalStateException("i fail")
+                                }
+                                @Suppress("UNREACHABLE_CODE")
+                                expectations.expectUnreached()
+                            } finally {
+                                expectations.expect(3)
+                            }
+                        }
+                        expectations.expectUnreached()
+                    } finally {
+                        expectations.expect(4)
+                    }
+                }
+            }
+            assertThat(res.exceptionOrNull()).hasMessageThat().matches("i fail")
+        }
+        expectations.expectTotal(4)
+    }
+
+    @Test
+    fun cancelInnerCoroutine() {
+        runBlocking {
+            val job = launch {
+                owner.whenResumed {
+                    withContext(testingScope.coroutineContext) {
+                        delay(200_000)
+                        expectations.expectUnreached()
+                    }
+                    expectations.expectUnreached()
+                }
+            }
+            job.cancelAndJoin()
+            expectations.expectTotal(0)
+        }
+    }
+
+    private fun pause() {
+        runBlocking(Dispatchers.Main) {
+            owner.setState(Lifecycle.State.STARTED)
+        }
+    }
+
+    private fun finish() {
+        runBlocking(Dispatchers.Main) {
+            owner.setState(Lifecycle.State.DESTROYED)
+        }
+    }
+
+    private fun resume() {
+        runBlocking(Dispatchers.Main) {
+            owner.setState(Lifecycle.State.RESUMED)
+        }
+    }
+
+    private fun assertThread() {
+        log("asserting looper")
+        assertThat(Thread.currentThread()).isSameAs(mainThread)
+    }
+
+    private fun log(msg: Any?) {
+        Log.d("TEST-RUN", "[${Thread.currentThread().name}] $msg")
+    }
+
+    private fun drain() {
+        assertThat(taskTracker.awaitIdle(10, TimeUnit.SECONDS)).isTrue()
+    }
+}
\ No newline at end of file
diff --git a/lifecycle/runtime/eap/src/androidTest/java/androidx/lifecycle/TaskTracker.kt b/lifecycle/runtime/eap/src/androidTest/java/androidx/lifecycle/TaskTracker.kt
new file mode 100644
index 0000000..f56e505
--- /dev/null
+++ b/lifecycle/runtime/eap/src/androidTest/java/androidx/lifecycle/TaskTracker.kt
@@ -0,0 +1,54 @@
+/*
+ * 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.lifecycle
+
+import java.util.concurrent.TimeUnit
+import java.util.concurrent.locks.ReentrantLock
+import kotlin.concurrent.withLock
+
+/**
+ * A simple counter on which we can await 0.
+ */
+class TaskTracker : TrackedExecutor.Callback {
+    private val lock = ReentrantLock()
+    private val idle = lock.newCondition()
+    private var counter = 0
+
+    override fun inc() {
+        lock.withLock {
+            counter++
+        }
+    }
+
+    override fun dec() {
+        lock.withLock {
+            counter--
+            if (counter == 0) {
+                idle.signalAll()
+            }
+        }
+    }
+
+    fun awaitIdle(time: Long, timeUnit: TimeUnit): Boolean {
+        lock.withLock {
+            if (counter == 0) {
+                return true
+            }
+            return idle.await(time, timeUnit)
+        }
+    }
+}
\ No newline at end of file
diff --git a/lifecycle/runtime/eap/src/androidTest/java/androidx/lifecycle/TrackedExecutor.kt b/lifecycle/runtime/eap/src/androidTest/java/androidx/lifecycle/TrackedExecutor.kt
new file mode 100644
index 0000000..d33030c
--- /dev/null
+++ b/lifecycle/runtime/eap/src/androidTest/java/androidx/lifecycle/TrackedExecutor.kt
@@ -0,0 +1,50 @@
+/*
+ * 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.lifecycle
+
+import java.util.concurrent.Executor
+import java.util.concurrent.ExecutorService
+import java.util.concurrent.TimeUnit
+
+/**
+ * An executor wrapper that tracks active tasks and reports back to a Callback when it changes.
+ */
+class TrackedExecutor(
+    private val callback: Callback,
+    private val delegate: ExecutorService
+) : Executor {
+    override fun execute(runnable: Runnable) {
+        callback.inc()
+        delegate.execute {
+            try {
+                runnable.run()
+            } finally {
+                callback.dec()
+            }
+        }
+    }
+
+    fun shutdown(time: Long, unit: TimeUnit): Boolean {
+        delegate.shutdown()
+        return delegate.awaitTermination(time, unit)
+    }
+
+    interface Callback {
+        fun inc()
+        fun dec()
+    }
+}
\ No newline at end of file
diff --git a/lifecycle/runtime/eap/src/main/AndroidManifest.xml b/lifecycle/runtime/eap/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..1c2bf90
--- /dev/null
+++ b/lifecycle/runtime/eap/src/main/AndroidManifest.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  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.
+  -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="androidx.lifecycle.ktx">
+</manifest>
diff --git a/lifecycle/runtime/eap/src/main/java/androidx/lifecycle/DispatchQueue.kt b/lifecycle/runtime/eap/src/main/java/androidx/lifecycle/DispatchQueue.kt
new file mode 100644
index 0000000..87e3d08
--- /dev/null
+++ b/lifecycle/runtime/eap/src/main/java/androidx/lifecycle/DispatchQueue.kt
@@ -0,0 +1,103 @@
+/*
+ * 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.lifecycle
+
+import android.annotation.SuppressLint
+import androidx.annotation.AnyThread
+import androidx.annotation.MainThread
+import kotlinx.coroutines.Dispatchers
+import java.util.ArrayDeque
+import java.util.Queue
+import kotlin.coroutines.EmptyCoroutineContext
+
+/**
+ * Helper class for [PausingDispatcher] that tracks runnables which are enqueued to the dispatcher
+ * and also calls back the [PausingDispatcher] when the runnable should run.
+ */
+internal class DispatchQueue {
+    // handler thread
+    private var paused: Boolean = false
+    // handler thread
+    private var finished: Boolean = false
+
+    private val queue: Queue<Runnable> = ArrayDeque<Runnable>()
+
+    private val consumer = Runnable {
+        // this one runs inside Dispatchers.Main
+        // if it should run, grabs an item, runs it
+        // if it has more, will re-enqueue
+        // To avoid starving Dispatchers.Main, we don't consume more than 1
+        if (!canRun()) {
+            return@Runnable
+        }
+        val next = queue.poll() ?: return@Runnable
+        try {
+            next.run()
+        } finally {
+            maybeEnqueueConsumer()
+        }
+    }
+
+    @MainThread
+    fun pause() {
+        paused = true
+    }
+
+    @MainThread
+    fun resume() {
+        if (!paused) {
+            return
+        }
+        check(!finished) {
+            "Cannot resume a finished dispatcher"
+        }
+        paused = false
+        maybeEnqueueConsumer()
+    }
+
+    @MainThread
+    fun finish() {
+        finished = true
+        maybeEnqueueConsumer()
+    }
+
+    @MainThread
+    fun maybeEnqueueConsumer() {
+        if (queue.isNotEmpty()) {
+            Dispatchers.Main.dispatch(EmptyCoroutineContext, consumer)
+        }
+    }
+
+    @MainThread
+    private fun canRun() = finished || !paused
+
+    @AnyThread
+    @SuppressLint("WrongThread") // false negative, we are checking the thread
+    fun runOrEnqueue(runnable: Runnable) {
+        Dispatchers.Main.immediate.dispatch(EmptyCoroutineContext, Runnable {
+            enqueue(runnable)
+        })
+    }
+
+    @MainThread
+    private fun enqueue(runnable: Runnable) {
+        check(queue.offer(runnable)) {
+            "cannot enqueue any more runnables"
+        }
+        maybeEnqueueConsumer()
+    }
+}
diff --git a/lifecycle/runtime/eap/src/main/java/androidx/lifecycle/LifecycleController.kt b/lifecycle/runtime/eap/src/main/java/androidx/lifecycle/LifecycleController.kt
new file mode 100644
index 0000000..4edc2fb
--- /dev/null
+++ b/lifecycle/runtime/eap/src/main/java/androidx/lifecycle/LifecycleController.kt
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.lifecycle
+
+import androidx.annotation.MainThread
+import kotlinx.coroutines.Job
+
+/**
+ * Attaches to a lifecycle and controls the [DispatchQueue]'s execution.
+ */
+@MainThread
+internal class LifecycleController(
+    private val lifecycle: Lifecycle,
+    private val minState: Lifecycle.State,
+    private val dispatchQueue: DispatchQueue,
+    parentJob: Job
+) {
+    private val observer = LifecycleEventObserver { source, _ ->
+        if (source.lifecycle.currentState == Lifecycle.State.DESTROYED) {
+            // cancel job before resuming remaining coroutines so that they run in cancelled
+            // state
+            handleDestroy(parentJob)
+        } else if (source.lifecycle.currentState < minState) {
+            dispatchQueue.pause()
+        } else {
+            dispatchQueue.resume()
+        }
+    }
+
+    init {
+        // If Lifecycle is already destroyed (e.g. developer leaked the lifecycle), we won't get
+        // an event callback so we need to check for it before registering
+        // see: b/128749497 for details.
+        if (lifecycle.currentState == Lifecycle.State.DESTROYED) {
+            handleDestroy(parentJob)
+        } else {
+            lifecycle.addObserver(observer)
+        }
+    }
+
+    @Suppress("NOTHING_TO_INLINE") // avoid unnecessary method
+    private inline fun handleDestroy(parentJob: Job) {
+        parentJob.cancel()
+        finish()
+    }
+
+    /**
+     * Removes the observer and also marks the [DispatchQueue] as finished so that any remaining
+     * runnables can be executed.
+     */
+    @MainThread
+    fun finish() {
+        lifecycle.removeObserver(observer)
+        dispatchQueue.finish()
+    }
+}
\ No newline at end of file
diff --git a/lifecycle/runtime/eap/src/main/java/androidx/lifecycle/PausingDispatcher.kt b/lifecycle/runtime/eap/src/main/java/androidx/lifecycle/PausingDispatcher.kt
new file mode 100644
index 0000000..d40e75d
--- /dev/null
+++ b/lifecycle/runtime/eap/src/main/java/androidx/lifecycle/PausingDispatcher.kt
@@ -0,0 +1,184 @@
+/*
+ * 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.lifecycle
+
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.Runnable
+import kotlinx.coroutines.withContext
+import kotlin.coroutines.CoroutineContext
+
+/**
+ * Runs the given block when the [LifecycleOwner]'s [Lifecycle] is at least in
+ * [Lifecycle.State.CREATED] state.
+ *
+ * @see Lifecycle.whenStateAtLeast for details
+ */
+suspend fun <T> LifecycleOwner.whenCreated(block: suspend CoroutineScope.() -> T): T =
+    lifecycle.whenCreated(block)
+
+/**
+ * Runs the given block when the [Lifecycle] is at least in [Lifecycle.State.CREATED] state.
+ *
+ * @see Lifecycle.whenStateAtLeast for details
+ */
+suspend fun <T> Lifecycle.whenCreated(block: suspend CoroutineScope.() -> T): T {
+    return whenStateAtLeast(Lifecycle.State.CREATED, block)
+}
+
+/**
+ * Runs the given block when the [LifecycleOwner]'s [Lifecycle] is at least in
+ * [Lifecycle.State.STARTED] state.
+ *
+ * @see Lifecycle.whenStateAtLeast for details
+ */
+suspend fun <T> LifecycleOwner.whenStarted(block: suspend CoroutineScope.() -> T): T =
+    lifecycle.whenStarted(block)
+
+/**
+ * Runs the given block when the [Lifecycle] is at least in [Lifecycle.State.STARTED] state.
+ *
+ * @see Lifecycle.whenStateAtLeast for details
+ */
+suspend fun <T> Lifecycle.whenStarted(block: suspend CoroutineScope.() -> T): T {
+    return whenStateAtLeast(Lifecycle.State.STARTED, block)
+}
+
+/**
+ * Runs the given block when the [LifecycleOwner]'s [Lifecycle] is at least in
+ * [Lifecycle.State.RESUMED] state.
+ *
+ * @see Lifecycle.whenStateAtLeast for details
+ */
+suspend fun <T> LifecycleOwner.whenResumed(block: suspend CoroutineScope.() -> T): T =
+    lifecycle.whenResumed(block)
+
+/**
+ * Runs the given block when the [Lifecycle] is at least in [Lifecycle.State.RESUMED] state.
+ *
+ * @see Lifecycle.whenStateAtLeast for details
+ */
+suspend fun <T> Lifecycle.whenResumed(block: suspend CoroutineScope.() -> T): T {
+    return whenStateAtLeast(Lifecycle.State.RESUMED, block)
+}
+
+/**
+ * Runs the given [block] on a [CoroutineDispatcher] that executes the [block] on the main thread
+ * and suspends the execution unless the [Lifecycle]'s state is at least [minState].
+ *
+ * If the [Lifecycle] moves to a lesser state while the [block] is running, the [block] will
+ * be suspended until the [Lifecycle] reaches to a state greater or equal to [minState].
+ *
+ * Note that this won't effect any sub coroutine if they use a different [CoroutineDispatcher].
+ * However, the [block] will not resume execution when the sub coroutine finishes unless the
+ * [Lifecycle] is at least in [minState].
+ *
+ * If the [Lifecycle] is destroyed while the [block] is suspended, the [block] will be cancelled
+ * which will also cancel any child coroutine launched inside the [block].
+ *
+ * If you have a `try finally` block in your code, the `finally` might run after the [Lifecycle]
+ * moves outside the desired state. It is recommended to check the [Lifecycle.getCurrentState]
+ * before accessing the UI. Similarly, if you have a `catch` statement that might catch
+ * `CancellationException`, you should check the [Lifecycle.getCurrentState] before accessing the
+ * UI. See the sample below for more details.
+ *
+ * ```
+ * // running a block of code only if lifecycle is STARTED
+ * viewLifecycle.whenStateAtLeast(Lifecycle.State.STARTED) {
+ *     // here, we are on the main thread and view lifecycle is guaranteed to be STARTED or RESUMED.
+ *     // We can safely access our views.
+ *     loadingBar.visibility = View.VISIBLE
+ *     try {
+ *         // we can call any suspend function
+ *         val data = withContext(Dispatchers.IO) {
+ *             // this will run in IO thread pool. It will keep running as long as Lifecycle
+ *             // is not DESTROYED. If it is destroyed, this coroutine will be cancelled as well.
+ *             // However, we CANNOT access Views here.
+ *
+ *             // We are using withContext(Dispatchers.IO) here just for demonstration purposes.
+ *             // Such code should live in your business logic classes and your UI should use a
+ *             // ViewModel (or similar) to access it.
+ *             api.getUser()
+ *         }
+ *         // this line will execute on the main thread and only if the lifecycle is in at least
+ *         // STARTED state (STARTED is the parameter we've passed to whenStateAtLeast)
+ *         // Because of this guarantee, we can safely access the UI again.
+ *         loadingBar.visibility = View.GONE
+ *         nameTextView.text = user.name
+ *         lastNameTextView.text = user.lastName
+ *     } catch(ex : UserNotFoundException) {
+ *         // same as above, this code can safely access UI elements because it only runs if
+ *         // view lifecycle is at least STARTED
+ *         loadingBar.visibility = View.GONE
+ *         showErrorDialog(ex)
+ *     } catch(th : Throwable) {
+ *          // Unlike the catch statement above, this catch statements it too generic and might
+ *          // also catch the CancellationException. Before accessing UI, you should check isActive
+ *          // or lifecycle state
+ *          if (viewLifecycle.currentState >= Lifecycle.State.STARTED) {
+ *              // here you can access the view because you've checked the coroutine is active
+ *          }
+ *     } finally {
+ *         // in case of cancellation, this line might run even if the Lifecycle is not DESTROYED.
+ *         // You cannot access Views here unless you check `isActive` or lifecycle state
+ *         if (viewLifecycle.currentState >= Lifecycle.State.STARTED) {
+ *             // safe to access views
+ *         } else {
+ *             // not safe to access views
+ *         }
+ *     }
+ * }
+ * ```
+ *
+ * @param minState The desired minimum state to run the [block].
+ * @param block The block to run when the lifecycle is at least in [minState].
+ * @return <T> The return value of the [block]
+ */
+suspend fun <T> Lifecycle.whenStateAtLeast(
+    minState: Lifecycle.State,
+    block: suspend CoroutineScope.() -> T
+) = withContext(Dispatchers.Main) {
+    val job = coroutineContext[Job] ?: error("when[State] methods should have a parent job")
+    val dispatcher = PausingDispatcher()
+    val controller =
+        LifecycleController(this@whenStateAtLeast, minState, dispatcher.dispatchQueue, job)
+    try {
+        withContext(dispatcher, block)
+    } finally {
+        controller.finish()
+    }
+}
+
+/**
+ * A [CoroutineDispatcher] implementation that maintains a dispatch queue to be able to pause
+ * execution of coroutines.
+ *
+ * @see [DispatchQueue] and [Lifecycle.whenStateAtLeast] for details.
+ */
+internal class PausingDispatcher : CoroutineDispatcher() {
+    /**
+     * helper class to maintain state and enqueued continuations.
+     */
+    @JvmField
+    internal val dispatchQueue = DispatchQueue()
+
+    override fun dispatch(context: CoroutineContext, block: Runnable) {
+        dispatchQueue.runOrEnqueue(block)
+    }
+}
\ No newline at end of file
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/media/src/main/java/androidx/media/MediaBrowserServiceCompat.java b/media/src/main/java/androidx/media/MediaBrowserServiceCompat.java
index d2b97f8..d9ffcd9 100644
--- a/media/src/main/java/androidx/media/MediaBrowserServiceCompat.java
+++ b/media/src/main/java/androidx/media/MediaBrowserServiceCompat.java
@@ -1103,7 +1103,9 @@
                     mConnections.remove(b);
 
                     ConnectionRecord connection = null;
-                    for (ConnectionRecord pendingConnection : mPendingConnections) {
+                    Iterator<ConnectionRecord> iter = mPendingConnections.iterator();
+                    while (iter.hasNext()) {
+                        ConnectionRecord pendingConnection = iter.next();
                         // Note: We cannot use Map/Set for mPendingConnections but List because
                         // multiple MediaBrowserCompats with the same UID can request connect.
                         if (pendingConnection.uid == uid) {
@@ -1115,7 +1117,7 @@
                                         pendingConnection.pid, pendingConnection.uid,
                                         rootHints, callbacks);
                             }
-                            mPendingConnections.remove(pendingConnection);
+                            iter.remove();
                         }
                     }
                     if (connection == null) {
diff --git a/media/version-compat-tests/current/client/src/androidTest/java/android/support/mediacompat/client/MediaBrowserCompatTest.java b/media/version-compat-tests/current/client/src/androidTest/java/android/support/mediacompat/client/MediaBrowserCompatTest.java
index 411f14d..8defe09 100644
--- a/media/version-compat-tests/current/client/src/androidTest/java/android/support/mediacompat/client/MediaBrowserCompatTest.java
+++ b/media/version-compat-tests/current/client/src/androidTest/java/android/support/mediacompat/client/MediaBrowserCompatTest.java
@@ -63,6 +63,7 @@
 import static org.junit.Assert.fail;
 
 import android.content.ComponentName;
+import android.content.Context;
 import android.os.Build;
 import android.os.Bundle;
 import android.support.mediacompat.testlib.util.PollingCheck;
@@ -315,6 +316,54 @@
 
     @Test
     @MediumTest
+    public void testMultipleConnections() throws Exception {
+        final Context context = getInstrumentation().getTargetContext();
+        final StubConnectionCallback callback1 = new StubConnectionCallback();
+        final StubConnectionCallback callback2 = new StubConnectionCallback();
+        final StubConnectionCallback callback3 = new StubConnectionCallback();
+        final List<MediaBrowserCompat> browserList = new ArrayList<>();
+
+        getInstrumentation().runOnMainSync(new Runnable() {
+            @Override
+            public void run() {
+                MediaBrowserCompat browser1 = new MediaBrowserCompat(context, TEST_BROWSER_SERVICE,
+                        callback1, new Bundle());
+                MediaBrowserCompat browser2 = new MediaBrowserCompat(context, TEST_BROWSER_SERVICE,
+                        callback2, new Bundle());
+                MediaBrowserCompat browser3 = new MediaBrowserCompat(context, TEST_BROWSER_SERVICE,
+                        callback3, new Bundle());
+
+                browserList.add(browser1);
+                browserList.add(browser2);
+                browserList.add(browser3);
+
+                browser1.connect();
+                browser2.connect();
+                browser3.connect();
+            }
+        });
+
+        try {
+            new PollingCheck(TIME_OUT_MS) {
+                @Override
+                protected boolean check() {
+                    return callback1.mConnectedCount == 1
+                            && callback2.mConnectedCount == 1
+                            && callback3.mConnectedCount == 1;
+                }
+            }.run();
+        } finally {
+            for (int i = 0; i < browserList.size(); i++) {
+                MediaBrowserCompat browser = browserList.get(i);
+                if (browser.isConnected()) {
+                    browser.disconnect();
+                }
+            }
+        }
+    }
+
+    @Test
+    @MediumTest
     public void testSubscribe() throws Exception {
         connectMediaBrowserService();
 
diff --git a/media/version-compat-tests/current/client/src/androidTest/java/android/support/mediacompat/client/MediaBrowserTest.java b/media/version-compat-tests/current/client/src/androidTest/java/android/support/mediacompat/client/MediaBrowserTest.java
index fe57150..185f0f5 100644
--- a/media/version-compat-tests/current/client/src/androidTest/java/android/support/mediacompat/client/MediaBrowserTest.java
+++ b/media/version-compat-tests/current/client/src/androidTest/java/android/support/mediacompat/client/MediaBrowserTest.java
@@ -43,6 +43,7 @@
 import static org.junit.Assert.fail;
 
 import android.content.ComponentName;
+import android.content.Context;
 import android.media.MediaDescription;
 import android.media.browse.MediaBrowser;
 import android.media.browse.MediaBrowser.MediaItem;
@@ -295,6 +296,54 @@
 
     @Test
     @MediumTest
+    public void testMultipleConnections() throws Exception {
+        final Context context = getInstrumentation().getTargetContext();
+        final StubConnectionCallback callback1 = new StubConnectionCallback();
+        final StubConnectionCallback callback2 = new StubConnectionCallback();
+        final StubConnectionCallback callback3 = new StubConnectionCallback();
+        final List<MediaBrowser> browserList = new ArrayList<>();
+
+        getInstrumentation().runOnMainSync(new Runnable() {
+            @Override
+            public void run() {
+                MediaBrowser browser1 = new MediaBrowser(context, TEST_BROWSER_SERVICE,
+                        callback1, new Bundle());
+                MediaBrowser browser2 = new MediaBrowser(context, TEST_BROWSER_SERVICE,
+                        callback2, new Bundle());
+                MediaBrowser browser3 = new MediaBrowser(context, TEST_BROWSER_SERVICE,
+                        callback3, new Bundle());
+
+                browserList.add(browser1);
+                browserList.add(browser2);
+                browserList.add(browser3);
+
+                browser1.connect();
+                browser2.connect();
+                browser3.connect();
+            }
+        });
+
+        try {
+            new PollingCheck(TIME_OUT_MS) {
+                @Override
+                protected boolean check() {
+                    return callback1.mConnectedCount == 1
+                            && callback2.mConnectedCount == 1
+                            && callback3.mConnectedCount == 1;
+                }
+            }.run();
+        } finally {
+            for (int i = 0; i < browserList.size(); i++) {
+                MediaBrowser browser = browserList.get(i);
+                if (browser.isConnected()) {
+                    browser.disconnect();
+                }
+            }
+        }
+    }
+
+    @Test
+    @MediumTest
     public void testSubscribe() throws Exception {
         connectMediaBrowserService();
 
diff --git a/media/version-compat-tests/previous/client/src/androidTest/java/android/support/mediacompat/client/MediaBrowserCompatTest.java b/media/version-compat-tests/previous/client/src/androidTest/java/android/support/mediacompat/client/MediaBrowserCompatTest.java
index ffabc62..7a7d0c1 100644
--- a/media/version-compat-tests/previous/client/src/androidTest/java/android/support/mediacompat/client/MediaBrowserCompatTest.java
+++ b/media/version-compat-tests/previous/client/src/androidTest/java/android/support/mediacompat/client/MediaBrowserCompatTest.java
@@ -59,6 +59,7 @@
 import static org.junit.Assert.fail;
 
 import android.content.ComponentName;
+import android.content.Context;
 import android.os.Build;
 import android.os.Bundle;
 import android.support.annotation.NonNull;
@@ -309,6 +310,54 @@
 
     @Test
     @MediumTest
+    public void testMultipleConnections() throws Exception {
+        final Context context = getInstrumentation().getTargetContext();
+        final StubConnectionCallback callback1 = new StubConnectionCallback();
+        final StubConnectionCallback callback2 = new StubConnectionCallback();
+        final StubConnectionCallback callback3 = new StubConnectionCallback();
+        final List<MediaBrowserCompat> browserList = new ArrayList<>();
+
+        getInstrumentation().runOnMainSync(new Runnable() {
+            @Override
+            public void run() {
+                MediaBrowserCompat browser1 = new MediaBrowserCompat(context, TEST_BROWSER_SERVICE,
+                        callback1, new Bundle());
+                MediaBrowserCompat browser2 = new MediaBrowserCompat(context, TEST_BROWSER_SERVICE,
+                        callback2, new Bundle());
+                MediaBrowserCompat browser3 = new MediaBrowserCompat(context, TEST_BROWSER_SERVICE,
+                        callback3, new Bundle());
+
+                browserList.add(browser1);
+                browserList.add(browser2);
+                browserList.add(browser3);
+
+                browser1.connect();
+                browser2.connect();
+                browser3.connect();
+            }
+        });
+
+        try {
+            new PollingCheck(TIME_OUT_MS) {
+                @Override
+                protected boolean check() {
+                    return callback1.mConnectedCount == 1
+                            && callback2.mConnectedCount == 1
+                            && callback3.mConnectedCount == 1;
+                }
+            }.run();
+        } finally {
+            for (int i = 0; i < browserList.size(); i++) {
+                MediaBrowserCompat browser = browserList.get(i);
+                if (browser.isConnected()) {
+                    browser.disconnect();
+                }
+            }
+        }
+    }
+
+    @Test
+    @MediumTest
     public void testSubscribe() throws Exception {
         connectMediaBrowserService();
 
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/VideoView.java b/media2-widget/src/main/java/androidx/media2/widget/VideoView.java
index cf1ba6f..a619381 100644
--- a/media2-widget/src/main/java/androidx/media2/widget/VideoView.java
+++ b/media2-widget/src/main/java/androidx/media2/widget/VideoView.java
@@ -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}
@@ -773,9 +777,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 +786,10 @@
         if (mAudioTrackIndices.size() > 0) {
             mSelectedAudioTrackIndex = 0;
         }
+        // Re-select originally selected subtitle track since SubtitleController has been reset.
+        if (mSelectedSubtitleTrackIndex != INVALID_TRACK_INDEX) {
+            selectSubtitleTrack(mSelectedSubtitleTrackIndex);
+        }
 
         Bundle data = new Bundle();
         data.putInt(MediaControlView.KEY_VIDEO_TRACK_COUNT, mVideoTrackIndices.size());
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/exoplayer/ExoPlayerWrapper.java b/media2/src/main/java/androidx/media2/exoplayer/ExoPlayerWrapper.java
index 69052ca..59235e0 100644
--- a/media2/src/main/java/androidx/media2/exoplayer/ExoPlayerWrapper.java
+++ b/media2/src/main/java/androidx/media2/exoplayer/ExoPlayerWrapper.java
@@ -164,6 +164,7 @@
     private final Runnable mPollBufferRunnable;
 
     private SimpleExoPlayer mPlayer;
+    private Handler mPlayerHandler;
     private DefaultAudioSink mAudioSink;
     private TrackSelector mTrackSelector;
     private MediaItemQueue mMediaItemQueue;
@@ -323,9 +324,10 @@
     public void setAudioAttributes(AudioAttributesCompat audioAttributes) {
         mHasAudioAttributes = true;
         mPlayer.setAudioAttributes(ExoPlayerUtils.getAudioAttributes(audioAttributes));
+
         // Reset the audio session ID, as it gets cleared by setting audio attributes.
         if (mAudioSessionId != C.AUDIO_SESSION_ID_UNSET) {
-            mAudioSink.setAudioSessionId(mAudioSessionId);
+            updatePlayerAudioSessionId(mPlayerHandler, mAudioSink, mAudioSessionId);
         }
     }
 
@@ -336,7 +338,9 @@
 
     public void setAudioSessionId(int audioSessionId) {
         mAudioSessionId = audioSessionId;
-        mAudioSink.setAudioSessionId(mAudioSessionId);
+        if (mPlayer != null) {
+            updatePlayerAudioSessionId(mPlayerHandler, mAudioSink, mAudioSessionId);
+        }
     }
 
     public int getAudioSessionId() {
@@ -465,6 +469,7 @@
                 mBandwidthMeter,
                 new AnalyticsCollector.Factory(),
                 mLooper);
+        mPlayerHandler = new Handler(mPlayer.getPlaybackLooper());
         mMediaItemQueue = new MediaItemQueue(mContext, mPlayer, mListener);
         mPlayer.addListener(listener);
         // TODO(b/80232248): Switch to AnalyticsListener once default methods work.
@@ -795,6 +800,19 @@
         }
     }
 
+    private static void updatePlayerAudioSessionId(
+            Handler playerHandler,
+            final DefaultAudioSink audioSink,
+            final int audioSessionId) {
+        // DefaultAudioSink is not thread-safe, so post the update to the playback thread.
+        playerHandler.post(new Runnable() {
+            @Override
+            public void run() {
+                audioSink.setAudioSessionId(audioSessionId);
+            }
+        });
+    }
+
     private static final class MediaItemInfo {
 
         final MediaItem mMediaItem;
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/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/main/java/androidx/room/paging/LimitOffsetDataSource.java b/room/runtime/src/main/java/androidx/room/paging/LimitOffsetDataSource.java
index 0607dd5..845b64e 100644
--- a/room/runtime/src/main/java/androidx/room/paging/LimitOffsetDataSource.java
+++ b/room/runtime/src/main/java/androidx/room/paging/LimitOffsetDataSource.java
@@ -113,7 +113,7 @@
         int firstLoadPosition = 0;
         RoomSQLiteQuery sqLiteQuery = null;
         Cursor cursor = null;
-
+        //noinspection deprecation
         mDb.beginTransaction();
         try {
             totalCount = countItems();
@@ -125,6 +125,7 @@
                 sqLiteQuery = getSQLiteQuery(firstLoadPosition, firstLoadSize);
                 cursor = mDb.query(sqLiteQuery);
                 List<T> rows = convertRows(cursor);
+                //noinspection deprecation
                 mDb.setTransactionSuccessful();
                 list = rows;
             }
@@ -132,6 +133,7 @@
             if (cursor != null) {
                 cursor.close();
             }
+            //noinspection deprecation
             mDb.endTransaction();
             if (sqLiteQuery != null) {
                 sqLiteQuery.release();
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 de45149..4e26b66 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -99,6 +99,7 @@
 includeProject(":lifecycle:lifecycle-reactivestreams", "lifecycle/reactivestreams")
 includeProject(":lifecycle:lifecycle-reactivestreams-ktx", "lifecycle/reactivestreams/ktx")
 includeProject(":lifecycle:lifecycle-runtime", "lifecycle/runtime")
+includeProject(":lifecycle:lifecycle-runtime-eap", "lifecycle/runtime/eap") // temporary
 includeProject(":lifecycle:lifecycle-service", "lifecycle/service")
 includeProject(":lifecycle:lifecycle-viewmodel", "lifecycle/viewmodel")
 includeProject(":lifecycle:lifecycle-viewmodel-ktx", "lifecycle/viewmodel/ktx")
@@ -146,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 192bc62..a2d04e0 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
@@ -117,7 +117,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.