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