[go: nahoru, domu]

Ignore special effects for views not attachedToWindow

When adding fragments to the activity before the View hierarchy has been
attached to the window, if the fragment has special effects, they will
never be started. In the case of Transitions, this means there is never
an onEndTransition() callback, which causes fragment to be unable to
move to resumed.

Since unattached views will not run their transitions anyway, we should
drop any special effect operations before the special effects container
is attached to the window.

RelNote: "Fragments with transitions added before the view hierarchy is
attached now properly reach resumed."
Test: FragmentTransitionTest
Bug: 177154873

Change-Id: I1fc1db01450466f9ee012f07e4b608aa3337c838
(cherry picked from commit b57c42bc3a96fea02e15a732d1b7c22383eb010e)
diff --git a/fragment/fragment/src/androidTest/AndroidManifest.xml b/fragment/fragment/src/androidTest/AndroidManifest.xml
index 97ee537..51eaed7 100644
--- a/fragment/fragment/src/androidTest/AndroidManifest.xml
+++ b/fragment/fragment/src/androidTest/AndroidManifest.xml
@@ -53,6 +53,7 @@
                   android:theme="@style/DialogTheme"/>
         <activity android:name="androidx.fragment.app.TestAppCompatActivity"
                   android:theme="@style/Theme.AppCompat.Light" />
+        <activity android:name="androidx.fragment.app.AddTransitionFragmentInActivity"/>
     </application>
 
 </manifest>
diff --git a/fragment/fragment/src/androidTest/java/androidx/fragment/app/FragmentTransitionTest.kt b/fragment/fragment/src/androidTest/java/androidx/fragment/app/FragmentTransitionTest.kt
index 28269ba..1ab4cad 100644
--- a/fragment/fragment/src/androidTest/java/androidx/fragment/app/FragmentTransitionTest.kt
+++ b/fragment/fragment/src/androidTest/java/androidx/fragment/app/FragmentTransitionTest.kt
@@ -24,10 +24,12 @@
 import androidx.core.app.SharedElementCallback
 import androidx.fragment.app.test.FragmentTestActivity
 import androidx.fragment.test.R
+import androidx.test.core.app.ActivityScenario
 import androidx.test.filters.MediumTest
 import androidx.test.filters.SdkSuppress
 import androidx.test.platform.app.InstrumentationRegistry
 import androidx.testutils.waitForExecution
+import androidx.testutils.withActivity
 import com.google.common.truth.Truth.assertThat
 import com.google.common.truth.Truth.assertWithMessage
 import org.junit.After
@@ -1213,6 +1215,14 @@
         assertThat(fragmentContainer.getTag(R.id.visible_removing_fragment_view_tag)).isNull()
     }
 
+    @Test
+    fun ignoreWhenViewNotAttached() {
+        with(ActivityScenario.launch(AddTransitionFragmentInActivity::class.java)) {
+            val fragment = withActivity { fragment }
+            assertThat(fragment.calledOnResume).isTrue()
+        }
+    }
+
     private fun setupInitialFragment(): TransitionFragment {
         val fragment1 = TransitionFragment(R.layout.scene1)
         fragmentManager.beginTransaction()
@@ -1570,3 +1580,15 @@
         }
     }
 }
+
+class AddTransitionFragmentInActivity : FragmentActivity() {
+    val fragment = TransitionFragment()
+
+    override fun onStart() {
+        super.onStart()
+        supportFragmentManager.beginTransaction()
+            .setReorderingAllowed(true)
+            .add(android.R.id.content, fragment)
+            .commit()
+    }
+}
diff --git a/fragment/fragment/src/main/java/androidx/fragment/app/SpecialEffectsController.java b/fragment/fragment/src/main/java/androidx/fragment/app/SpecialEffectsController.java
index f6862fc..f46ac62 100644
--- a/fragment/fragment/src/main/java/androidx/fragment/app/SpecialEffectsController.java
+++ b/fragment/fragment/src/main/java/androidx/fragment/app/SpecialEffectsController.java
@@ -24,6 +24,7 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.core.os.CancellationSignal;
+import androidx.core.view.ViewCompat;
 import androidx.fragment.R;
 
 import java.util.ArrayList;
@@ -257,6 +258,13 @@
             // No operations should execute while the container is postponed
             return;
         }
+        // If the container is not attached to the window, ignore the special effect
+        // since none of the special effect systems will run them anyway.
+        if (!ViewCompat.isAttachedToWindow(mContainer)) {
+            forceCompleteAllOperations();
+            mOperationDirectionIsPop = false;
+            return;
+        }
         synchronized (mPendingOperations) {
             if (!mPendingOperations.isEmpty()) {
                 ArrayList<Operation> currentlyRunningOperations =
@@ -290,6 +298,7 @@
     }
 
     void forceCompleteAllOperations() {
+        boolean attachedToWindow = ViewCompat.isAttachedToWindow(mContainer);
         synchronized (mPendingOperations) {
             updateFinalState();
             for (Operation operation : mPendingOperations) {
@@ -301,8 +310,9 @@
             for (Operation operation : runningOperations) {
                 if (FragmentManager.isLoggingEnabled(Log.VERBOSE)) {
                     Log.v(FragmentManager.TAG,
-                            "SpecialEffectsController: Cancelling running operation "
-                                    + operation);
+                            "SpecialEffectsController: " + (attachedToWindow ? "" :
+                                    "Container " + mContainer + " is not attached to window. ")
+                                    + "Cancelling running operation " + operation);
                 }
                 operation.cancel();
             }
@@ -312,8 +322,9 @@
             for (Operation operation : pendingOperations) {
                 if (FragmentManager.isLoggingEnabled(Log.VERBOSE)) {
                     Log.v(FragmentManager.TAG,
-                            "SpecialEffectsController: Cancelling pending operation "
-                                    + operation);
+                            "SpecialEffectsController: " + (attachedToWindow ? "" :
+                                    "Container " + mContainer + " is not attached to window. ")
+                                    + "Cancelling pending operation " + operation);
                 }
                 operation.cancel();
             }
diff --git a/transition/transition/src/androidTest/AndroidManifest.xml b/transition/transition/src/androidTest/AndroidManifest.xml
index 0e4c4f4..c6660c7 100755
--- a/transition/transition/src/androidTest/AndroidManifest.xml
+++ b/transition/transition/src/androidTest/AndroidManifest.xml
@@ -22,6 +22,8 @@
         <activity android:name="androidx.transition.FragmentTransitionTestActivity"/>
         <activity
             android:name="androidx.transition.TransitionActivity"/>
+        <activity
+            android:name="androidx.transition.AddTransitionFragmentInActivity"/>
     </application>
 
 </manifest>
diff --git a/transition/transition/src/androidTest/java/androidx/transition/FragmentTransitionTest.kt b/transition/transition/src/androidTest/java/androidx/transition/FragmentTransitionTest.kt
index 5a10d9d..c45abaf 100644
--- a/transition/transition/src/androidTest/java/androidx/transition/FragmentTransitionTest.kt
+++ b/transition/transition/src/androidTest/java/androidx/transition/FragmentTransitionTest.kt
@@ -23,10 +23,12 @@
 import androidx.fragment.app.Fragment
 import androidx.fragment.app.FragmentActivity
 import androidx.fragment.app.FragmentManager
+import androidx.test.core.app.ActivityScenario
 import androidx.test.filters.MediumTest
 import androidx.test.filters.SdkSuppress
 import androidx.test.platform.app.InstrumentationRegistry
 import androidx.testutils.waitForExecution
+import androidx.testutils.withActivity
 import androidx.transition.test.R
 import com.google.common.truth.Truth.assertThat
 import com.google.common.truth.Truth.assertWithMessage
@@ -1186,6 +1188,14 @@
         assertThat(endGreen.transitionName).isEqualTo("greenSquare")
     }
 
+    @Test
+    fun ignoreWhenViewNotAttached() {
+        with(ActivityScenario.launch(AddTransitionFragmentInActivity::class.java)) {
+            val fragment = withActivity { fragment }
+            assertThat(fragment.calledOnResume).isTrue()
+        }
+    }
+
     private fun setupInitialFragment(): TransitionFragment {
         val fragment1 = TransitionFragment(R.layout.fragment_scene1)
         fragmentManager.beginTransaction()
@@ -1541,3 +1551,15 @@
 }
 
 class FragmentTransitionTestActivity : FragmentActivity(R.layout.simple_container)
+
+class AddTransitionFragmentInActivity : FragmentActivity() {
+    val fragment = TransitionFragment()
+
+    override fun onStart() {
+        super.onStart()
+        supportFragmentManager.beginTransaction()
+            .setReorderingAllowed(true)
+            .add(android.R.id.content, fragment)
+            .commit()
+    }
+}