Merge "Fix direct transition from VIEW_CREATED to CREATED" into androidx-master-dev
diff --git a/fragment/fragment/src/androidTest/AndroidManifest.xml b/fragment/fragment/src/androidTest/AndroidManifest.xml
index 5bac34a..97ee537 100644
--- a/fragment/fragment/src/androidTest/AndroidManifest.xml
+++ b/fragment/fragment/src/androidTest/AndroidManifest.xml
@@ -48,6 +48,7 @@
         <activity android:name="androidx.fragment.app.FragmentSavedStateActivity" />
         <activity android:name="androidx.fragment.app.FragmentFinishEarlyTestActivity" />
         <activity android:name="androidx.fragment.app.SimpleContainerActivity" />
+        <activity android:name="androidx.fragment.app.ReplaceInCreateActivity" />
         <activity android:name="androidx.fragment.app.DialogActivity"
                   android:theme="@style/DialogTheme"/>
         <activity android:name="androidx.fragment.app.TestAppCompatActivity"
diff --git a/fragment/fragment/src/androidTest/java/androidx/fragment/app/FragmentReplaceTest.kt b/fragment/fragment/src/androidTest/java/androidx/fragment/app/FragmentReplaceTest.kt
index 4c155a7..f10ddfc 100644
--- a/fragment/fragment/src/androidTest/java/androidx/fragment/app/FragmentReplaceTest.kt
+++ b/fragment/fragment/src/androidTest/java/androidx/fragment/app/FragmentReplaceTest.kt
@@ -15,60 +15,121 @@
  */
 package androidx.fragment.app
 
+import android.os.Bundle
 import android.view.View
 import androidx.fragment.app.test.FragmentTestActivity
 import androidx.fragment.test.R
+import androidx.test.core.app.ActivityScenario
 import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.MediumTest
+import androidx.test.filters.LargeTest
+import androidx.testutils.withActivity
 import com.google.common.truth.Truth.assertThat
-import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
 
 /**
- * Test to prevent regressions in SupportFragmentManager fragment replace method. See b/24693644
+ * Test to prevent regressions in SupportFragmentManager fragment replace method.
  */
 @RunWith(AndroidJUnit4::class)
-@MediumTest
+@LargeTest
 class FragmentReplaceTest {
-    @Suppress("DEPRECATION")
-    @get:Rule
-    val activityRule = androidx.test.rule.ActivityTestRule(FragmentTestActivity::class.java)
 
     @Test
     fun testReplaceFragment() {
-        val activity = activityRule.activity
-        val fm = activity.supportFragmentManager
+        with(ActivityScenario.launch(FragmentTestActivity::class.java)) {
+            val fm = withActivity { supportFragmentManager }
 
-        fm.beginTransaction()
-            .add(R.id.content, StrictViewFragment(R.layout.fragment_a))
-            .addToBackStack(null)
-            .commit()
-        executePendingTransactions(fm)
-        assertThat(activity.findViewById<View>(R.id.textA)).isNotNull()
-        assertThat(activity.findViewById<View>(R.id.textB)).isNull()
-        assertThat(activity.findViewById<View>(R.id.textC)).isNull()
+            fm.beginTransaction()
+                .add(R.id.content, StrictViewFragment(R.layout.fragment_a))
+                .addToBackStack(null)
+                .commit()
+            executePendingTransactions()
+            withActivity {
+                assertThat(findViewById<View>(R.id.textA)).isNotNull()
+                assertThat(findViewById<View>(R.id.textB)).isNull()
+                assertThat(findViewById<View>(R.id.textC)).isNull()
+            }
 
-        fm.beginTransaction()
-            .add(R.id.content, StrictViewFragment(R.layout.fragment_b))
-            .addToBackStack(null)
-            .commit()
-        executePendingTransactions(fm)
-        assertThat(activity.findViewById<View>(R.id.textA)).isNotNull()
-        assertThat(activity.findViewById<View>(R.id.textB)).isNotNull()
-        assertThat(activity.findViewById<View>(R.id.textC)).isNull()
+            fm.beginTransaction()
+                .add(R.id.content, StrictViewFragment(R.layout.fragment_b))
+                .addToBackStack(null)
+                .commit()
+            executePendingTransactions()
+            withActivity {
+                assertThat(findViewById<View>(R.id.textA)).isNotNull()
+                assertThat(findViewById<View>(R.id.textB)).isNotNull()
+                assertThat(findViewById<View>(R.id.textC)).isNull()
+            }
 
-        activity.supportFragmentManager.beginTransaction()
-            .replace(R.id.content, StrictViewFragment(R.layout.fragment_c))
-            .addToBackStack(null)
-            .commit()
-        executePendingTransactions(fm)
-        assertThat(activity.findViewById<View>(R.id.textA)).isNull()
-        assertThat(activity.findViewById<View>(R.id.textB)).isNull()
-        assertThat(activity.findViewById<View>(R.id.textC)).isNotNull()
+            fm.beginTransaction()
+                .replace(R.id.content, StrictViewFragment(R.layout.fragment_c))
+                .addToBackStack(null)
+                .commit()
+            executePendingTransactions()
+            withActivity {
+                assertThat(findViewById<View>(R.id.textA)).isNull()
+                assertThat(findViewById<View>(R.id.textB)).isNull()
+                assertThat(findViewById<View>(R.id.textC)).isNotNull()
+            }
+        }
     }
 
-    private fun executePendingTransactions(fm: FragmentManager) {
-        activityRule.runOnUiThread { fm.executePendingTransactions() }
+    @Test
+    fun testReplaceFragmentInOnCreate() {
+        with(ActivityScenario.launch(ReplaceInCreateActivity::class.java)) {
+            val replaceInCreateFragment = withActivity { this.replaceInCreateFragment }
+
+            assertThat(replaceInCreateFragment.isAdded)
+                .isFalse()
+            withActivity {
+                assertThat(findViewById<View>(R.id.textA)).isNull()
+                assertThat(findViewById<View>(R.id.textB)).isNotNull()
+            }
+        }
+    }
+}
+
+class ReplaceInCreateActivity : FragmentActivity(R.layout.activity_content) {
+    private val parentFragment: ParentFragment
+        get() = supportFragmentManager.findFragmentById(R.id.content) as ParentFragment
+    val replaceInCreateFragment: ReplaceInCreateFragment
+        get() = parentFragment.replaceInCreateFragment
+
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+        if (savedInstanceState == null) {
+            // This issue only appears for child fragments
+            // so add parent fragment that contains the ReplaceInCreateFragment
+            supportFragmentManager.beginTransaction()
+                .add(R.id.content, ParentFragment())
+                .setReorderingAllowed(true)
+                .commit()
+        }
+    }
+
+    class ParentFragment : StrictViewFragment(R.layout.simple_container) {
+        lateinit var replaceInCreateFragment: ReplaceInCreateFragment
+
+        override fun onCreate(savedInstanceState: Bundle?) {
+            super.onCreate(savedInstanceState)
+            if (savedInstanceState == null) {
+                replaceInCreateFragment = ReplaceInCreateFragment()
+                childFragmentManager.beginTransaction()
+                    .add(R.id.fragmentContainer, replaceInCreateFragment)
+                    .setReorderingAllowed(true)
+                    .commit()
+            }
+        }
+    }
+}
+
+class ReplaceInCreateFragment : StrictViewFragment(R.layout.fragment_a) {
+
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+        parentFragmentManager.beginTransaction()
+            .replace(R.id.fragmentContainer, StrictViewFragment(R.layout.fragment_b))
+            .setReorderingAllowed(true)
+            .commit()
     }
 }
diff --git a/fragment/fragment/src/androidTest/java/androidx/fragment/app/StrictViewFragment.kt b/fragment/fragment/src/androidTest/java/androidx/fragment/app/StrictViewFragment.kt
index 0720e07..256a07b 100644
--- a/fragment/fragment/src/androidTest/java/androidx/fragment/app/StrictViewFragment.kt
+++ b/fragment/fragment/src/androidTest/java/androidx/fragment/app/StrictViewFragment.kt
@@ -51,6 +51,7 @@
         checkGetActivity()
         checkActivityNotDestroyed()
         checkState("onViewCreated", State.CREATED)
+        currentState = State.VIEW_CREATED
         onViewCreatedCalled = true
     }
 
@@ -78,7 +79,8 @@
                 .isNotNull()
         }
         checkGetActivity()
-        checkState("onDestroyView", State.CREATED)
+        checkState("onDestroyView", State.CREATED, State.VIEW_CREATED, State.ACTIVITY_CREATED)
+        currentState = State.CREATED
         onDestroyViewCalled = true
     }
 
diff --git a/fragment/fragment/src/main/java/androidx/fragment/app/FragmentStateManager.java b/fragment/fragment/src/main/java/androidx/fragment/app/FragmentStateManager.java
index 360beab..4446391 100644
--- a/fragment/fragment/src/main/java/androidx/fragment/app/FragmentStateManager.java
+++ b/fragment/fragment/src/main/java/androidx/fragment/app/FragmentStateManager.java
@@ -247,6 +247,10 @@
         if (mFragment.mDeferStart && mFragment.mState < Fragment.STARTED) {
             maxState = Math.min(maxState, Fragment.ACTIVITY_CREATED);
         }
+        if (FragmentManager.isLoggingEnabled(Log.VERBOSE)) {
+            Log.v(FragmentManager.TAG, "computeExpectedState() of " + maxState + " for "
+                    + mFragment);
+        }
         return maxState;
     }
 
@@ -335,10 +339,11 @@
                             mFragment.mState = Fragment.AWAITING_EXIT_EFFECTS;
                             break;
                         case Fragment.VIEW_CREATED:
-                            destroyFragmentView();
+                            mFragment.mInLayout = false;
                             mFragment.mState = Fragment.VIEW_CREATED;
                             break;
                         case Fragment.CREATED:
+                            destroyFragmentView();
                             mFragment.mState = Fragment.CREATED;
                             break;
                         case Fragment.ATTACHED:
@@ -725,6 +730,12 @@
         if (FragmentManager.isLoggingEnabled(Log.DEBUG)) {
             Log.d(TAG, "movefrom CREATE_VIEW: " + mFragment);
         }
+        // In cases where we never got up to AWAITING_EXIT_EFFECTS, we
+        // need to manually remove the view from the container to reverse
+        // what we did in createView()
+        if (mFragment.mContainer != null && mFragment.mView != null) {
+            mFragment.mContainer.removeView(mFragment.mView);
+        }
         mFragment.performDestroyView();
         mDispatcher.dispatchOnFragmentViewDestroyed(mFragment, false);
         mFragment.mContainer = null;
diff --git a/testutils/testutils-runtime/src/main/java/androidx/fragment/app/StrictFragment.kt b/testutils/testutils-runtime/src/main/java/androidx/fragment/app/StrictFragment.kt
index b7feaa3..e52994e 100644
--- a/testutils/testutils-runtime/src/main/java/androidx/fragment/app/StrictFragment.kt
+++ b/testutils/testutils-runtime/src/main/java/androidx/fragment/app/StrictFragment.kt
@@ -127,7 +127,7 @@
         super.onActivityCreated(savedInstanceState)
         checkActivityNotDestroyed()
         calledOnActivityCreated = true
-        checkState("onActivityCreated", State.ATTACHED, State.CREATED)
+        checkState("onActivityCreated", State.ATTACHED, State.CREATED, State.VIEW_CREATED)
         val fromState = currentState
         currentState = State.ACTIVITY_CREATED
         onStateChanged(fromState)
@@ -198,6 +198,7 @@
         DETACHED,
         ATTACHED,
         CREATED,
+        VIEW_CREATED,
         ACTIVITY_CREATED,
         STARTED,
         RESUMED