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