Fix incorrect pop animation in multiple transactions

When a fragment is added by a pop and then removed in back to back
transactions, we should use a normal exit animation instead of a popExit
animation.

This change keeps track of the first Fragment being visibly removed
(remove, hide, or detach) in a set of FragmentTransactions. It passes
the animations of subsequent removing fragments to the one that will be
visible when the animation is executed. The Fragments are tracked by a
tag on their container.

Test: all tests pass, and tested in app
Bug: 111659726
Change-Id: I18eefc2fdb4fc4c27c467212cc2e68beb20ea00b
diff --git a/fragment/fragment/src/main/java/androidx/fragment/app/FragmentManager.java b/fragment/fragment/src/main/java/androidx/fragment/app/FragmentManager.java
index ced206d..4cf1fce 100644
--- a/fragment/fragment/src/main/java/androidx/fragment/app/FragmentManager.java
+++ b/fragment/fragment/src/main/java/androidx/fragment/app/FragmentManager.java
@@ -1117,6 +1117,16 @@
         int nextAnim = fragment.getNextAnim();
         // Clear the Fragment animation
         fragment.setNextAnim(0);
+        // We do not need to keep up with the removing Fragment after we get its next animation.
+        // If transactions do not allow reordering, this will always be true and the visible
+        // removing fragment will be cleared. If reordering is allowed, this will only be true
+        // after all records in a transaction have been executed and the visible removing
+        // fragment has the correct animation, so it is time to clear it.
+        View container = mContainer.onFindViewById(fragment.mContainerId);
+        if (container != null
+                && container.getTag(R.id.visible_removing_fragment_view_tag) != null) {
+            container.setTag(R.id.visible_removing_fragment_view_tag, null);
+        }
         // If there is a transition on the container, clear those set on the fragment
         if (fragment.mContainer != null && fragment.mContainer.getLayoutTransition() != null) {
             return null;
@@ -1633,12 +1643,8 @@
      * FragmentContainerView.
      */
     void setExitAnimationOrder(Fragment f, boolean isPop) {
-        // This will be false if a child fragment is added to its parent's childFragmentManager
-        // before a view is created for Parent. In all other cases (adding a fragment to an
-        // FragmentActivity's fragmentManager, adding a child fragment to a parent that has a view),
-        // it should be true.
-        if (mContainer.onHasView()) {
-            ViewGroup container = (ViewGroup) mContainer.onFindViewById(f.mFragmentId);
+        ViewGroup container = getFragmentContainer(f);
+        if (container != null) {
             if (container instanceof FragmentContainerView) {
                 ((FragmentContainerView) container).setDrawDisappearingViewsLast(!isPop);
             }
@@ -1923,6 +1929,7 @@
             }
             fragment.mAdded = false;
             fragment.mRemoving = true;
+            setVisibleRemovingFragment(fragment);
         }
     }
 
@@ -1939,6 +1946,7 @@
             // Toggle hidden changed so that if a fragment goes through show/hide/show
             // it doesn't go through the animation.
             fragment.mHiddenChanged = !fragment.mHiddenChanged;
+            setVisibleRemovingFragment(fragment);
         }
     }
 
@@ -1972,6 +1980,7 @@
                     mNeedMenuInvalidate = true;
                 }
                 fragment.mAdded = false;
+                setVisibleRemovingFragment(fragment);
             }
         }
     }
@@ -2578,6 +2587,34 @@
     }
 
     /**
+     * Set a Fragment that is visibly being removed from the screen to a tag on its container.
+     * If a Fragment with the same container is already set, the previously added
+     * Fragment has its exit animation updated to the correct exit animation (either exit or
+     * pop_exit).
+     */
+    private void setVisibleRemovingFragment(Fragment f) {
+        ViewGroup container = getFragmentContainer(f);
+        if (container != null) {
+            if (container.getTag(R.id.visible_removing_fragment_view_tag) == null) {
+                container.setTag(R.id.visible_removing_fragment_view_tag, f);
+            }
+            ((Fragment) container.getTag(R.id.visible_removing_fragment_view_tag))
+                    .setNextAnim(f.getNextAnim());
+        }
+    }
+
+    private ViewGroup getFragmentContainer(Fragment f) {
+        // This will be false if a child fragment is added to its parent's childFragmentManager
+        // before a view is created for Parent. In all other cases (adding a fragment to an
+        // FragmentActivity's fragmentManager, adding a child fragment to a parent that has a view),
+        // it should be true.
+        if (mContainer.onHasView()) {
+            return (ViewGroup) mContainer.onFindViewById(f.mContainerId);
+        }
+        return null;
+    }
+
+    /**
      * Ensure that fragments that are added are moved to at least the CREATED state.
      * Any newly-added Views are inserted into {@code added} so that the Transaction can be
      * postponed with {@link Fragment#postponeEnterTransition()}. They will later be made
diff --git a/fragment/fragment/src/main/res/values/ids.xml b/fragment/fragment/src/main/res/values/ids.xml
index db4ce6e..7dad6a7 100644
--- a/fragment/fragment/src/main/res/values/ids.xml
+++ b/fragment/fragment/src/main/res/values/ids.xml
@@ -17,4 +17,5 @@
 
 <resources>
         <item type="id" name="fragment_container_view_tag" />
+        <item type="id" name="visible_removing_fragment_view_tag" />
 </resources>
\ No newline at end of file