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.content.Intent;
     23 import android.graphics.Color;
     24 import android.graphics.Matrix;
     25 import android.graphics.RectF;
     26 import android.graphics.drawable.ColorDrawable;
     27 import android.graphics.drawable.Drawable;
     28 import android.os.Build.VERSION_CODES;
     29 import android.os.Bundle;
     30 import android.os.Handler;
     31 import android.os.Message;
     32 import android.os.ResultReceiver;
     33 import android.transition.Transition;
     34 import android.transition.TransitionListenerAdapter;
     35 import android.transition.TransitionManager;
     36 import android.view.View;
     37 import android.view.ViewGroup;
     38 import android.view.Window;
     39 
     40 import com.android.internal.view.OneShotPreDrawListener;
     41 
     42 import java.util.ArrayList;
     43 
     44 /**
     45  * This ActivityTransitionCoordinator is created in ActivityOptions#makeSceneTransitionAnimation
     46  * to govern the exit of the Scene and the shared elements when calling an Activity as well as
     47  * the reentry of the Scene when coming back from the called Activity.
     48  */
     49 class ExitTransitionCoordinator extends ActivityTransitionCoordinator {
     50     private static final String TAG = "ExitTransitionCoordinator";
     51     private static final long MAX_WAIT_MS = 1000;
     52 
     53     private Bundle mSharedElementBundle;
     54     private boolean mExitNotified;
     55     private boolean mSharedElementNotified;
     56     private Activity mActivity;
     57     private boolean mIsBackgroundReady;
     58     private boolean mIsCanceled;
     59     private Handler mHandler;
     60     private ObjectAnimator mBackgroundAnimator;
     61     private boolean mIsHidden;
     62     private Bundle mExitSharedElementBundle;
     63     private boolean mIsExitStarted;
     64     private boolean mSharedElementsHidden;
     65     private HideSharedElementsCallback mHideSharedElementsCallback;
     66 
     67     public ExitTransitionCoordinator(Activity activity, Window window,
     68             SharedElementCallback listener, ArrayList<String> names,
     69             ArrayList<String> accepted, ArrayList<View> mapped, boolean isReturning) {
     70         super(window, names, listener, isReturning);
     71         viewsReady(mapSharedElements(accepted, mapped));
     72         stripOffscreenViews();
     73         mIsBackgroundReady = !isReturning;
     74         mActivity = activity;
     75     }
     76 
     77     void setHideSharedElementsCallback(HideSharedElementsCallback callback) {
     78         mHideSharedElementsCallback = callback;
     79     }
     80 
     81     @Override
     82     protected void onReceiveResult(int resultCode, Bundle resultData) {
     83         switch (resultCode) {
     84             case MSG_SET_REMOTE_RECEIVER:
     85                 stopCancel();
     86                 mResultReceiver = resultData.getParcelable(KEY_REMOTE_RECEIVER);
     87                 if (mIsCanceled) {
     88                     mResultReceiver.send(MSG_CANCEL, null);
     89                     mResultReceiver = null;
     90                 } else {
     91                     notifyComplete();
     92                 }
     93                 break;
     94             case MSG_HIDE_SHARED_ELEMENTS:
     95                 stopCancel();
     96                 if (!mIsCanceled) {
     97                     hideSharedElements();
     98                 }
     99                 break;
    100             case MSG_START_EXIT_TRANSITION:
    101                 mHandler.removeMessages(MSG_CANCEL);
    102                 startExit();
    103                 break;
    104             case MSG_SHARED_ELEMENT_DESTINATION:
    105                 mExitSharedElementBundle = resultData;
    106                 sharedElementExitBack();
    107                 break;
    108             case MSG_CANCEL:
    109                 mIsCanceled = true;
    110                 finish();
    111                 break;
    112         }
    113     }
    114 
    115     private void stopCancel() {
    116         if (mHandler != null) {
    117             mHandler.removeMessages(MSG_CANCEL);
    118         }
    119     }
    120 
    121     private void delayCancel() {
    122         if (mHandler != null) {
    123             mHandler.sendEmptyMessageDelayed(MSG_CANCEL, MAX_WAIT_MS);
    124         }
    125     }
    126 
    127     public void resetViews() {
    128         ViewGroup decorView = getDecor();
    129         if (decorView != null) {
    130             TransitionManager.endTransitions(decorView);
    131         }
    132         if (mTransitioningViews != null) {
    133             showViews(mTransitioningViews, true);
    134             setTransitioningViewsVisiblity(View.VISIBLE, true);
    135         }
    136         showViews(mSharedElements, true);
    137         mIsHidden = true;
    138         if (!mIsReturning && decorView != null) {
    139             decorView.suppressLayout(false);
    140         }
    141         moveSharedElementsFromOverlay();
    142         clearState();
    143     }
    144 
    145     private void sharedElementExitBack() {
    146         final ViewGroup decorView = getDecor();
    147         if (decorView != null) {
    148             decorView.suppressLayout(true);
    149         }
    150         if (decorView != null && mExitSharedElementBundle != null &&
    151                 !mExitSharedElementBundle.isEmpty() &&
    152                 !mSharedElements.isEmpty() && getSharedElementTransition() != null) {
    153             startTransition(new Runnable() {
    154                 public void run() {
    155                     startSharedElementExit(decorView);
    156                 }
    157             });
    158         } else {
    159             sharedElementTransitionComplete();
    160         }
    161     }
    162 
    163     private void startSharedElementExit(final ViewGroup decorView) {
    164         Transition transition = getSharedElementExitTransition();
    165         transition.addListener(new TransitionListenerAdapter() {
    166             @Override
    167             public void onTransitionEnd(Transition transition) {
    168                 transition.removeListener(this);
    169                 if (isViewsTransitionComplete()) {
    170                     delayCancel();
    171                 }
    172             }
    173         });
    174         final ArrayList<View> sharedElementSnapshots = createSnapshots(mExitSharedElementBundle,
    175                 mSharedElementNames);
    176         OneShotPreDrawListener.add(decorView, () -> {
    177             setSharedElementState(mExitSharedElementBundle, sharedElementSnapshots);
    178         });
    179         setGhostVisibility(View.INVISIBLE);
    180         scheduleGhostVisibilityChange(View.INVISIBLE);
    181         if (mListener != null) {
    182             mListener.onSharedElementEnd(mSharedElementNames, mSharedElements,
    183                     sharedElementSnapshots);
    184         }
    185         TransitionManager.beginDelayedTransition(decorView, transition);
    186         scheduleGhostVisibilityChange(View.VISIBLE);
    187         setGhostVisibility(View.VISIBLE);
    188         decorView.invalidate();
    189     }
    190 
    191     private void hideSharedElements() {
    192         moveSharedElementsFromOverlay();
    193         if (mHideSharedElementsCallback != null) {
    194             mHideSharedElementsCallback.hideSharedElements();
    195         }
    196         if (!mIsHidden) {
    197             hideViews(mSharedElements);
    198         }
    199         mSharedElementsHidden = true;
    200         finishIfNecessary();
    201     }
    202 
    203     public void startExit() {
    204         if (!mIsExitStarted) {
    205             backgroundAnimatorComplete();
    206             mIsExitStarted = true;
    207             pauseInput();
    208             ViewGroup decorView = getDecor();
    209             if (decorView != null) {
    210                 decorView.suppressLayout(true);
    211             }
    212             moveSharedElementsToOverlay();
    213             startTransition(new Runnable() {
    214                 @Override
    215                 public void run() {
    216                     if (mActivity != null) {
    217                         beginTransitions();
    218                     } else {
    219                         startExitTransition();
    220                     }
    221                 }
    222             });
    223         }
    224     }
    225 
    226     public void startExit(int resultCode, Intent data) {
    227         if (!mIsExitStarted) {
    228             mIsExitStarted = true;
    229             pauseInput();
    230             ViewGroup decorView = getDecor();
    231             if (decorView != null) {
    232                 decorView.suppressLayout(true);
    233             }
    234             mHandler = new Handler() {
    235                 @Override
    236                 public void handleMessage(Message msg) {
    237                     mIsCanceled = true;
    238                     finish();
    239                 }
    240             };
    241             delayCancel();
    242             moveSharedElementsToOverlay();
    243             if (decorView != null && decorView.getBackground() == null) {
    244                 getWindow().setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
    245             }
    246             final boolean targetsM = decorView == null || decorView.getContext()
    247                     .getApplicationInfo().targetSdkVersion >= VERSION_CODES.M;
    248             ArrayList<String> sharedElementNames = targetsM ? mSharedElementNames :
    249                     mAllSharedElementNames;
    250             ActivityOptions options = ActivityOptions.makeSceneTransitionAnimation(mActivity, this,
    251                     sharedElementNames, resultCode, data);
    252             mActivity.convertToTranslucent(new Activity.TranslucentConversionListener() {
    253                 @Override
    254                 public void onTranslucentConversionComplete(boolean drawComplete) {
    255                     if (!mIsCanceled) {
    256                         fadeOutBackground();
    257                     }
    258                 }
    259             }, options);
    260             startTransition(new Runnable() {
    261                 @Override
    262                 public void run() {
    263                     startExitTransition();
    264                 }
    265             });
    266         }
    267     }
    268 
    269     public void stop() {
    270         if (mIsReturning && mActivity != null) {
    271             // Override the previous ActivityOptions. We don't want the
    272             // activity to have options since we're essentially canceling the
    273             // transition and finishing right now.
    274             mActivity.convertToTranslucent(null, null);
    275             finish();
    276         }
    277     }
    278 
    279     private void startExitTransition() {
    280         Transition transition = getExitTransition();
    281         ViewGroup decorView = getDecor();
    282         if (transition != null && decorView != null && mTransitioningViews != null) {
    283             setTransitioningViewsVisiblity(View.VISIBLE, false);
    284             TransitionManager.beginDelayedTransition(decorView, transition);
    285             setTransitioningViewsVisiblity(View.INVISIBLE, false);
    286             decorView.invalidate();
    287         } else {
    288             transitionStarted();
    289         }
    290     }
    291 
    292     private void fadeOutBackground() {
    293         if (mBackgroundAnimator == null) {
    294             ViewGroup decor = getDecor();
    295             Drawable background;
    296             if (decor != null && (background = decor.getBackground()) != null) {
    297                 background = background.mutate();
    298                 getWindow().setBackgroundDrawable(background);
    299                 mBackgroundAnimator = ObjectAnimator.ofInt(background, "alpha", 0);
    300                 mBackgroundAnimator.addListener(new AnimatorListenerAdapter() {
    301                     @Override
    302                     public void onAnimationEnd(Animator animation) {
    303                         mBackgroundAnimator = null;
    304                         if (!mIsCanceled) {
    305                             mIsBackgroundReady = true;
    306                             notifyComplete();
    307                         }
    308                         backgroundAnimatorComplete();
    309                     }
    310                 });
    311                 mBackgroundAnimator.setDuration(getFadeDuration());
    312                 mBackgroundAnimator.start();
    313             } else {
    314                 backgroundAnimatorComplete();
    315                 mIsBackgroundReady = true;
    316             }
    317         }
    318     }
    319 
    320     private Transition getExitTransition() {
    321         Transition viewsTransition = null;
    322         if (mTransitioningViews != null && !mTransitioningViews.isEmpty()) {
    323             viewsTransition = configureTransition(getViewsTransition(), true);
    324             removeExcludedViews(viewsTransition, mTransitioningViews);
    325             if (mTransitioningViews.isEmpty()) {
    326                 viewsTransition = null;
    327             }
    328         }
    329         if (viewsTransition == null) {
    330             viewsTransitionComplete();
    331         } else {
    332             final ArrayList<View> transitioningViews = mTransitioningViews;
    333             viewsTransition.addListener(new ContinueTransitionListener() {
    334                 @Override
    335                 public void onTransitionEnd(Transition transition) {
    336                     viewsTransitionComplete();
    337                     if (mIsHidden && transitioningViews != null) {
    338                         showViews(transitioningViews, true);
    339                         setTransitioningViewsVisiblity(View.VISIBLE, true);
    340                     }
    341                     if (mSharedElementBundle != null) {
    342                         delayCancel();
    343                     }
    344                     super.onTransitionEnd(transition);
    345                 }
    346             });
    347         }
    348         return viewsTransition;
    349     }
    350 
    351     private Transition getSharedElementExitTransition() {
    352         Transition sharedElementTransition = null;
    353         if (!mSharedElements.isEmpty()) {
    354             sharedElementTransition = configureTransition(getSharedElementTransition(), false);
    355         }
    356         if (sharedElementTransition == null) {
    357             sharedElementTransitionComplete();
    358         } else {
    359             sharedElementTransition.addListener(new ContinueTransitionListener() {
    360                 @Override
    361                 public void onTransitionEnd(Transition transition) {
    362                     sharedElementTransitionComplete();
    363                     if (mIsHidden) {
    364                         showViews(mSharedElements, true);
    365                     }
    366                     super.onTransitionEnd(transition);
    367                 }
    368             });
    369             mSharedElements.get(0).invalidate();
    370         }
    371         return sharedElementTransition;
    372     }
    373 
    374     private void beginTransitions() {
    375         Transition sharedElementTransition = getSharedElementExitTransition();
    376         Transition viewsTransition = getExitTransition();
    377 
    378         Transition transition = mergeTransitions(sharedElementTransition, viewsTransition);
    379         ViewGroup decorView = getDecor();
    380         if (transition != null && decorView != null) {
    381             setGhostVisibility(View.INVISIBLE);
    382             scheduleGhostVisibilityChange(View.INVISIBLE);
    383             if (viewsTransition != null) {
    384                 setTransitioningViewsVisiblity(View.VISIBLE, false);
    385             }
    386             TransitionManager.beginDelayedTransition(decorView, transition);
    387             scheduleGhostVisibilityChange(View.VISIBLE);
    388             setGhostVisibility(View.VISIBLE);
    389             if (viewsTransition != null) {
    390                 setTransitioningViewsVisiblity(View.INVISIBLE, false);
    391             }
    392             decorView.invalidate();
    393         } else {
    394             transitionStarted();
    395         }
    396     }
    397 
    398     protected boolean isReadyToNotify() {
    399         return mSharedElementBundle != null && mResultReceiver != null && mIsBackgroundReady;
    400     }
    401 
    402     @Override
    403     protected void sharedElementTransitionComplete() {
    404         mSharedElementBundle = mExitSharedElementBundle == null
    405                 ? captureSharedElementState() : captureExitSharedElementsState();
    406         super.sharedElementTransitionComplete();
    407     }
    408 
    409     private Bundle captureExitSharedElementsState() {
    410         Bundle bundle = new Bundle();
    411         RectF bounds = new RectF();
    412         Matrix matrix = new Matrix();
    413         for (int i = 0; i < mSharedElements.size(); i++) {
    414             String name = mSharedElementNames.get(i);
    415             Bundle sharedElementState = mExitSharedElementBundle.getBundle(name);
    416             if (sharedElementState != null) {
    417                 bundle.putBundle(name, sharedElementState);
    418             } else {
    419                 View view = mSharedElements.get(i);
    420                 captureSharedElementState(view, name, bundle, matrix, bounds);
    421             }
    422         }
    423         return bundle;
    424     }
    425 
    426     @Override
    427     protected void onTransitionsComplete() {
    428         notifyComplete();
    429     }
    430 
    431     protected void notifyComplete() {
    432         if (isReadyToNotify()) {
    433             if (!mSharedElementNotified) {
    434                 mSharedElementNotified = true;
    435                 delayCancel();
    436                 if (mListener == null) {
    437                     mResultReceiver.send(MSG_TAKE_SHARED_ELEMENTS, mSharedElementBundle);
    438                     notifyExitComplete();
    439                 } else {
    440                     final ResultReceiver resultReceiver = mResultReceiver;
    441                     final Bundle sharedElementBundle = mSharedElementBundle;
    442                     mListener.onSharedElementsArrived(mSharedElementNames, mSharedElements,
    443                             new OnSharedElementsReadyListener() {
    444                                 @Override
    445                                 public void onSharedElementsReady() {
    446                                     resultReceiver.send(MSG_TAKE_SHARED_ELEMENTS,
    447                                             sharedElementBundle);
    448                                     notifyExitComplete();
    449                                 }
    450                             });
    451                 }
    452             } else {
    453                 notifyExitComplete();
    454             }
    455         }
    456     }
    457 
    458     private void notifyExitComplete() {
    459         if (!mExitNotified && isViewsTransitionComplete()) {
    460             mExitNotified = true;
    461             mResultReceiver.send(MSG_EXIT_TRANSITION_COMPLETE, null);
    462             mResultReceiver = null; // done talking
    463             ViewGroup decorView = getDecor();
    464             if (!mIsReturning && decorView != null) {
    465                 decorView.suppressLayout(false);
    466             }
    467             finishIfNecessary();
    468         }
    469     }
    470 
    471     private void finishIfNecessary() {
    472         if (mIsReturning && mExitNotified && mActivity != null && (mSharedElements.isEmpty() ||
    473                 mSharedElementsHidden)) {
    474             finish();
    475         }
    476         if (!mIsReturning && mExitNotified) {
    477             mActivity = null; // don't need it anymore
    478         }
    479     }
    480 
    481     private void finish() {
    482         stopCancel();
    483         if (mActivity != null) {
    484             mActivity.mActivityTransitionState.clear();
    485             mActivity.finish();
    486             mActivity.overridePendingTransition(0, 0);
    487             mActivity = null;
    488         }
    489         // Clear the state so that we can't hold any references accidentally and leak memory.
    490         clearState();
    491     }
    492 
    493     @Override
    494     protected void clearState() {
    495         mHandler = null;
    496         mSharedElementBundle = null;
    497         if (mBackgroundAnimator != null) {
    498             mBackgroundAnimator.cancel();
    499             mBackgroundAnimator = null;
    500         }
    501         mExitSharedElementBundle = null;
    502         super.clearState();
    503     }
    504 
    505     @Override
    506     protected boolean moveSharedElementWithParent() {
    507         return !mIsReturning;
    508     }
    509 
    510     @Override
    511     protected Transition getViewsTransition() {
    512         if (mIsReturning) {
    513             return getWindow().getReturnTransition();
    514         } else {
    515             return getWindow().getExitTransition();
    516         }
    517     }
    518 
    519     protected Transition getSharedElementTransition() {
    520         if (mIsReturning) {
    521             return getWindow().getSharedElementReturnTransition();
    522         } else {
    523             return getWindow().getSharedElementExitTransition();
    524         }
    525     }
    526 
    527     interface HideSharedElementsCallback {
    528         void hideSharedElements();
    529     }
    530 }
    531