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