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