1 /*
2  * Copyright (C) 2014 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 android.app;
17 
18 import android.animation.Animator;
19 import android.animation.AnimatorListenerAdapter;
20 import android.animation.ObjectAnimator;
21 import android.app.SharedElementCallback.OnSharedElementsReadyListener;
22 import android.graphics.Color;
23 import android.graphics.drawable.ColorDrawable;
24 import android.graphics.drawable.Drawable;
25 import android.os.Bundle;
26 import android.os.ResultReceiver;
27 import android.text.TextUtils;
28 import android.transition.Transition;
29 import android.transition.TransitionListenerAdapter;
30 import android.transition.TransitionManager;
31 import android.util.ArrayMap;
32 import android.view.View;
33 import android.view.ViewGroup;
34 import android.view.ViewGroupOverlay;
35 import android.view.ViewTreeObserver;
36 import android.view.Window;
37 import android.view.accessibility.AccessibilityEvent;
38 
39 import com.android.internal.view.OneShotPreDrawListener;
40 
41 import java.util.ArrayList;
42 
43 /**
44  * This ActivityTransitionCoordinator is created by the Activity to manage
45  * the enter scene and shared element transfer into the Scene, either during
46  * launch of an Activity or returning from a launched Activity.
47  */
48 class EnterTransitionCoordinator extends ActivityTransitionCoordinator {
49     private static final String TAG = "EnterTransitionCoordinator";
50 
51     private static final int MIN_ANIMATION_FRAMES = 2;
52 
53     private boolean mSharedElementTransitionStarted;
54     private Activity mActivity;
55     private boolean mHasStopped;
56     private boolean mIsCanceled;
57     private ObjectAnimator mBackgroundAnimator;
58     private boolean mIsExitTransitionComplete;
59     private boolean mIsReadyForTransition;
60     private Bundle mSharedElementsBundle;
61     private boolean mWasOpaque;
62     private boolean mAreViewsReady;
63     private boolean mIsViewsTransitionStarted;
64     private Transition mEnterViewsTransition;
65     private OneShotPreDrawListener mViewsReadyListener;
66     private final boolean mIsCrossTask;
67     private Drawable mReplacedBackground;
68 
EnterTransitionCoordinator(Activity activity, ResultReceiver resultReceiver, ArrayList<String> sharedElementNames, boolean isReturning, boolean isCrossTask)69     public EnterTransitionCoordinator(Activity activity, ResultReceiver resultReceiver,
70             ArrayList<String> sharedElementNames, boolean isReturning, boolean isCrossTask) {
71         super(activity.getWindow(), sharedElementNames,
72                 getListener(activity, isReturning && !isCrossTask), isReturning);
73         mActivity = activity;
74         mIsCrossTask = isCrossTask;
75         setResultReceiver(resultReceiver);
76         prepareEnter();
77         Bundle resultReceiverBundle = new Bundle();
78         resultReceiverBundle.putParcelable(KEY_REMOTE_RECEIVER, this);
79         mResultReceiver.send(MSG_SET_REMOTE_RECEIVER, resultReceiverBundle);
80         final View decorView = getDecor();
81         if (decorView != null) {
82             final ViewTreeObserver viewTreeObserver = decorView.getViewTreeObserver();
83             viewTreeObserver.addOnPreDrawListener(
84                     new ViewTreeObserver.OnPreDrawListener() {
85                         @Override
86                         public boolean onPreDraw() {
87                             if (mIsReadyForTransition) {
88                                 if (viewTreeObserver.isAlive()) {
89                                     viewTreeObserver.removeOnPreDrawListener(this);
90                                 } else {
91                                     decorView.getViewTreeObserver().removeOnPreDrawListener(this);
92                                 }
93                             }
94                             return false;
95                         }
96                     });
97         }
98     }
99 
isCrossTask()100     boolean isCrossTask() {
101         return mIsCrossTask;
102     }
103 
viewInstancesReady(ArrayList<String> accepted, ArrayList<String> localNames, ArrayList<View> localViews)104     public void viewInstancesReady(ArrayList<String> accepted, ArrayList<String> localNames,
105             ArrayList<View> localViews) {
106         boolean remap = false;
107         for (int i = 0; i < localViews.size(); i++) {
108             View view = localViews.get(i);
109             if (!TextUtils.equals(view.getTransitionName(), localNames.get(i))
110                     || !view.isAttachedToWindow()) {
111                 remap = true;
112                 break;
113             }
114         }
115         if (remap) {
116             triggerViewsReady(mapNamedElements(accepted, localNames));
117         } else {
118             triggerViewsReady(mapSharedElements(accepted, localViews));
119         }
120     }
121 
namedViewsReady(ArrayList<String> accepted, ArrayList<String> localNames)122     public void namedViewsReady(ArrayList<String> accepted, ArrayList<String> localNames) {
123         triggerViewsReady(mapNamedElements(accepted, localNames));
124     }
125 
getEnterViewsTransition()126     public Transition getEnterViewsTransition() {
127         return mEnterViewsTransition;
128     }
129 
130     @Override
viewsReady(ArrayMap<String, View> sharedElements)131     protected void viewsReady(ArrayMap<String, View> sharedElements) {
132         super.viewsReady(sharedElements);
133         mIsReadyForTransition = true;
134         hideViews(mSharedElements);
135         Transition viewsTransition = getViewsTransition();
136         if (viewsTransition != null && mTransitioningViews != null) {
137             removeExcludedViews(viewsTransition, mTransitioningViews);
138             stripOffscreenViews();
139             hideViews(mTransitioningViews);
140         }
141         if (mIsReturning) {
142             sendSharedElementDestination();
143         } else {
144             moveSharedElementsToOverlay();
145         }
146         if (mSharedElementsBundle != null) {
147             onTakeSharedElements();
148         }
149     }
150 
triggerViewsReady(final ArrayMap<String, View> sharedElements)151     private void triggerViewsReady(final ArrayMap<String, View> sharedElements) {
152         if (mAreViewsReady) {
153             return;
154         }
155         mAreViewsReady = true;
156         final ViewGroup decor = getDecor();
157         // Ensure the views have been laid out before capturing the views -- we need the epicenter.
158         if (decor == null || (decor.isAttachedToWindow() &&
159                 (sharedElements.isEmpty() || !sharedElements.valueAt(0).isLayoutRequested()))) {
160             viewsReady(sharedElements);
161         } else {
162             mViewsReadyListener = OneShotPreDrawListener.add(decor, () -> {
163                 mViewsReadyListener = null;
164                 viewsReady(sharedElements);
165             });
166             decor.invalidate();
167         }
168     }
169 
mapNamedElements(ArrayList<String> accepted, ArrayList<String> localNames)170     private ArrayMap<String, View> mapNamedElements(ArrayList<String> accepted,
171             ArrayList<String> localNames) {
172         ArrayMap<String, View> sharedElements = new ArrayMap<String, View>();
173         ViewGroup decorView = getDecor();
174         if (decorView != null) {
175             decorView.findNamedViews(sharedElements);
176         }
177         if (accepted != null) {
178             for (int i = 0; i < localNames.size(); i++) {
179                 String localName = localNames.get(i);
180                 String acceptedName = accepted.get(i);
181                 if (localName != null && !localName.equals(acceptedName)) {
182                     View view = sharedElements.get(localName);
183                     if (view != null) {
184                         sharedElements.put(acceptedName, view);
185                     }
186                 }
187             }
188         }
189         return sharedElements;
190     }
191 
sendSharedElementDestination()192     private void sendSharedElementDestination() {
193         boolean allReady;
194         final View decorView = getDecor();
195         if (allowOverlappingTransitions() && getEnterViewsTransition() != null) {
196             allReady = false;
197         } else if (decorView == null) {
198             allReady = true;
199         } else {
200             allReady = !decorView.isLayoutRequested();
201             if (allReady) {
202                 for (int i = 0; i < mSharedElements.size(); i++) {
203                     if (mSharedElements.get(i).isLayoutRequested()) {
204                         allReady = false;
205                         break;
206                     }
207                 }
208             }
209         }
210         if (allReady) {
211             Bundle state = captureSharedElementState();
212             moveSharedElementsToOverlay();
213             mResultReceiver.send(MSG_SHARED_ELEMENT_DESTINATION, state);
214         } else if (decorView != null) {
215             OneShotPreDrawListener.add(decorView, () -> {
216                 if (mResultReceiver != null) {
217                     Bundle state = captureSharedElementState();
218                     moveSharedElementsToOverlay();
219                     mResultReceiver.send(MSG_SHARED_ELEMENT_DESTINATION, state);
220                 }
221             });
222         }
223         if (allowOverlappingTransitions()) {
224             startEnterTransitionOnly();
225         }
226     }
227 
getListener(Activity activity, boolean isReturning)228     private static SharedElementCallback getListener(Activity activity, boolean isReturning) {
229         return isReturning ? activity.mExitTransitionListener : activity.mEnterTransitionListener;
230     }
231 
232     @Override
onReceiveResult(int resultCode, Bundle resultData)233     protected void onReceiveResult(int resultCode, Bundle resultData) {
234         switch (resultCode) {
235             case MSG_TAKE_SHARED_ELEMENTS:
236                 if (!mIsCanceled) {
237                     mSharedElementsBundle = resultData;
238                     onTakeSharedElements();
239                 }
240                 break;
241             case MSG_EXIT_TRANSITION_COMPLETE:
242                 if (!mIsCanceled) {
243                     mIsExitTransitionComplete = true;
244                     if (mSharedElementTransitionStarted) {
245                         onRemoteExitTransitionComplete();
246                     }
247                 }
248                 break;
249             case MSG_CANCEL:
250                 cancel();
251                 break;
252         }
253     }
254 
isWaitingForRemoteExit()255     public boolean isWaitingForRemoteExit() {
256         return mIsReturning && mResultReceiver != null;
257     }
258 
259     /**
260      * This is called onResume. If an Activity is resuming and the transitions
261      * haven't started yet, force the views to appear. This is likely to be
262      * caused by the top Activity finishing before the transitions started.
263      * In that case, we can finish any transition that was started, but we
264      * should cancel any pending transition and just bring those Views visible.
265      */
forceViewsToAppear()266     public void forceViewsToAppear() {
267         if (!mIsReturning) {
268             return;
269         }
270         if (!mIsReadyForTransition) {
271             mIsReadyForTransition = true;
272             final ViewGroup decor = getDecor();
273             if (decor != null && mViewsReadyListener != null) {
274                 mViewsReadyListener.removeListener();
275                 mViewsReadyListener = null;
276             }
277             showViews(mTransitioningViews, true);
278             setTransitioningViewsVisiblity(View.VISIBLE, true);
279             mSharedElements.clear();
280             mAllSharedElementNames.clear();
281             mTransitioningViews.clear();
282             mIsReadyForTransition = true;
283             viewsTransitionComplete();
284             sharedElementTransitionComplete();
285         } else {
286             if (!mSharedElementTransitionStarted) {
287                 moveSharedElementsFromOverlay();
288                 mSharedElementTransitionStarted = true;
289                 showViews(mSharedElements, true);
290                 mSharedElements.clear();
291                 sharedElementTransitionComplete();
292             }
293             if (!mIsViewsTransitionStarted) {
294                 mIsViewsTransitionStarted = true;
295                 showViews(mTransitioningViews, true);
296                 setTransitioningViewsVisiblity(View.VISIBLE, true);
297                 mTransitioningViews.clear();
298                 viewsTransitionComplete();
299             }
300             cancelPendingTransitions();
301         }
302         mAreViewsReady = true;
303         if (mResultReceiver != null) {
304             mResultReceiver.send(MSG_CANCEL, null);
305             mResultReceiver = null;
306         }
307     }
308 
cancel()309     private void cancel() {
310         if (!mIsCanceled) {
311             mIsCanceled = true;
312             if (getViewsTransition() == null || mIsViewsTransitionStarted) {
313                 showViews(mSharedElements, true);
314             } else if (mTransitioningViews != null) {
315                 mTransitioningViews.addAll(mSharedElements);
316             }
317             moveSharedElementsFromOverlay();
318             mSharedElementNames.clear();
319             mSharedElements.clear();
320             mAllSharedElementNames.clear();
321             startSharedElementTransition(null);
322             onRemoteExitTransitionComplete();
323         }
324     }
325 
isReturning()326     public boolean isReturning() {
327         return mIsReturning;
328     }
329 
prepareEnter()330     protected void prepareEnter() {
331         ViewGroup decorView = getDecor();
332         if (mActivity == null || decorView == null) {
333             return;
334         }
335         if (!isCrossTask()) {
336             mActivity.overridePendingTransition(0, 0);
337         }
338         if (!mIsReturning) {
339             mWasOpaque = mActivity.convertToTranslucent(null, null);
340             Drawable background = decorView.getBackground();
341             if (background == null) {
342                 background = new ColorDrawable(Color.TRANSPARENT);
343                 mReplacedBackground = background;
344             } else {
345                 getWindow().setBackgroundDrawable(null);
346                 background = background.mutate();
347                 background.setAlpha(0);
348             }
349             getWindow().setBackgroundDrawable(background);
350         } else {
351             mActivity = null; // all done with it now.
352         }
353     }
354 
355     @Override
getViewsTransition()356     protected Transition getViewsTransition() {
357         Window window = getWindow();
358         if (window == null) {
359             return null;
360         }
361         if (mIsReturning) {
362             return window.getReenterTransition();
363         } else {
364             return window.getEnterTransition();
365         }
366     }
367 
getSharedElementTransition()368     protected Transition getSharedElementTransition() {
369         Window window = getWindow();
370         if (window == null) {
371             return null;
372         }
373         if (mIsReturning) {
374             return window.getSharedElementReenterTransition();
375         } else {
376             return window.getSharedElementEnterTransition();
377         }
378     }
379 
startSharedElementTransition(Bundle sharedElementState)380     private void startSharedElementTransition(Bundle sharedElementState) {
381         ViewGroup decorView = getDecor();
382         if (decorView == null) {
383             return;
384         }
385         // Remove rejected shared elements
386         ArrayList<String> rejectedNames = new ArrayList<String>(mAllSharedElementNames);
387         rejectedNames.removeAll(mSharedElementNames);
388         ArrayList<View> rejectedSnapshots = createSnapshots(sharedElementState, rejectedNames);
389         if (mListener != null) {
390             mListener.onRejectSharedElements(rejectedSnapshots);
391         }
392         removeNullViews(rejectedSnapshots);
393         startRejectedAnimations(rejectedSnapshots);
394 
395         // Now start shared element transition
396         ArrayList<View> sharedElementSnapshots = createSnapshots(sharedElementState,
397                 mSharedElementNames);
398         showViews(mSharedElements, true);
399         scheduleSetSharedElementEnd(sharedElementSnapshots);
400         ArrayList<SharedElementOriginalState> originalImageViewState =
401                 setSharedElementState(sharedElementState, sharedElementSnapshots);
402         requestLayoutForSharedElements();
403 
404         boolean startEnterTransition = allowOverlappingTransitions() && !mIsReturning;
405         boolean startSharedElementTransition = true;
406         setGhostVisibility(View.INVISIBLE);
407         scheduleGhostVisibilityChange(View.INVISIBLE);
408         pauseInput();
409         Transition transition = beginTransition(decorView, startEnterTransition,
410                 startSharedElementTransition);
411         scheduleGhostVisibilityChange(View.VISIBLE);
412         setGhostVisibility(View.VISIBLE);
413 
414         if (startEnterTransition) {
415             startEnterTransition(transition);
416         }
417 
418         setOriginalSharedElementState(mSharedElements, originalImageViewState);
419 
420         if (mResultReceiver != null) {
421             // We can't trust that the view will disappear on the same frame that the shared
422             // element appears here. Assure that we get at least 2 frames for double-buffering.
423             decorView.postOnAnimation(new Runnable() {
424                 int mAnimations;
425 
426                 @Override
427                 public void run() {
428                     if (mAnimations++ < MIN_ANIMATION_FRAMES) {
429                         View decorView = getDecor();
430                         if (decorView != null) {
431                             decorView.postOnAnimation(this);
432                         }
433                     } else if (mResultReceiver != null) {
434                         mResultReceiver.send(MSG_HIDE_SHARED_ELEMENTS, null);
435                         mResultReceiver = null; // all done sending messages.
436                     }
437                 }
438             });
439         }
440     }
441 
removeNullViews(ArrayList<View> views)442     private static void removeNullViews(ArrayList<View> views) {
443         if (views != null) {
444             for (int i = views.size() - 1; i >= 0; i--) {
445                 if (views.get(i) == null) {
446                     views.remove(i);
447                 }
448             }
449         }
450     }
451 
onTakeSharedElements()452     private void onTakeSharedElements() {
453         if (!mIsReadyForTransition || mSharedElementsBundle == null) {
454             return;
455         }
456         final Bundle sharedElementState = mSharedElementsBundle;
457         mSharedElementsBundle = null;
458         OnSharedElementsReadyListener listener = new OnSharedElementsReadyListener() {
459             @Override
460             public void onSharedElementsReady() {
461                 final View decorView = getDecor();
462                 if (decorView != null) {
463                     OneShotPreDrawListener.add(decorView, false, () -> {
464                         startTransition(() -> {
465                                 startSharedElementTransition(sharedElementState);
466                         });
467                     });
468                     decorView.invalidate();
469                 }
470             }
471         };
472         if (mListener == null) {
473             listener.onSharedElementsReady();
474         } else {
475             mListener.onSharedElementsArrived(mSharedElementNames, mSharedElements, listener);
476         }
477     }
478 
requestLayoutForSharedElements()479     private void requestLayoutForSharedElements() {
480         int numSharedElements = mSharedElements.size();
481         for (int i = 0; i < numSharedElements; i++) {
482             mSharedElements.get(i).requestLayout();
483         }
484     }
485 
beginTransition(ViewGroup decorView, boolean startEnterTransition, boolean startSharedElementTransition)486     private Transition beginTransition(ViewGroup decorView, boolean startEnterTransition,
487             boolean startSharedElementTransition) {
488         Transition sharedElementTransition = null;
489         if (startSharedElementTransition) {
490             if (!mSharedElementNames.isEmpty()) {
491                 sharedElementTransition = configureTransition(getSharedElementTransition(), false);
492             }
493             if (sharedElementTransition == null) {
494                 sharedElementTransitionStarted();
495                 sharedElementTransitionComplete();
496             } else {
497                 sharedElementTransition.addListener(new TransitionListenerAdapter() {
498                     @Override
499                     public void onTransitionStart(Transition transition) {
500                         sharedElementTransitionStarted();
501                     }
502 
503                     @Override
504                     public void onTransitionEnd(Transition transition) {
505                         transition.removeListener(this);
506                         sharedElementTransitionComplete();
507                     }
508                 });
509             }
510         }
511         Transition viewsTransition = null;
512         if (startEnterTransition) {
513             mIsViewsTransitionStarted = true;
514             if (mTransitioningViews != null && !mTransitioningViews.isEmpty()) {
515                 viewsTransition = configureTransition(getViewsTransition(), true);
516             }
517             if (viewsTransition == null) {
518                 viewsTransitionComplete();
519             } else {
520                 final ArrayList<View> transitioningViews = mTransitioningViews;
521                 viewsTransition.addListener(new ContinueTransitionListener() {
522                     @Override
523                     public void onTransitionStart(Transition transition) {
524                         mEnterViewsTransition = transition;
525                         if (transitioningViews != null) {
526                             showViews(transitioningViews, false);
527                         }
528                         super.onTransitionStart(transition);
529                     }
530 
531                     @Override
532                     public void onTransitionEnd(Transition transition) {
533                         mEnterViewsTransition = null;
534                         transition.removeListener(this);
535                         viewsTransitionComplete();
536                         super.onTransitionEnd(transition);
537                     }
538                 });
539             }
540         }
541 
542         Transition transition = mergeTransitions(sharedElementTransition, viewsTransition);
543         if (transition != null) {
544             transition.addListener(new ContinueTransitionListener());
545             if (startEnterTransition) {
546                 setTransitioningViewsVisiblity(View.INVISIBLE, false);
547             }
548             TransitionManager.beginDelayedTransition(decorView, transition);
549             if (startEnterTransition) {
550                 setTransitioningViewsVisiblity(View.VISIBLE, false);
551             }
552             decorView.invalidate();
553         } else {
554             transitionStarted();
555         }
556         return transition;
557     }
558 
559     @Override
onTransitionsComplete()560     protected void onTransitionsComplete() {
561         moveSharedElementsFromOverlay();
562         final ViewGroup decorView = getDecor();
563         if (decorView != null) {
564             decorView.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
565 
566             Window window = getWindow();
567             if (window != null && mReplacedBackground == decorView.getBackground()) {
568                 window.setBackgroundDrawable(null);
569             }
570         }
571     }
572 
sharedElementTransitionStarted()573     private void sharedElementTransitionStarted() {
574         mSharedElementTransitionStarted = true;
575         if (mIsExitTransitionComplete) {
576             send(MSG_EXIT_TRANSITION_COMPLETE, null);
577         }
578     }
579 
startEnterTransition(Transition transition)580     private void startEnterTransition(Transition transition) {
581         ViewGroup decorView = getDecor();
582         if (!mIsReturning && decorView != null) {
583             Drawable background = decorView.getBackground();
584             if (background != null) {
585                 background = background.mutate();
586                 getWindow().setBackgroundDrawable(background);
587                 mBackgroundAnimator = ObjectAnimator.ofInt(background, "alpha", 255);
588                 mBackgroundAnimator.setDuration(getFadeDuration());
589                 mBackgroundAnimator.addListener(new AnimatorListenerAdapter() {
590                     @Override
591                     public void onAnimationEnd(Animator animation) {
592                         makeOpaque();
593                         backgroundAnimatorComplete();
594                     }
595                 });
596                 mBackgroundAnimator.start();
597             } else if (transition != null) {
598                 transition.addListener(new TransitionListenerAdapter() {
599                     @Override
600                     public void onTransitionEnd(Transition transition) {
601                         transition.removeListener(this);
602                         makeOpaque();
603                     }
604                 });
605                 backgroundAnimatorComplete();
606             } else {
607                 makeOpaque();
608                 backgroundAnimatorComplete();
609             }
610         } else {
611             backgroundAnimatorComplete();
612         }
613     }
614 
stop()615     public void stop() {
616         // Restore the background to its previous state since the
617         // Activity is stopping.
618         if (mBackgroundAnimator != null) {
619             mBackgroundAnimator.end();
620             mBackgroundAnimator = null;
621         } else if (mWasOpaque) {
622             ViewGroup decorView = getDecor();
623             if (decorView != null) {
624                 Drawable drawable = decorView.getBackground();
625                 if (drawable != null) {
626                     drawable.setAlpha(1);
627                 }
628             }
629         }
630         makeOpaque();
631         mIsCanceled = true;
632         mResultReceiver = null;
633         mActivity = null;
634         moveSharedElementsFromOverlay();
635         if (mTransitioningViews != null) {
636             showViews(mTransitioningViews, true);
637             setTransitioningViewsVisiblity(View.VISIBLE, true);
638         }
639         showViews(mSharedElements, true);
640         clearState();
641     }
642 
643     /**
644      * Cancels the enter transition.
645      * @return True if the enter transition is still pending capturing the target state. If so,
646      * any transition started on the decor will do nothing.
647      */
cancelEnter()648     public boolean cancelEnter() {
649         setGhostVisibility(View.INVISIBLE);
650         mHasStopped = true;
651         mIsCanceled = true;
652         clearState();
653         return super.cancelPendingTransitions();
654     }
655 
656     @Override
clearState()657     protected void clearState() {
658         mSharedElementsBundle = null;
659         mEnterViewsTransition = null;
660         mResultReceiver = null;
661         if (mBackgroundAnimator != null) {
662             mBackgroundAnimator.cancel();
663             mBackgroundAnimator = null;
664         }
665         super.clearState();
666     }
667 
makeOpaque()668     private void makeOpaque() {
669         if (!mHasStopped && mActivity != null) {
670             if (mWasOpaque) {
671                 mActivity.convertFromTranslucent();
672             }
673             mActivity = null;
674         }
675     }
676 
allowOverlappingTransitions()677     private boolean allowOverlappingTransitions() {
678         return mIsReturning ? getWindow().getAllowReturnTransitionOverlap()
679                 : getWindow().getAllowEnterTransitionOverlap();
680     }
681 
startRejectedAnimations(final ArrayList<View> rejectedSnapshots)682     private void startRejectedAnimations(final ArrayList<View> rejectedSnapshots) {
683         if (rejectedSnapshots == null || rejectedSnapshots.isEmpty()) {
684             return;
685         }
686         final ViewGroup decorView = getDecor();
687         if (decorView != null) {
688             ViewGroupOverlay overlay = decorView.getOverlay();
689             ObjectAnimator animator = null;
690             int numRejected = rejectedSnapshots.size();
691             for (int i = 0; i < numRejected; i++) {
692                 View snapshot = rejectedSnapshots.get(i);
693                 overlay.add(snapshot);
694                 animator = ObjectAnimator.ofFloat(snapshot, View.ALPHA, 1, 0);
695                 animator.start();
696             }
697             animator.addListener(new AnimatorListenerAdapter() {
698                 @Override
699                 public void onAnimationEnd(Animator animation) {
700                     ViewGroupOverlay overlay = decorView.getOverlay();
701                     int numRejected = rejectedSnapshots.size();
702                     for (int i = 0; i < numRejected; i++) {
703                         overlay.remove(rejectedSnapshots.get(i));
704                     }
705                 }
706             });
707         }
708     }
709 
onRemoteExitTransitionComplete()710     protected void onRemoteExitTransitionComplete() {
711         if (!allowOverlappingTransitions()) {
712             startEnterTransitionOnly();
713         }
714     }
715 
startEnterTransitionOnly()716     private void startEnterTransitionOnly() {
717         startTransition(new Runnable() {
718             @Override
719             public void run() {
720                 boolean startEnterTransition = true;
721                 boolean startSharedElementTransition = false;
722                 ViewGroup decorView = getDecor();
723                 if (decorView != null) {
724                     Transition transition = beginTransition(decorView, startEnterTransition,
725                             startSharedElementTransition);
726                     startEnterTransition(transition);
727                 }
728             }
729         });
730     }
731 }
732