Home | History | Annotate | Download | only in app
      1 /*
      2  * Copyright 2018 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 package androidx.fragment.app;
     17 
     18 import android.graphics.Rect;
     19 import android.os.Build;
     20 import android.util.SparseArray;
     21 import android.view.View;
     22 import android.view.ViewGroup;
     23 
     24 import androidx.collection.ArrayMap;
     25 import androidx.core.app.SharedElementCallback;
     26 import androidx.core.view.ViewCompat;
     27 
     28 import java.util.ArrayList;
     29 import java.util.Collection;
     30 import java.util.List;
     31 import java.util.Map;
     32 
     33 /**
     34  * Contains the Fragment Transition functionality for both ordered and reordered
     35  * Fragment Transactions. With reordered fragment transactions, all Views have been
     36  * added to the View hierarchy prior to calling startTransitions. With ordered
     37  * fragment transactions, Views will be removed and added after calling startTransitions.
     38  */
     39 class FragmentTransition {
     40     /**
     41      * The inverse of all BackStackRecord operation commands. This assumes that
     42      * REPLACE operations have already been replaced by add/remove operations.
     43      */
     44     private static final int[] INVERSE_OPS = {
     45             BackStackRecord.OP_NULL,              // inverse of OP_NULL (error)
     46             BackStackRecord.OP_REMOVE,            // inverse of OP_ADD
     47             BackStackRecord.OP_NULL,              // inverse of OP_REPLACE (error)
     48             BackStackRecord.OP_ADD,               // inverse of OP_REMOVE
     49             BackStackRecord.OP_SHOW,              // inverse of OP_HIDE
     50             BackStackRecord.OP_HIDE,              // inverse of OP_SHOW
     51             BackStackRecord.OP_ATTACH,            // inverse of OP_DETACH
     52             BackStackRecord.OP_DETACH,            // inverse of OP_ATTACH
     53             BackStackRecord.OP_UNSET_PRIMARY_NAV, // inverse of OP_SET_PRIMARY_NAV
     54             BackStackRecord.OP_SET_PRIMARY_NAV,   // inverse of OP_UNSET_PRIMARY_NAV
     55     };
     56 
     57     private static final FragmentTransitionImpl PLATFORM_IMPL = Build.VERSION.SDK_INT >= 21
     58             ? new FragmentTransitionCompat21()
     59             : null;
     60 
     61     private static final FragmentTransitionImpl SUPPORT_IMPL = resolveSupportImpl();
     62 
     63     private static FragmentTransitionImpl resolveSupportImpl() {
     64         try {
     65             @SuppressWarnings("unchecked")
     66             Class<FragmentTransitionImpl> impl = (Class<FragmentTransitionImpl>) Class.forName(
     67                     "androidx.transition.FragmentTransitionSupport");
     68             return impl.getDeclaredConstructor().newInstance();
     69         } catch (Exception ignored) {
     70             // support-transition is not loaded; ignore
     71         }
     72         return null;
     73     }
     74 
     75     /**
     76      * The main entry point for Fragment Transitions, this starts the transitions
     77      * set on the leaving Fragment's {@link Fragment#getExitTransition()}, the
     78      * entering Fragment's {@link Fragment#getEnterTransition()} and
     79      * {@link Fragment#getSharedElementEnterTransition()}. When popping,
     80      * the leaving Fragment's {@link Fragment#getReturnTransition()} and
     81      * {@link Fragment#getSharedElementReturnTransition()} and the entering
     82      * {@link Fragment#getReenterTransition()} will be run.
     83      * <p>
     84      * With reordered Fragment Transitions, all Views have been added to the
     85      * View hierarchy prior to calling this method. The incoming Fragment's Views
     86      * will be INVISIBLE. With ordered Fragment Transitions, this method
     87      * is called before any change has been made to the hierarchy. That means
     88      * that the added Fragments have not created their Views yet and the hierarchy
     89      * is unknown.
     90      *
     91      * @param fragmentManager The executing FragmentManagerImpl
     92      * @param records The list of transactions being executed.
     93      * @param isRecordPop For each transaction, whether it is a pop transaction or not.
     94      * @param startIndex The first index into records and isRecordPop to execute as
     95      *                   part of this transition.
     96      * @param endIndex One past the last index into records and isRecordPop to execute
     97      *                 as part of this transition.
     98      * @param isReordered true if this is a reordered transaction, meaning that the
     99      *                    Views of incoming fragments have been added. false if the
    100      *                    transaction has yet to be run and Views haven't been created.
    101      */
    102     static void startTransitions(FragmentManagerImpl fragmentManager,
    103             ArrayList<BackStackRecord> records, ArrayList<Boolean> isRecordPop,
    104             int startIndex, int endIndex, boolean isReordered) {
    105         if (fragmentManager.mCurState < Fragment.CREATED) {
    106             return;
    107         }
    108 
    109         SparseArray<FragmentContainerTransition> transitioningFragments =
    110                 new SparseArray<>();
    111         for (int i = startIndex; i < endIndex; i++) {
    112             final BackStackRecord record = records.get(i);
    113             final boolean isPop = isRecordPop.get(i);
    114             if (isPop) {
    115                 calculatePopFragments(record, transitioningFragments, isReordered);
    116             } else {
    117                 calculateFragments(record, transitioningFragments, isReordered);
    118             }
    119         }
    120 
    121         if (transitioningFragments.size() != 0) {
    122             final View nonExistentView = new View(fragmentManager.mHost.getContext());
    123             final int numContainers = transitioningFragments.size();
    124             for (int i = 0; i < numContainers; i++) {
    125                 int containerId = transitioningFragments.keyAt(i);
    126                 ArrayMap<String, String> nameOverrides = calculateNameOverrides(containerId,
    127                         records, isRecordPop, startIndex, endIndex);
    128 
    129                 FragmentContainerTransition containerTransition =
    130                         transitioningFragments.valueAt(i);
    131 
    132                 if (isReordered) {
    133                     configureTransitionsReordered(fragmentManager, containerId,
    134                             containerTransition, nonExistentView, nameOverrides);
    135                 } else {
    136                     configureTransitionsOrdered(fragmentManager, containerId,
    137                             containerTransition, nonExistentView, nameOverrides);
    138                 }
    139             }
    140         }
    141     }
    142 
    143     /**
    144      * Iterates through the transactions that affect a given fragment container
    145      * and tracks the shared element names across transactions. This is most useful
    146      * in pop transactions where the names of shared elements are known.
    147      *
    148      * @param containerId The container ID that is executing the transition.
    149      * @param records The list of transactions being executed.
    150      * @param isRecordPop For each transaction, whether it is a pop transaction or not.
    151      * @param startIndex The first index into records and isRecordPop to execute as
    152      *                   part of this transition.
    153      * @param endIndex One past the last index into records and isRecordPop to execute
    154      *                 as part of this transition.
    155      * @return A map from the initial shared element name to the final shared element name
    156      * before any onMapSharedElements is run.
    157      */
    158     private static ArrayMap<String, String> calculateNameOverrides(int containerId,
    159             ArrayList<BackStackRecord> records, ArrayList<Boolean> isRecordPop,
    160             int startIndex, int endIndex) {
    161         ArrayMap<String, String> nameOverrides = new ArrayMap<>();
    162         for (int recordNum = endIndex - 1; recordNum >= startIndex; recordNum--) {
    163             final BackStackRecord record = records.get(recordNum);
    164             if (!record.interactsWith(containerId)) {
    165                 continue;
    166             }
    167             final boolean isPop = isRecordPop.get(recordNum);
    168             if (record.mSharedElementSourceNames != null) {
    169                 final int numSharedElements = record.mSharedElementSourceNames.size();
    170                 final ArrayList<String> sources;
    171                 final ArrayList<String> targets;
    172                 if (isPop) {
    173                     targets = record.mSharedElementSourceNames;
    174                     sources = record.mSharedElementTargetNames;
    175                 } else {
    176                     sources = record.mSharedElementSourceNames;
    177                     targets = record.mSharedElementTargetNames;
    178                 }
    179                 for (int i = 0; i < numSharedElements; i++) {
    180                     String sourceName = sources.get(i);
    181                     String targetName = targets.get(i);
    182                     String previousTarget = nameOverrides.remove(targetName);
    183                     if (previousTarget != null) {
    184                         nameOverrides.put(sourceName, previousTarget);
    185                     } else {
    186                         nameOverrides.put(sourceName, targetName);
    187                     }
    188                 }
    189             }
    190         }
    191         return nameOverrides;
    192     }
    193 
    194     /**
    195      * Configures a transition for a single fragment container for which the transaction was
    196      * reordered. That means that all Fragment Views have been added and incoming fragment
    197      * Views are marked invisible.
    198      *
    199      * @param fragmentManager The executing FragmentManagerImpl
    200      * @param containerId The container ID that is executing the transition.
    201      * @param fragments A structure holding the transitioning fragments in this container.
    202      * @param nonExistentView A View that does not exist in the hierarchy. This is used to
    203      *                        prevent transitions from acting on other Views when there is no
    204      *                        other target.
    205      * @param nameOverrides A map of the shared element names from the starting fragment to
    206      *                      the final fragment's Views as given in
    207      *                      {@link FragmentTransaction#addSharedElement(View, String)}.
    208      */
    209     private static void configureTransitionsReordered(FragmentManagerImpl fragmentManager,
    210             int containerId, FragmentContainerTransition fragments,
    211             View nonExistentView, ArrayMap<String, String> nameOverrides) {
    212         ViewGroup sceneRoot = null;
    213         if (fragmentManager.mContainer.onHasView()) {
    214             sceneRoot = (ViewGroup) fragmentManager.mContainer.onFindViewById(containerId);
    215         }
    216         if (sceneRoot == null) {
    217             return;
    218         }
    219         final Fragment inFragment = fragments.lastIn;
    220         final Fragment outFragment = fragments.firstOut;
    221         final FragmentTransitionImpl impl = chooseImpl(outFragment, inFragment);
    222         if (impl == null) {
    223             return;
    224         }
    225         final boolean inIsPop = fragments.lastInIsPop;
    226         final boolean outIsPop = fragments.firstOutIsPop;
    227 
    228         ArrayList<View> sharedElementsIn = new ArrayList<>();
    229         ArrayList<View> sharedElementsOut = new ArrayList<>();
    230         Object enterTransition = getEnterTransition(impl, inFragment, inIsPop);
    231         Object exitTransition = getExitTransition(impl, outFragment, outIsPop);
    232 
    233         Object sharedElementTransition = configureSharedElementsReordered(impl, sceneRoot,
    234                 nonExistentView, nameOverrides, fragments, sharedElementsOut, sharedElementsIn,
    235                 enterTransition, exitTransition);
    236 
    237         if (enterTransition == null && sharedElementTransition == null
    238                 && exitTransition == null) {
    239             return; // no transitions!
    240         }
    241 
    242         ArrayList<View> exitingViews = configureEnteringExitingViews(impl, exitTransition,
    243                 outFragment, sharedElementsOut, nonExistentView);
    244 
    245         ArrayList<View> enteringViews = configureEnteringExitingViews(impl, enterTransition,
    246                 inFragment, sharedElementsIn, nonExistentView);
    247 
    248         setViewVisibility(enteringViews, View.INVISIBLE);
    249 
    250         Object transition = mergeTransitions(impl, enterTransition, exitTransition,
    251                 sharedElementTransition, inFragment, inIsPop);
    252 
    253         if (transition != null) {
    254             replaceHide(impl, exitTransition, outFragment, exitingViews);
    255             ArrayList<String> inNames =
    256                     impl.prepareSetNameOverridesReordered(sharedElementsIn);
    257             impl.scheduleRemoveTargets(transition,
    258                     enterTransition, enteringViews, exitTransition, exitingViews,
    259                     sharedElementTransition, sharedElementsIn);
    260             impl.beginDelayedTransition(sceneRoot, transition);
    261             impl.setNameOverridesReordered(sceneRoot, sharedElementsOut,
    262                     sharedElementsIn, inNames, nameOverrides);
    263             setViewVisibility(enteringViews, View.VISIBLE);
    264             impl.swapSharedElementTargets(sharedElementTransition,
    265                     sharedElementsOut, sharedElementsIn);
    266         }
    267     }
    268 
    269     /**
    270      * Replace hide operations with visibility changes on the exiting views. Instead of making
    271      * the entire fragment's view GONE, make each exiting view INVISIBLE. At the end of the
    272      * transition, make the fragment's view GONE.
    273      */
    274     private static void replaceHide(FragmentTransitionImpl impl,
    275             Object exitTransition, Fragment exitingFragment,
    276             final ArrayList<View> exitingViews) {
    277         if (exitingFragment != null && exitTransition != null && exitingFragment.mAdded
    278                 && exitingFragment.mHidden && exitingFragment.mHiddenChanged) {
    279             exitingFragment.setHideReplaced(true);
    280             impl.scheduleHideFragmentView(exitTransition,
    281                     exitingFragment.getView(), exitingViews);
    282             final ViewGroup container = exitingFragment.mContainer;
    283             OneShotPreDrawListener.add(container, new Runnable() {
    284                 @Override
    285                 public void run() {
    286                     setViewVisibility(exitingViews, View.INVISIBLE);
    287                 }
    288             });
    289         }
    290     }
    291 
    292     /**
    293      * Configures a transition for a single fragment container for which the transaction was
    294      * ordered. That means that the transaction has not been executed yet, so incoming
    295      * Views are not yet known.
    296      *
    297      * @param fragmentManager The executing FragmentManagerImpl
    298      * @param containerId The container ID that is executing the transition.
    299      * @param fragments A structure holding the transitioning fragments in this container.
    300      * @param nonExistentView A View that does not exist in the hierarchy. This is used to
    301      *                        prevent transitions from acting on other Views when there is no
    302      *                        other target.
    303      * @param nameOverrides A map of the shared element names from the starting fragment to
    304      *                      the final fragment's Views as given in
    305      *                      {@link FragmentTransaction#addSharedElement(View, String)}.
    306      */
    307     private static void configureTransitionsOrdered(FragmentManagerImpl fragmentManager,
    308             int containerId, FragmentContainerTransition fragments,
    309             View nonExistentView, ArrayMap<String, String> nameOverrides) {
    310         ViewGroup sceneRoot = null;
    311         if (fragmentManager.mContainer.onHasView()) {
    312             sceneRoot = (ViewGroup) fragmentManager.mContainer.onFindViewById(containerId);
    313         }
    314         if (sceneRoot == null) {
    315             return;
    316         }
    317         final Fragment inFragment = fragments.lastIn;
    318         final Fragment outFragment = fragments.firstOut;
    319         final FragmentTransitionImpl impl = chooseImpl(outFragment, inFragment);
    320         if (impl == null) {
    321             return;
    322         }
    323         final boolean inIsPop = fragments.lastInIsPop;
    324         final boolean outIsPop = fragments.firstOutIsPop;
    325 
    326         Object enterTransition = getEnterTransition(impl, inFragment, inIsPop);
    327         Object exitTransition = getExitTransition(impl, outFragment, outIsPop);
    328 
    329         ArrayList<View> sharedElementsOut = new ArrayList<>();
    330         ArrayList<View> sharedElementsIn = new ArrayList<>();
    331 
    332         Object sharedElementTransition = configureSharedElementsOrdered(impl, sceneRoot,
    333                 nonExistentView, nameOverrides, fragments, sharedElementsOut, sharedElementsIn,
    334                 enterTransition, exitTransition);
    335 
    336         if (enterTransition == null && sharedElementTransition == null
    337                 && exitTransition == null) {
    338             return; // no transitions!
    339         }
    340 
    341         ArrayList<View> exitingViews = configureEnteringExitingViews(impl, exitTransition,
    342                 outFragment, sharedElementsOut, nonExistentView);
    343 
    344         if (exitingViews == null || exitingViews.isEmpty()) {
    345             exitTransition = null;
    346         }
    347 
    348         // Ensure the entering transition doesn't target anything until the views are made
    349         // visible
    350         impl.addTarget(enterTransition, nonExistentView);
    351 
    352         Object transition = mergeTransitions(impl, enterTransition, exitTransition,
    353                 sharedElementTransition, inFragment, fragments.lastInIsPop);
    354 
    355         if (transition != null) {
    356             final ArrayList<View> enteringViews = new ArrayList<>();
    357             impl.scheduleRemoveTargets(transition,
    358                     enterTransition, enteringViews, exitTransition, exitingViews,
    359                     sharedElementTransition, sharedElementsIn);
    360             scheduleTargetChange(impl, sceneRoot, inFragment, nonExistentView, sharedElementsIn,
    361                     enterTransition, enteringViews, exitTransition, exitingViews);
    362             impl.setNameOverridesOrdered(sceneRoot, sharedElementsIn, nameOverrides);
    363 
    364             impl.beginDelayedTransition(sceneRoot, transition);
    365             impl.scheduleNameReset(sceneRoot, sharedElementsIn, nameOverrides);
    366         }
    367     }
    368 
    369     /**
    370      * This method is used for fragment transitions for ordrered transactions to change the
    371      * enter and exit transition targets after the call to
    372      * {@link FragmentTransitionCompat21#beginDelayedTransition(ViewGroup, Object)}. The exit
    373      * transition must ensure that it does not target any Views and the enter transition must start
    374      * targeting the Views of the incoming Fragment.
    375      *
    376      * @param sceneRoot The fragment container View
    377      * @param inFragment The last fragment that is entering
    378      * @param nonExistentView A view that does not exist in the hierarchy that is used as a
    379      *                        transition target to ensure no View is targeted.
    380      * @param sharedElementsIn The shared element Views of the incoming fragment
    381      * @param enterTransition The enter transition of the incoming fragment
    382      * @param enteringViews The entering Views of the incoming fragment
    383      * @param exitTransition The exit transition of the outgoing fragment
    384      * @param exitingViews The exiting views of the outgoing fragment
    385      */
    386     private static void scheduleTargetChange(final FragmentTransitionImpl impl,
    387             final ViewGroup sceneRoot,
    388             final Fragment inFragment, final View nonExistentView,
    389             final ArrayList<View> sharedElementsIn,
    390             final Object enterTransition, final ArrayList<View> enteringViews,
    391             final Object exitTransition, final ArrayList<View> exitingViews) {
    392         OneShotPreDrawListener.add(sceneRoot, new Runnable() {
    393             @Override
    394             public void run() {
    395                 if (enterTransition != null) {
    396                     impl.removeTarget(enterTransition,
    397                             nonExistentView);
    398                     ArrayList<View> views = configureEnteringExitingViews(impl,
    399                             enterTransition, inFragment, sharedElementsIn, nonExistentView);
    400                     enteringViews.addAll(views);
    401                 }
    402 
    403                 if (exitingViews != null) {
    404                     if (exitTransition != null) {
    405                         ArrayList<View> tempExiting = new ArrayList<>();
    406                         tempExiting.add(nonExistentView);
    407                         impl.replaceTargets(exitTransition, exitingViews,
    408                                 tempExiting);
    409                     }
    410                     exitingViews.clear();
    411                     exitingViews.add(nonExistentView);
    412                 }
    413             }
    414         });
    415     }
    416 
    417     /**
    418      * Chooses the appropriate implementation depending on the Transition instances hold by the
    419      * Fragments.
    420      */
    421     private static FragmentTransitionImpl chooseImpl(Fragment outFragment, Fragment inFragment) {
    422         // Collect all transition instances
    423         final ArrayList<Object> transitions = new ArrayList<>();
    424         if (outFragment != null) {
    425             final Object exitTransition = outFragment.getExitTransition();
    426             if (exitTransition != null) {
    427                 transitions.add(exitTransition);
    428             }
    429             final Object returnTransition = outFragment.getReturnTransition();
    430             if (returnTransition != null) {
    431                 transitions.add(returnTransition);
    432             }
    433             final Object sharedReturnTransition = outFragment.getSharedElementReturnTransition();
    434             if (sharedReturnTransition != null) {
    435                 transitions.add(sharedReturnTransition);
    436             }
    437         }
    438         if (inFragment != null) {
    439             final Object enterTransition = inFragment.getEnterTransition();
    440             if (enterTransition != null) {
    441                 transitions.add(enterTransition);
    442             }
    443             final Object reenterTransition = inFragment.getReenterTransition();
    444             if (reenterTransition != null) {
    445                 transitions.add(reenterTransition);
    446             }
    447             final Object sharedEnterTransition = inFragment.getSharedElementEnterTransition();
    448             if (sharedEnterTransition != null) {
    449                 transitions.add(sharedEnterTransition);
    450             }
    451         }
    452         if (transitions.isEmpty()) {
    453             return null; // No transition to run
    454         }
    455         // Pick the implementation that can handle all the transitions
    456         if (PLATFORM_IMPL != null && canHandleAll(PLATFORM_IMPL, transitions)) {
    457             return PLATFORM_IMPL;
    458         }
    459         if (SUPPORT_IMPL != null && canHandleAll(SUPPORT_IMPL, transitions)) {
    460             return SUPPORT_IMPL;
    461         }
    462         if (PLATFORM_IMPL != null || SUPPORT_IMPL != null) {
    463             throw new IllegalArgumentException("Invalid Transition types");
    464         }
    465         return null;
    466     }
    467 
    468     private static boolean canHandleAll(FragmentTransitionImpl impl, List<Object> transitions) {
    469         for (int i = 0, size = transitions.size(); i < size; i++) {
    470             if (!impl.canHandle(transitions.get(i))) {
    471                 return false;
    472             }
    473         }
    474         return true;
    475     }
    476 
    477     /**
    478      * Returns a TransitionSet containing the shared element transition. The wrapping TransitionSet
    479      * targets all shared elements to ensure that no other Views are targeted. The shared element
    480      * transition can then target any or all shared elements without worrying about accidentally
    481      * targeting entering or exiting Views.
    482      *
    483      * @param inFragment The incoming fragment
    484      * @param outFragment the outgoing fragment
    485      * @param isPop True if this is a pop transaction or false if it is a normal (add) transaction.
    486      * @return A TransitionSet wrapping the shared element transition or null if no such transition
    487      * exists.
    488      */
    489     private static Object getSharedElementTransition(FragmentTransitionImpl impl,
    490             Fragment inFragment, Fragment outFragment, boolean isPop) {
    491         if (inFragment == null || outFragment == null) {
    492             return null;
    493         }
    494         Object transition = impl.cloneTransition(isPop
    495                 ? outFragment.getSharedElementReturnTransition()
    496                 : inFragment.getSharedElementEnterTransition());
    497         return impl.wrapTransitionInSet(transition);
    498     }
    499 
    500     /**
    501      * Returns a clone of the enter transition or null if no such transition exists.
    502      */
    503     private static Object getEnterTransition(FragmentTransitionImpl impl,
    504             Fragment inFragment, boolean isPop) {
    505         if (inFragment == null) {
    506             return null;
    507         }
    508         return impl.cloneTransition(isPop
    509                 ? inFragment.getReenterTransition()
    510                 : inFragment.getEnterTransition());
    511     }
    512 
    513     /**
    514      * Returns a clone of the exit transition or null if no such transition exists.
    515      */
    516     private static Object getExitTransition(FragmentTransitionImpl impl,
    517             Fragment outFragment, boolean isPop) {
    518         if (outFragment == null) {
    519             return null;
    520         }
    521         return impl.cloneTransition(isPop
    522                 ? outFragment.getReturnTransition()
    523                 : outFragment.getExitTransition());
    524     }
    525 
    526     /**
    527      * Configures the shared elements of a reordered fragment transaction's transition.
    528      * This retrieves the shared elements of the outgoing and incoming fragments, maps the
    529      * views, and sets up the epicenter on the transitions.
    530      * <p>
    531      * The epicenter of exit and shared element transitions is the first shared element
    532      * in the outgoing fragment. The epicenter of the entering transition is the first shared
    533      * element in the incoming fragment.
    534      *
    535      * @param sceneRoot The fragment container View
    536      * @param nonExistentView A View that does not exist in the hierarchy. This is used to
    537      *                        prevent transitions from acting on other Views when there is no
    538      *                        other target.
    539      * @param nameOverrides A map of the shared element names from the starting fragment to
    540      *                      the final fragment's Views as given in
    541      *                      {@link FragmentTransaction#addSharedElement(View, String)}.
    542      * @param fragments A structure holding the transitioning fragments in this container.
    543      * @param sharedElementsOut A list modified to contain the shared elements in the outgoing
    544      *                          fragment
    545      * @param sharedElementsIn A list modified to contain the shared elements in the incoming
    546      *                         fragment
    547      * @param enterTransition The transition used for entering Views, modified by applying the
    548      *                        epicenter
    549      * @param exitTransition The transition used for exiting Views, modified by applying the
    550      *                       epicenter
    551      * @return The shared element transition or null if no shared elements exist
    552      */
    553     private static Object configureSharedElementsReordered(final FragmentTransitionImpl impl,
    554             final ViewGroup sceneRoot,
    555             final View nonExistentView, final ArrayMap<String, String> nameOverrides,
    556             final FragmentContainerTransition fragments,
    557             final ArrayList<View> sharedElementsOut,
    558             final ArrayList<View> sharedElementsIn,
    559             final Object enterTransition, final Object exitTransition) {
    560         final Fragment inFragment = fragments.lastIn;
    561         final Fragment outFragment = fragments.firstOut;
    562         if (inFragment != null) {
    563             inFragment.getView().setVisibility(View.VISIBLE);
    564         }
    565         if (inFragment == null || outFragment == null) {
    566             return null; // no shared element without a fragment
    567         }
    568 
    569         final boolean inIsPop = fragments.lastInIsPop;
    570         Object sharedElementTransition = nameOverrides.isEmpty() ? null
    571                 : getSharedElementTransition(impl, inFragment, outFragment, inIsPop);
    572 
    573         final ArrayMap<String, View> outSharedElements = captureOutSharedElements(impl,
    574                 nameOverrides, sharedElementTransition, fragments);
    575 
    576         final ArrayMap<String, View> inSharedElements = captureInSharedElements(impl,
    577                 nameOverrides, sharedElementTransition, fragments);
    578 
    579         if (nameOverrides.isEmpty()) {
    580             sharedElementTransition = null;
    581             if (outSharedElements != null) {
    582                 outSharedElements.clear();
    583             }
    584             if (inSharedElements != null) {
    585                 inSharedElements.clear();
    586             }
    587         } else {
    588             addSharedElementsWithMatchingNames(sharedElementsOut, outSharedElements,
    589                     nameOverrides.keySet());
    590             addSharedElementsWithMatchingNames(sharedElementsIn, inSharedElements,
    591                     nameOverrides.values());
    592         }
    593 
    594         if (enterTransition == null && exitTransition == null && sharedElementTransition == null) {
    595             // don't call onSharedElementStart/End since there is no transition
    596             return null;
    597         }
    598 
    599         callSharedElementStartEnd(inFragment, outFragment, inIsPop, outSharedElements, true);
    600 
    601         final Rect epicenter;
    602         final View epicenterView;
    603         if (sharedElementTransition != null) {
    604             sharedElementsIn.add(nonExistentView);
    605             impl.setSharedElementTargets(sharedElementTransition,
    606                     nonExistentView, sharedElementsOut);
    607             final boolean outIsPop = fragments.firstOutIsPop;
    608             final BackStackRecord outTransaction = fragments.firstOutTransaction;
    609             setOutEpicenter(impl, sharedElementTransition, exitTransition, outSharedElements,
    610                     outIsPop, outTransaction);
    611             epicenter = new Rect();
    612             epicenterView = getInEpicenterView(inSharedElements, fragments,
    613                     enterTransition, inIsPop);
    614             if (epicenterView != null) {
    615                 impl.setEpicenter(enterTransition, epicenter);
    616             }
    617         } else {
    618             epicenter = null;
    619             epicenterView = null;
    620         }
    621 
    622         OneShotPreDrawListener.add(sceneRoot, new Runnable() {
    623             @Override
    624             public void run() {
    625                 callSharedElementStartEnd(inFragment, outFragment, inIsPop,
    626                         inSharedElements, false);
    627                 if (epicenterView != null) {
    628                     impl.getBoundsOnScreen(epicenterView, epicenter);
    629                 }
    630             }
    631         });
    632         return sharedElementTransition;
    633     }
    634 
    635     /**
    636      * Add Views from sharedElements into views that have the transitionName in the
    637      * nameOverridesSet.
    638      *
    639      * @param views               Views list to add shared elements to
    640      * @param sharedElements      List of shared elements
    641      * @param nameOverridesSet    The transition names for all views to be copied from
    642      *                            sharedElements to views.
    643      */
    644     private static void addSharedElementsWithMatchingNames(ArrayList<View> views,
    645             ArrayMap<String, View> sharedElements, Collection<String> nameOverridesSet) {
    646         for (int i = sharedElements.size() - 1; i >= 0; i--) {
    647             View view = sharedElements.valueAt(i);
    648             if (nameOverridesSet.contains(ViewCompat.getTransitionName(view))) {
    649                 views.add(view);
    650             }
    651         }
    652     }
    653 
    654     /**
    655      * Configures the shared elements of an ordered fragment transaction's transition.
    656      * This retrieves the shared elements of the incoming fragments, and schedules capturing
    657      * the incoming fragment's shared elements. It also maps the views, and sets up the epicenter
    658      * on the transitions.
    659      * <p>
    660      * The epicenter of exit and shared element transitions is the first shared element
    661      * in the outgoing fragment. The epicenter of the entering transition is the first shared
    662      * element in the incoming fragment.
    663      *
    664      * @param sceneRoot The fragment container View
    665      * @param nonExistentView A View that does not exist in the hierarchy. This is used to
    666      *                        prevent transitions from acting on other Views when there is no
    667      *                        other target.
    668      * @param nameOverrides A map of the shared element names from the starting fragment to
    669      *                      the final fragment's Views as given in
    670      *                      {@link FragmentTransaction#addSharedElement(View, String)}.
    671      * @param fragments A structure holding the transitioning fragments in this container.
    672      * @param sharedElementsOut A list modified to contain the shared elements in the outgoing
    673      *                          fragment
    674      * @param sharedElementsIn A list modified to contain the shared elements in the incoming
    675      *                         fragment
    676      * @param enterTransition The transition used for entering Views, modified by applying the
    677      *                        epicenter
    678      * @param exitTransition The transition used for exiting Views, modified by applying the
    679      *                       epicenter
    680      * @return The shared element transition or null if no shared elements exist
    681      */
    682     private static Object configureSharedElementsOrdered(final FragmentTransitionImpl impl,
    683             final ViewGroup sceneRoot,
    684             final View nonExistentView, final ArrayMap<String, String> nameOverrides,
    685             final FragmentContainerTransition fragments,
    686             final ArrayList<View> sharedElementsOut,
    687             final ArrayList<View> sharedElementsIn,
    688             final Object enterTransition, final Object exitTransition) {
    689         final Fragment inFragment = fragments.lastIn;
    690         final Fragment outFragment = fragments.firstOut;
    691 
    692         if (inFragment == null || outFragment == null) {
    693             return null; // no transition
    694         }
    695 
    696         final boolean inIsPop = fragments.lastInIsPop;
    697         Object sharedElementTransition = nameOverrides.isEmpty() ? null
    698                 : getSharedElementTransition(impl, inFragment, outFragment, inIsPop);
    699 
    700         ArrayMap<String, View> outSharedElements = captureOutSharedElements(impl, nameOverrides,
    701                 sharedElementTransition, fragments);
    702 
    703         if (nameOverrides.isEmpty()) {
    704             sharedElementTransition = null;
    705         } else {
    706             sharedElementsOut.addAll(outSharedElements.values());
    707         }
    708 
    709         if (enterTransition == null && exitTransition == null && sharedElementTransition == null) {
    710             // don't call onSharedElementStart/End since there is no transition
    711             return null;
    712         }
    713 
    714         callSharedElementStartEnd(inFragment, outFragment, inIsPop, outSharedElements, true);
    715 
    716         final Rect inEpicenter;
    717         if (sharedElementTransition != null) {
    718             inEpicenter = new Rect();
    719             impl.setSharedElementTargets(sharedElementTransition,
    720                     nonExistentView, sharedElementsOut);
    721             final boolean outIsPop = fragments.firstOutIsPop;
    722             final BackStackRecord outTransaction = fragments.firstOutTransaction;
    723             setOutEpicenter(impl, sharedElementTransition, exitTransition, outSharedElements,
    724                     outIsPop, outTransaction);
    725             if (enterTransition != null) {
    726                 impl.setEpicenter(enterTransition, inEpicenter);
    727             }
    728         } else {
    729             inEpicenter = null;
    730         }
    731 
    732 
    733         final Object finalSharedElementTransition = sharedElementTransition;
    734         OneShotPreDrawListener.add(sceneRoot, new Runnable() {
    735             @Override
    736             public void run() {
    737                 ArrayMap<String, View> inSharedElements = captureInSharedElements(impl,
    738                         nameOverrides, finalSharedElementTransition, fragments);
    739 
    740                 if (inSharedElements != null) {
    741                     sharedElementsIn.addAll(inSharedElements.values());
    742                     sharedElementsIn.add(nonExistentView);
    743                 }
    744 
    745                 callSharedElementStartEnd(inFragment, outFragment, inIsPop,
    746                         inSharedElements, false);
    747                 if (finalSharedElementTransition != null) {
    748                     impl.swapSharedElementTargets(
    749                             finalSharedElementTransition, sharedElementsOut,
    750                             sharedElementsIn);
    751 
    752                     final View inEpicenterView = getInEpicenterView(inSharedElements,
    753                             fragments, enterTransition, inIsPop);
    754                     if (inEpicenterView != null) {
    755                         impl.getBoundsOnScreen(inEpicenterView,
    756                                 inEpicenter);
    757                     }
    758                 }
    759             }
    760         });
    761 
    762         return sharedElementTransition;
    763     }
    764 
    765     /**
    766      * Finds the shared elements in the outgoing fragment. It also calls
    767      * {@link SharedElementCallback#onMapSharedElements(List, Map)} to allow more control
    768      * of the shared element mapping. {@code nameOverrides} is updated to match the
    769      * actual transition name of the mapped shared elements.
    770      *
    771      * @param nameOverrides A map of the shared element names from the starting fragment to
    772      *                      the final fragment's Views as given in
    773      *                      {@link FragmentTransaction#addSharedElement(View, String)}.
    774      * @param sharedElementTransition The shared element transition
    775      * @param fragments A structure holding the transitioning fragments in this container.
    776      * @return The mapping of shared element names to the Views in the hierarchy or null
    777      * if there is no shared element transition.
    778      */
    779     private static ArrayMap<String, View> captureOutSharedElements(FragmentTransitionImpl impl,
    780             ArrayMap<String, String> nameOverrides, Object sharedElementTransition,
    781             FragmentContainerTransition fragments) {
    782         if (nameOverrides.isEmpty() || sharedElementTransition == null) {
    783             nameOverrides.clear();
    784             return null;
    785         }
    786         final Fragment outFragment = fragments.firstOut;
    787         final ArrayMap<String, View> outSharedElements = new ArrayMap<>();
    788         impl.findNamedViews(outSharedElements, outFragment.getView());
    789 
    790         final SharedElementCallback sharedElementCallback;
    791         final ArrayList<String> names;
    792         final BackStackRecord outTransaction = fragments.firstOutTransaction;
    793         if (fragments.firstOutIsPop) {
    794             sharedElementCallback = outFragment.getEnterTransitionCallback();
    795             names = outTransaction.mSharedElementTargetNames;
    796         } else {
    797             sharedElementCallback = outFragment.getExitTransitionCallback();
    798             names = outTransaction.mSharedElementSourceNames;
    799         }
    800 
    801         outSharedElements.retainAll(names);
    802         if (sharedElementCallback != null) {
    803             sharedElementCallback.onMapSharedElements(names, outSharedElements);
    804             for (int i = names.size() - 1; i >= 0; i--) {
    805                 String name = names.get(i);
    806                 View view = outSharedElements.get(name);
    807                 if (view == null) {
    808                     nameOverrides.remove(name);
    809                 } else if (!name.equals(ViewCompat.getTransitionName(view))) {
    810                     String targetValue = nameOverrides.remove(name);
    811                     nameOverrides.put(ViewCompat.getTransitionName(view), targetValue);
    812                 }
    813             }
    814         } else {
    815             nameOverrides.retainAll(outSharedElements.keySet());
    816         }
    817         return outSharedElements;
    818     }
    819 
    820     /**
    821      * Finds the shared elements in the incoming fragment. It also calls
    822      * {@link SharedElementCallback#onMapSharedElements(List, Map)} to allow more control
    823      * of the shared element mapping. {@code nameOverrides} is updated to match the
    824      * actual transition name of the mapped shared elements.
    825      *
    826      * @param nameOverrides A map of the shared element names from the starting fragment to
    827      *                      the final fragment's Views as given in
    828      *                      {@link FragmentTransaction#addSharedElement(View, String)}.
    829      * @param sharedElementTransition The shared element transition
    830      * @param fragments A structure holding the transitioning fragments in this container.
    831      * @return The mapping of shared element names to the Views in the hierarchy or null
    832      * if there is no shared element transition.
    833      */
    834     private static ArrayMap<String, View> captureInSharedElements(FragmentTransitionImpl impl,
    835             ArrayMap<String, String> nameOverrides, Object sharedElementTransition,
    836             FragmentContainerTransition fragments) {
    837         Fragment inFragment = fragments.lastIn;
    838         final View fragmentView = inFragment.getView();
    839         if (nameOverrides.isEmpty() || sharedElementTransition == null || fragmentView == null) {
    840             nameOverrides.clear();
    841             return null;
    842         }
    843         final ArrayMap<String, View> inSharedElements = new ArrayMap<>();
    844         impl.findNamedViews(inSharedElements, fragmentView);
    845 
    846         final SharedElementCallback sharedElementCallback;
    847         final ArrayList<String> names;
    848         final BackStackRecord inTransaction = fragments.lastInTransaction;
    849         if (fragments.lastInIsPop) {
    850             sharedElementCallback = inFragment.getExitTransitionCallback();
    851             names = inTransaction.mSharedElementSourceNames;
    852         } else {
    853             sharedElementCallback = inFragment.getEnterTransitionCallback();
    854             names = inTransaction.mSharedElementTargetNames;
    855         }
    856 
    857         if (names != null) {
    858             inSharedElements.retainAll(names);
    859             inSharedElements.retainAll(nameOverrides.values());
    860         }
    861         if (sharedElementCallback != null) {
    862             sharedElementCallback.onMapSharedElements(names, inSharedElements);
    863             for (int i = names.size() - 1; i >= 0; i--) {
    864                 String name = names.get(i);
    865                 View view = inSharedElements.get(name);
    866                 if (view == null) {
    867                     String key = findKeyForValue(nameOverrides, name);
    868                     if (key != null) {
    869                         nameOverrides.remove(key);
    870                     }
    871                 } else if (!name.equals(ViewCompat.getTransitionName(view))) {
    872                     String key = findKeyForValue(nameOverrides, name);
    873                     if (key != null) {
    874                         nameOverrides.put(key, ViewCompat.getTransitionName(view));
    875                     }
    876                 }
    877             }
    878         } else {
    879             retainValues(nameOverrides, inSharedElements);
    880         }
    881         return inSharedElements;
    882     }
    883 
    884     /**
    885      * Utility to find the String key in {@code map} that maps to {@code value}.
    886      */
    887     private static String findKeyForValue(ArrayMap<String, String> map, String value) {
    888         final int numElements = map.size();
    889         for (int i = 0; i < numElements; i++) {
    890             if (value.equals(map.valueAt(i))) {
    891                 return map.keyAt(i);
    892             }
    893         }
    894         return null;
    895     }
    896 
    897     /**
    898      * Returns the View in the incoming Fragment that should be used as the epicenter.
    899      *
    900      * @param inSharedElements The mapping of shared element names to Views in the
    901      *                         incoming fragment.
    902      * @param fragments A structure holding the transitioning fragments in this container.
    903      * @param enterTransition The transition used for the incoming Fragment's views
    904      * @param inIsPop Is the incoming fragment being added as a pop transaction?
    905      */
    906     private static View getInEpicenterView(ArrayMap<String, View> inSharedElements,
    907             FragmentContainerTransition fragments,
    908             Object enterTransition, boolean inIsPop) {
    909         BackStackRecord inTransaction = fragments.lastInTransaction;
    910         if (enterTransition != null && inSharedElements != null
    911                 && inTransaction.mSharedElementSourceNames != null
    912                 && !inTransaction.mSharedElementSourceNames.isEmpty()) {
    913             final String targetName = inIsPop
    914                     ? inTransaction.mSharedElementSourceNames.get(0)
    915                     : inTransaction.mSharedElementTargetNames.get(0);
    916             return inSharedElements.get(targetName);
    917         }
    918         return null;
    919     }
    920 
    921     /**
    922      * Sets the epicenter for the exit transition.
    923      *
    924      * @param sharedElementTransition The shared element transition
    925      * @param exitTransition The transition for the outgoing fragment's views
    926      * @param outSharedElements Shared elements in the outgoing fragment
    927      * @param outIsPop Is the outgoing fragment being removed as a pop transaction?
    928      * @param outTransaction The transaction that caused the fragment to be removed.
    929      */
    930     private static void setOutEpicenter(FragmentTransitionImpl impl, Object sharedElementTransition,
    931             Object exitTransition, ArrayMap<String, View> outSharedElements, boolean outIsPop,
    932             BackStackRecord outTransaction) {
    933         if (outTransaction.mSharedElementSourceNames != null
    934                 && !outTransaction.mSharedElementSourceNames.isEmpty()) {
    935             final String sourceName = outIsPop
    936                     ? outTransaction.mSharedElementTargetNames.get(0)
    937                     : outTransaction.mSharedElementSourceNames.get(0);
    938             final View outEpicenterView = outSharedElements.get(sourceName);
    939             impl.setEpicenter(sharedElementTransition, outEpicenterView);
    940 
    941             if (exitTransition != null) {
    942                 impl.setEpicenter(exitTransition, outEpicenterView);
    943             }
    944         }
    945     }
    946 
    947     /**
    948      * A utility to retain only the mappings in {@code nameOverrides} that have a value
    949      * that has a key in {@code namedViews}. This is a useful equivalent to
    950      * {@link ArrayMap#retainAll(Collection)} for values.
    951      */
    952     private static void retainValues(ArrayMap<String, String> nameOverrides,
    953             ArrayMap<String, View> namedViews) {
    954         for (int i = nameOverrides.size() - 1; i >= 0; i--) {
    955             final String targetName = nameOverrides.valueAt(i);
    956             if (!namedViews.containsKey(targetName)) {
    957                 nameOverrides.removeAt(i);
    958             }
    959         }
    960     }
    961 
    962     /**
    963      * Calls the {@link SharedElementCallback#onSharedElementStart(List, List, List)} or
    964      * {@link SharedElementCallback#onSharedElementEnd(List, List, List)} on the appropriate
    965      * incoming or outgoing fragment.
    966      *
    967      * @param inFragment The incoming fragment
    968      * @param outFragment The outgoing fragment
    969      * @param isPop Is the incoming fragment part of a pop transaction?
    970      * @param sharedElements The shared element Views
    971      * @param isStart Call the start or end call on the SharedElementCallback
    972      */
    973     private static void callSharedElementStartEnd(Fragment inFragment, Fragment outFragment,
    974             boolean isPop, ArrayMap<String, View> sharedElements, boolean isStart) {
    975         SharedElementCallback sharedElementCallback = isPop
    976                 ? outFragment.getEnterTransitionCallback()
    977                 : inFragment.getEnterTransitionCallback();
    978         if (sharedElementCallback != null) {
    979             ArrayList<View> views = new ArrayList<>();
    980             ArrayList<String> names = new ArrayList<>();
    981             final int count = sharedElements == null ? 0 : sharedElements.size();
    982             for (int i = 0; i < count; i++) {
    983                 names.add(sharedElements.keyAt(i));
    984                 views.add(sharedElements.valueAt(i));
    985             }
    986             if (isStart) {
    987                 sharedElementCallback.onSharedElementStart(names, views, null);
    988             } else {
    989                 sharedElementCallback.onSharedElementEnd(names, views, null);
    990             }
    991         }
    992     }
    993 
    994     private static ArrayList<View> configureEnteringExitingViews(FragmentTransitionImpl impl,
    995             Object transition,
    996             Fragment fragment, ArrayList<View> sharedElements, View nonExistentView) {
    997         ArrayList<View> viewList = null;
    998         if (transition != null) {
    999             viewList = new ArrayList<>();
   1000             View root = fragment.getView();
   1001             if (root != null) {
   1002                 impl.captureTransitioningViews(viewList, root);
   1003             }
   1004             if (sharedElements != null) {
   1005                 viewList.removeAll(sharedElements);
   1006             }
   1007             if (!viewList.isEmpty()) {
   1008                 viewList.add(nonExistentView);
   1009                 impl.addTargets(transition, viewList);
   1010             }
   1011         }
   1012         return viewList;
   1013     }
   1014 
   1015     /**
   1016      * Sets the visibility of all Views in {@code views} to {@code visibility}.
   1017      */
   1018     private static void setViewVisibility(ArrayList<View> views, int visibility) {
   1019         if (views == null) {
   1020             return;
   1021         }
   1022         for (int i = views.size() - 1; i >= 0; i--) {
   1023             final View view = views.get(i);
   1024             view.setVisibility(visibility);
   1025         }
   1026     }
   1027 
   1028     /**
   1029      * Merges exit, shared element, and enter transitions so that they act together or
   1030      * sequentially as defined in the fragments.
   1031      */
   1032     private static Object mergeTransitions(FragmentTransitionImpl impl, Object enterTransition,
   1033             Object exitTransition, Object sharedElementTransition, Fragment inFragment,
   1034             boolean isPop) {
   1035         boolean overlap = true;
   1036         if (enterTransition != null && exitTransition != null && inFragment != null) {
   1037             overlap = isPop ? inFragment.getAllowReturnTransitionOverlap() :
   1038                     inFragment.getAllowEnterTransitionOverlap();
   1039         }
   1040 
   1041         // Wrap the transitions. Explicit targets like in enter and exit will cause the
   1042         // views to be targeted regardless of excluded views. If that happens, then the
   1043         // excluded fragments views (hidden fragments) will still be in the transition.
   1044 
   1045         Object transition;
   1046         if (overlap) {
   1047             // Regular transition -- do it all together
   1048             transition = impl.mergeTransitionsTogether(exitTransition,
   1049                     enterTransition, sharedElementTransition);
   1050         } else {
   1051             // First do exit, then enter, but allow shared element transition to happen
   1052             // during both.
   1053             transition = impl.mergeTransitionsInSequence(exitTransition,
   1054                     enterTransition, sharedElementTransition);
   1055         }
   1056         return transition;
   1057     }
   1058 
   1059     /**
   1060      * Finds the first removed fragment and last added fragments when going forward.
   1061      * If none of the fragments have transitions, then both lists will be empty.
   1062      *
   1063      * @param transitioningFragments Keyed on the container ID, the first fragments to be removed,
   1064      *                               and last fragments to be added. This will be modified by
   1065      *                               this method.
   1066      */
   1067     public static void calculateFragments(BackStackRecord transaction,
   1068             SparseArray<FragmentContainerTransition> transitioningFragments,
   1069             boolean isReordered) {
   1070         final int numOps = transaction.mOps.size();
   1071         for (int opNum = 0; opNum < numOps; opNum++) {
   1072             final BackStackRecord.Op op = transaction.mOps.get(opNum);
   1073             addToFirstInLastOut(transaction, op, transitioningFragments, false, isReordered);
   1074         }
   1075     }
   1076 
   1077     /**
   1078      * Finds the first removed fragment and last added fragments when popping the back stack.
   1079      * If none of the fragments have transitions, then both lists will be empty.
   1080      *
   1081      * @param transitioningFragments Keyed on the container ID, the first fragments to be removed,
   1082      *                               and last fragments to be added. This will be modified by
   1083      *                               this method.
   1084      */
   1085     public static void calculatePopFragments(BackStackRecord transaction,
   1086             SparseArray<FragmentContainerTransition> transitioningFragments, boolean isReordered) {
   1087         if (!transaction.mManager.mContainer.onHasView()) {
   1088             return; // nothing to see, so no transitions
   1089         }
   1090         final int numOps = transaction.mOps.size();
   1091         for (int opNum = numOps - 1; opNum >= 0; opNum--) {
   1092             final BackStackRecord.Op op = transaction.mOps.get(opNum);
   1093             addToFirstInLastOut(transaction, op, transitioningFragments, true, isReordered);
   1094         }
   1095     }
   1096 
   1097     static boolean supportsTransition() {
   1098         return PLATFORM_IMPL != null || SUPPORT_IMPL != null;
   1099     }
   1100 
   1101     /**
   1102      * Examines the {@code command} and may set the first out or last in fragment for the fragment's
   1103      * container.
   1104      *
   1105      * @param transaction The executing transaction
   1106      * @param op The operation being run.
   1107      * @param transitioningFragments A structure holding the first in and last out fragments
   1108      *                               for each fragment container.
   1109      * @param isPop Is the operation a pop?
   1110      * @param isReorderedTransaction True if the operations have been partially executed and the
   1111      *                               added fragments have Views in the hierarchy or false if the
   1112      *                               operations haven't been executed yet.
   1113      */
   1114     @SuppressWarnings("ReferenceEquality")
   1115     private static void addToFirstInLastOut(BackStackRecord transaction, BackStackRecord.Op op,
   1116             SparseArray<FragmentContainerTransition> transitioningFragments, boolean isPop,
   1117             boolean isReorderedTransaction) {
   1118         final Fragment fragment = op.fragment;
   1119         if (fragment == null) {
   1120             return; // no fragment, no transition
   1121         }
   1122         final int containerId = fragment.mContainerId;
   1123         if (containerId == 0) {
   1124             return; // no container, no transition
   1125         }
   1126         final int command = isPop ? INVERSE_OPS[op.cmd] : op.cmd;
   1127         boolean setLastIn = false;
   1128         boolean wasRemoved = false;
   1129         boolean setFirstOut = false;
   1130         boolean wasAdded = false;
   1131         switch (command) {
   1132             case BackStackRecord.OP_SHOW:
   1133                 if (isReorderedTransaction) {
   1134                     setLastIn = fragment.mHiddenChanged && !fragment.mHidden && fragment.mAdded;
   1135                 } else {
   1136                     setLastIn = fragment.mHidden;
   1137                 }
   1138                 wasAdded = true;
   1139                 break;
   1140             case BackStackRecord.OP_ADD:
   1141             case BackStackRecord.OP_ATTACH:
   1142                 if (isReorderedTransaction) {
   1143                     setLastIn = fragment.mIsNewlyAdded;
   1144                 } else {
   1145                     setLastIn = !fragment.mAdded && !fragment.mHidden;
   1146                 }
   1147                 wasAdded = true;
   1148                 break;
   1149             case BackStackRecord.OP_HIDE:
   1150                 if (isReorderedTransaction) {
   1151                     setFirstOut = fragment.mHiddenChanged && fragment.mAdded && fragment.mHidden;
   1152                 } else {
   1153                     setFirstOut = fragment.mAdded && !fragment.mHidden;
   1154                 }
   1155                 wasRemoved = true;
   1156                 break;
   1157             case BackStackRecord.OP_REMOVE:
   1158             case BackStackRecord.OP_DETACH:
   1159                 if (isReorderedTransaction) {
   1160                     setFirstOut = !fragment.mAdded && fragment.mView != null
   1161                             && fragment.mView.getVisibility() == View.VISIBLE
   1162                             && fragment.mPostponedAlpha >= 0;
   1163                 } else {
   1164                     setFirstOut = fragment.mAdded && !fragment.mHidden;
   1165                 }
   1166                 wasRemoved = true;
   1167                 break;
   1168         }
   1169         FragmentContainerTransition containerTransition = transitioningFragments.get(containerId);
   1170         if (setLastIn) {
   1171             containerTransition =
   1172                     ensureContainer(containerTransition, transitioningFragments, containerId);
   1173             containerTransition.lastIn = fragment;
   1174             containerTransition.lastInIsPop = isPop;
   1175             containerTransition.lastInTransaction = transaction;
   1176         }
   1177         if (!isReorderedTransaction && wasAdded) {
   1178             if (containerTransition != null && containerTransition.firstOut == fragment) {
   1179                 containerTransition.firstOut = null;
   1180             }
   1181 
   1182             /*
   1183              * Ensure that fragments that are entering are at least at the CREATED state
   1184              * so that they may load Transitions using TransitionInflater.
   1185              */
   1186             FragmentManagerImpl manager = transaction.mManager;
   1187             if (fragment.mState < Fragment.CREATED && manager.mCurState >= Fragment.CREATED
   1188                     && !transaction.mReorderingAllowed) {
   1189                 manager.makeActive(fragment);
   1190                 manager.moveToState(fragment, Fragment.CREATED, 0, 0, false);
   1191             }
   1192         }
   1193         if (setFirstOut && (containerTransition == null || containerTransition.firstOut == null)) {
   1194             containerTransition =
   1195                     ensureContainer(containerTransition, transitioningFragments, containerId);
   1196             containerTransition.firstOut = fragment;
   1197             containerTransition.firstOutIsPop = isPop;
   1198             containerTransition.firstOutTransaction = transaction;
   1199         }
   1200 
   1201         if (!isReorderedTransaction && wasRemoved
   1202                 && (containerTransition != null && containerTransition.lastIn == fragment)) {
   1203             containerTransition.lastIn = null;
   1204         }
   1205     }
   1206 
   1207     /**
   1208      * Ensures that a FragmentContainerTransition has been added to the SparseArray. If so,
   1209      * it returns the existing one. If not, one is created and added to the SparseArray and
   1210      * returned.
   1211      */
   1212     private static FragmentContainerTransition ensureContainer(
   1213             FragmentContainerTransition containerTransition,
   1214             SparseArray<FragmentContainerTransition> transitioningFragments, int containerId) {
   1215         if (containerTransition == null) {
   1216             containerTransition = new FragmentContainerTransition();
   1217             transitioningFragments.put(containerId, containerTransition);
   1218         }
   1219         return containerTransition;
   1220     }
   1221 
   1222     /**
   1223      * Tracks the last fragment added and first fragment removed for fragment transitions.
   1224      * This also tracks which fragments are changed by push or pop transactions.
   1225      */
   1226     static class FragmentContainerTransition {
   1227         /**
   1228          * The last fragment added/attached/shown in its container
   1229          */
   1230         public Fragment lastIn;
   1231 
   1232         /**
   1233          * true when lastIn was added during a pop transaction or false if added with a push
   1234          */
   1235         public boolean lastInIsPop;
   1236 
   1237         /**
   1238          * The transaction that included the last in fragment
   1239          */
   1240         public BackStackRecord lastInTransaction;
   1241 
   1242         /**
   1243          * The first fragment with a View that was removed/detached/hidden in its container.
   1244          */
   1245         public Fragment firstOut;
   1246 
   1247         /**
   1248          * true when firstOut was removed during a pop transaction or false otherwise
   1249          */
   1250         public boolean firstOutIsPop;
   1251 
   1252         /**
   1253          * The transaction that included the first out fragment
   1254          */
   1255         public BackStackRecord firstOutTransaction;
   1256     }
   1257 
   1258     private FragmentTransition() {
   1259     }
   1260 }
   1261