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 */
16package androidx.fragment.app;
17
18import android.graphics.Rect;
19import android.os.Build;
20import android.util.SparseArray;
21import android.view.View;
22import android.view.ViewGroup;
23
24import androidx.collection.ArrayMap;
25import androidx.core.app.SharedElementCallback;
26import androidx.core.view.ViewCompat;
27
28import java.util.ArrayList;
29import java.util.Collection;
30import java.util.List;
31import 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 */
39class 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