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